Quickly build a gateway service, the process of dynamic routing and authentication, after reading the second meeting (including flow chart)

</dependency>

routing configuration

The gateway is used as a unified entry for requests, and routing is equivalent to the entry of each business system. Through routing rules, it can match the entry of the corresponding microservice and hit the request to the corresponding business system.

server:
  port: 8080

spring:
  cloud:
    gateway:
      enabled: true
      routes:
      - id: demo-server
        uri: http://localhost:8081
        predicates:
        - Path=/demo-server/**
        filters:
          - StripPrefix= 1

routes

Interpret the configuration

  • Now there is a service demo-server deployed on this machine, the address and port are 127.0.0.1:8081, so the routing configuration uri is http://localhost:8081

  • Use the gateway service to route to this service, predicates -Path=/demo-server/**, the port of the gateway service is 8080, start the gateway service, access localhost:8080/demo-server, the routing assertion will route the request to the demo- server

  • Directly access the demo-server interface localhost:8081/api/test, the access address through the gateway is localhost:8080/demo-server/api/test, the predicates configuration will assert the request to this route, filters-StripPrefix=1 means that the The first interception in/after the address, so demo-server intercepts it

It is very convenient to use gateway to complete routing configuration through configuration files. We only need to fully understand the meaning and rules of configuration items; however, if these configurations are to be modified, the service needs to be restarted. Restarting the gateway service will cause the entire system to be unavailable. , this is unacceptable, the following describes how to implement dynamic routing through Nacos

dynamic routing

To use nacos combined with gateway-server to implement dynamic routing, we need to deploy a nacos service first, which can be started locally by using docker deployment or downloading the source code. For details, please refer to the official documentation.

Nacos configuration

groupId: just use the gateway service name

dataId: routes

Configuration format: json

[{
      "id": "xxx-server",
      "order": 1, #priority
      "predicates": [{ #route assertion
          "args": {
              "pattern": "/xxx-server/**"
          },
          "name": "Path"
      }],
      "filters":[{ #filter rules
          "args": {
              "parts": 0 #If the internal access container of the k8s service is http://xxx-server/xxx-server, you can configure 0
          },
          "name": "StripPrefix" #truncation start index
      }],
      "uri": "http://localhost:8080/xxx-server" #target address
  }]

The configuration items in json format correspond to those in yaml. You need to understand how the configuration is written in json.

Pay attention to the official account: Java back-end programming, reply: 666 to receive information.

Compare the json configuration with the yaml configuration

{
    "id":"demo-server",
    "predicates":[
        {
            "args":{
                "pattern":"/demo-server/**"
            },
            "name":"Path"
        }
    ],
    "filters":[
        {
            "args":{
                "parts":1
            },
            "name":"StripPrefix"
        }
    ],
    "uri":"http://localhost:8081"
}
spring:
  cloud:
    gateway:
      enabled: true
      routes:
      - id: demo-server
        uri: http://localhost:8081
        predicates:
        - Path=/demo-server/**
        filters:
          - StripPrefix= 1

Code

The core of the way that Nacos implements dynamic routing is to monitor the configuration through Nacos. After the configuration is changed, the gateway-related api is executed to create a route.

@Component
public class NacosDynamicRouteService implements ApplicationEventPublisherAware {

    private static final Logger LOGGER = LoggerFactory.getLogger(NacosDynamicRouteService.class);

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    private ApplicationEventPublisher applicationEventPublisher;

    /** route id*/
    private static List<String> routeIds = Lists.newArrayList();

    /**
     * Monitor nacos routing configuration and dynamically change routing
     * @param configInfo
     */
    @NacosConfigListener(dataId = "routes", groupId = "gateway-server")
    public void routeConfigListener(String configInfo) {
        clearRoute();
        try {
            List<RouteDefinition> gatewayRouteDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
            for (RouteDefinition routeDefinition : gatewayRouteDefinitions) {
                addRoute(routeDefinition);
            }
            publish();
            LOGGER.info("Dynamic Routing Publish Success");
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
        
    }


    /**
     * clear route
     */
    private void clearRoute() {
        for (String id : routeIds) {
            routeDefinitionWriter.delete(Mono.just(id)).subscribe();
        }
        routeIds.clear();
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    /**
     * add route
     * 
     * @param definition
     */
    private void addRoute(RouteDefinition definition) {
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            routeIds.add(definition.getId());
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
    }

    /**
     * Publish routes and make routes take effect
     */
    private void publish() {
        this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this.routeDefinitionWriter));
    }
}

