</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
-
Define the filter AuthFilter
-
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)
-
No token, return 401
-
If there is a token, check whether redis is valid
-
If it is invalid, it will return 401. If it is valid, it will complete the verification and release.
-
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