Design ideas for internal calls of interfaces between microservices (server side)

Foreword

First of all, when it comes to internal calls between interface microservices, the most important thing to consider is how the service provider can dynamically identify which interfaces need to be exposed, without opening the access scope too large to avoid security issues.

Secondly, how to ensure that these exposed interfaces can normally accept front-end service requests or requests forwarded by the gateway without affecting the calls of third-party clients.

Server-side design

Last time it was mentioned in the client that there are four types of interface authentication requests on the server side:

1. User requests forwarded by external GateWay require normal authentication.

2. No authentication is required for calls between external GateWay services.

3. For calls between internal microservices, the request carries the token.

4. External third-party service calls exceed authentication and require request verification.

First of all, if authentication is not required, then you only need to add this interface to the permitAll whitelist of the security filter chain in the service.

Secondly, if the external service cannot be directly accessed and authentication is not required between internal services. First, dynamically add the interface to the permitAll whitelist of the security filter chain and the whitelist for verifying accesstoken (BearerTokenResolver). Then when calling the interface, directly pass the security filter chain, and then determine whether it is the value of the @Inner annotation. Called internally, and verified by judging whether there is a specified field in the header.

Finally, if the outside requires authentication to access the service, and internal services do not require authentication between them. In addition to the normal permitAll whitelist of the security filter chain, the whitelist of the verification accesstoken, and the @Inner annotation, it is also necessary to remove the specified fields in the external request header through the interceptor of the GateWay gateway.

Server code

Interceptor code for externally exposed gateway clearing requests, suitable for situations where external requests require authentication

public class InnerRequestGlobalFilter implements GlobalFilter, Ordered {<!-- --></code><code> private static final String HEADER_NAME = "Specified field";</code><code> </code><code> /**</code><code> * Process the Web request and (optionally) delegate to the next</code><code> * {@code WebFilter} through the given {@link GatewayFilterChain}.</code><code> *</code><code> * @param exchange the current server exchange</code><code> * @param chain provides a way to delegate to the next filter</code><code> * @return {@code Mono<Void>} to indicate when request processing is complete</code><code> */</code><code> @Override</code><code> public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {<!-- --></code><code> // 1. Clean the from parameter in the request header</code><code> ServerHttpRequest request = exchange.getRequest().mutate()</code><code> .headers(httpHeaders -> {httpHeaders.remove(SecurityConstants.FROM);})</code><code> .build();</code><code> return chain.filter(exchange.mutate( )</code><code> .request(newRequest.mutate()</code><code> .header(HEADER_NAME, basePath)</code><code> .build()).build());</code><code> code><code> }</code><code> </code><code> @Override</code><code> public int getOrder() {<!-- --></code><code> return -1000;</code><code> }</code><code>}

Interceptor code for externally exposed gateway clearing requests (external authentication access required)

Dynamically configure the interface address that the server needs to expose, and dynamically modify the list value before the security request chain is created.

@Configuration
@ConditionalOnExpression("!'${user-settings.ignore-url}'.isEmpty()")
@Order(20)
public class PermitIgnoreUrlProperties implements InitializingBean {
    private static final Pattern PATTERN = Pattern.compile("\{(.*?)\}");

    @Autowired
    private WebApplicationContext applicationContext;

    @Autowired
    private UserSetting userSetting;


    @Override
    public void afterPropertiesSet() throws Exception {
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
        //Initialize the collection of filter addresses
        Set<String> ignoreUrlSet = new HashSet<>();
        map.keySet().forEach(info -> {
            HandlerMethod handlerMethod = map.get(info);

            // Get the annotation on the method and replace the path variable with *
            Inner method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Inner.class);
            Optional.ofNullable(method)
                    .ifPresent(inner -> info.getPathPatternsCondition().getPatterns()
                            .forEach(url -> ignoreUrlSet.add(ReUtil.replaceAll(url.getPatternString(), PATTERN, "**"))));

            // Get the annotation on the class, replace path variable with *
            Inner controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Inner.class);
            Optional.ofNullable(controller)
                    .ifPresent(inner -> info.getPathPatternsCondition().getPatterns()
                            .forEach(url -> ignoreUrlSet.add(ReUtil.replaceAll(url.getPatternString(), PATTERN, "**"))));
        });
        List<String> handleIgnoreUrl = Arrays.asList(ignoreUrlSet.stream()
                .collect(Collectors.joining(","))
                .split(","));
        userSetting.setIgnorePath(handleIgnoreUrl);
    }
}

Dynamically configure the interface address that the server needs to expose

Inner annotations used to mark internal access interfaces

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Inner {

    /**
     * Whether AOP is processed uniformly
     *
     * @return false, true
     */
    boolean value() default true;

    /**
     * Fields that require special blanking (reserved)
     *
     * @return {}
     */
    String[] field() default {};
}

Determine whether the exposed interface annotated with @Inner annotation has the specified header information.

 @SneakyThrows
    @Around("@annotation(inner)")
    public Object aroundInner(ProceedingJoinPoint point, Inner inner){
        LoginUser userInfo = SecurityUtils.getUserInfo();
        //If it is a login access, it will be allowed directly.
        if(userInfo!=null){
            return point.proceed();
        }
        //Get the specified header information value of the third-party client request interface
        String thirdClientApiValue = request.getHeader(ThirdAssociateEnum.ALLOW_OUT_CLIENT_API.getValue());
        //Get the request path
        String requestURI = request.getRequestURI();
        //Get today's date
        String formattedDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        //If Aop verification is enabled
        if (BooleanUtil.isTrue(inner.value())) {
            if(StrUtil.isEmpty(thirdClientApiValue)||!encoder.matches(formattedDate + File.separator + requestURI + formattedDate,thirdClientApiValue)){
                log.warn("The interface {} request did not query the specified header information for release, and there is no permission!", point.getSignature().getName());
                throw new AccessDeniedException("The request did not find the specified header information for release, and there is no permission");
            }
        }
        return point.proceed();
    }

@Inner intercepts aspects

The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. Java Skill TreeHomepageOverview 138924 people are learning the system