filter

gateway provides two interfaces, GlobalFilter and Ordered, to define filters. Our custom filter only needs to implement these two interfaces.

  • GlobalFilter filter() implements the filter business

  • Ordered getOrder() defines the filter execution order

Usually, the filtering of a gateway service mainly includes authentication (whether to log in, whether to blacklist, whether to exempt the login interface...) and current limiting (ip current limiting, etc.) functions. Today, we briefly introduce the process implementation of the authentication filter.

Authentication filter

To implement an authentication filter, we must first understand the login and authentication process, as shown in the following figure

As can be seen from the figure, the core of our authentication and filtering is to verify whether the token is valid, so our gateway service needs to be in the same redis library as the business system, first add redis dependency and configuration to the gateway

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
spring:
  redis:
    host: redis-server
    port: 6379
    password:
    database: 0

Code

  1. Define the filter AuthFilter

  2. Get the request object Get the token from the request header or parameter or cookie (supporting multiple ways to pass the token is more friendly to the client, for example, some web download requests will create a new page, and it is more troublesome to pass the token in the request header)

  3. No token, return 401

  4. If there is a token, check whether redis is valid

  5. If it is invalid, it will return 401. If it is valid, it will complete the verification and release.

  6. Reset token expiration time and add internal request header information to facilitate business system permission processing

@Component
public class AuthFilter implements GlobalFilter, Ordered {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private static final String TOKEN_HEADER_KEY = "auth_token";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1. Get the request object
        ServerHttpRequest request = exchange.getRequest();
        //2. Get token
        String token = getToken(request);
        ServerHttpResponse response = exchange.getResponse();
        if (StringUtils.isBlank(token)) {
            //3. The token is empty and returns 401
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        //4. Verify that the token is valid
        String userId = getUserIdByToken(token);
        if (StringUtils.isBlank(userId)) {
            //5. The token is invalid and returns 401
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        //token is valid, follow-up business processing
        //From writing the request header, it is convenient for the business system to obtain the user id from the request header for permission-related processing
        ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
        request = builder.header("user_id", userId).build();
        //Extend the cache expiration time - if the token cache user has been operating, it will always reset the expiration
        //This avoids sudden expiration during user operation, which affects business operations and experience. Only when the user operation interval is greater than the cache expiration time will it expire.
        resetTokenExpirationTime(token, userId);
        //complete verification
        return chain.filter(exchange);
    }


    @Override
    public int getOrder() {
        //The smaller the priority, the higher the priority
        return 0;
    }

    /**
     * get user id from redis
     * During the login operation, a token will be generated when the login is successful, and the key of redis is auth_token:token The value is the user id
     *
     * @param token
     * @return
     */
    private String getUserIdByToken(String token) {
        String redisKey = String.join(":", "auth_token", token);
        return redisTemplate.opsForValue().get(redisKey);
    }

    /**
     * Reset token expiration time
     *
     * @param token
     * @param userId
     */
    private void resetTokenExpirationTime(String token, String userId) {
        String redisKey = String.join(":", "auth_token", token);
        redisTemplate.opsForValue().set(redisKey, userId, 2, TimeUnit.HOURS);
    }


    /**
     * get token
     *
     * @param request
     * @return
     */
    private static String getToken(ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        //Get token from request header
        String token = headers.getFirst(TOKEN_HEADER_KEY);
        if (StringUtils.isBlank(token)) {
            //If there is no token in the request header, the token is obtained from the url
            token = request.getQueryParams().getFirst(TOKEN_HEADER_KEY);
        }
        if (StringUtils.isBlank(token)) {
            //If the request header and url do not have a token, they are obtained from cookies
            HttpCookie cookie = request.getCookies().getFirst(TOKEN_HEADER_KEY);
            if (cookie != null) {
                token = cookie.getValue();
            }
        }
        return token;
    }
}

Summarize

Gateway can implement routing function through configuration items, integrate Nacos and configure monitoring to implement dynamic routing, and implement GlobalFilter and Ordered interfaces to quickly implement a filter. The article also introduces the request authentication process after login in detail. See you in the comments section if you know where to go.

Thanks for reading, hope it helps you :)

Source: juejin.cn/post/7004756545741258765

Tags: Java Microservices

Posted by krystof78 on Sun, 02 Oct 2022 04:37:33 +1030