“This is my 32nd day of participating in the First Challenge 2022. For details: First Challenge 2022”

One, foreword

Microservices need to communicate with each other, which belongs to internal communication, and there are some security problems:

  1. The internal interface cannot be accessed externally.
  2. In traditional micro services, internal communication is secure by default and authentication is not required.

At work, an external request transforms an internal request as shown below:

All requests passapiWhen the gateway enters and converts to an internal request, the following scenarios occur:

  1. External request passingapiGateway for authentication: for example, user logintokenmechanism
  2. External request passingapiGateway without authentication: For example, to obtain platform public information, add the corresponding interface address to the gatewayignore-urlsIn the configuration
  3. Internal requests, trusted by default, generally do not pass through the gateway, by which the call service for load balancing to directly call other services

For example: the administrator passedcmsDelete user

  • Normal process: The call is made firstcmsService, the administrator authentication after the calluserService, delete user B.
  • Exception process: After user A logs in to user B, user A directly deletes user B through the interface.

This article will discuss how to prevent illegal indirect internal access.

Let’s review the service invocationfeignUsage:

  1. Service A definitioncontroller
@RestController
@RequestMapping(path = "/test")
public class TestController {

    @GetMapping("/feign")
    public String test(a) {
    
        return "test feign success"; }}Copy the code
  1. Service B definitionfeign
@FeignClient(value = "test")
public interface TestClient {
    
    @GetMapping(value = "/test/feign")
    String test(a);
} 
Copy the code

Service B invokes service A’s interface through feign.




Second, the design

In order to be directly provided by internal interfaces, the following directions can be considered:

  1. apiThe gateway translates external requests with labels (or deletes corresponding ones)tag)
  2. Each invocation of a service carries a corresponding identifier: for example, inHTTPheaderAdd special parameters
  3. Microservices define interceptors for internal interfaces: they can only be accessed if they are identified as internal interfaces

(1)feignconfiguration

Each internal request carries an identifier: X-inner-request :true.

@Component
public class FeignInnerRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        template.header("x-inner-request".true); }}Copy the code

Simply place the configuration in a file specified by a third-party JAR and the user will load it automatically to avoid code intrusion:

  1. Create a directory under the resource directoryMETA-INF
  2. inMETA-INFCreate a file in the directoryspring.factories
  3. Add the following configuration to the file:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.test.config.InternalApiInterceptor, \
cn.test.config.FeignInnerRequestInterceptor, \
cn.test.config.InternalApiConfig
Copy the code


(2) Gateway filtering

Each external request to an internal request carries an identifier: X-inner-request :false.

Gateway is used here:

@Configuration
public class InternalApiGatewayFilter {

    @Bean
    @Order(1)
    public GlobalFilter filterInteralApi(a) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            request = exchange.getRequest().mutate().header("x-inner-request".false).build();
            returnchain.filter(exchange.mutate().request(request).build()); }; }}Copy the code


(3) Internal request interceptor for microservices

There are two parts to this:

  1. Annotation: Used to indicate that methods or classes are accessed internally.
  2. Interceptor: Used to process interception requests uniformly
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface InternalApi {
    /** * Indicates whether the interface is internal. The default value is true *@returnWhether * /
    boolean value(a) default true;
}
Copy the code

The interceptor:

@Slf4j
@Component
public class InternalApiInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {

        InternalApi internalApi = getAnnotation(handler);

        if (Objects.isNull(internalApi)) {
 
            return true;
        }

        boolean isInternalApi = Boolean.valueOf(request.getHeader("x-inner-request"));
        
        if(! isInternalApi) {throw new Exception(); // Throw an exception and no access is given
        }

        return true;
    }
    
    private InternalApi getAnnotation(Object handler) {

        HandlerMethod handlerMethod = (HandlerMethod) handler;

        InternalApi internalApi = handlerMethod.getMethodAnnotation(InternalApi.class);

        if (Objects.isNull(internalApi)) {

            internalApi = handlerMethod.getMethod()
                .getDeclaringClass().getAnnotation(InternalApi.class);
        }

        return internalApi;
    }

    private <A extends Annotation> A getAnnotation(HandlerMethod handlerMethod, Class annotationType) {

        A annotation = handlerMethod.getMethodAnnotation(annotationType);

        if (Objects.isNull(annotation)) {

            return handlerMethod.getMethod()
                .getDeclaringClass().getAnnotation(annotationType);
        }

        returnannotation; }}Copy the code