In my opinion, in some scenarios, a gateway is like a public method that abstracts functions needed in a project into a service. For example, we can do log collection, Token verification and so on in the service gateway. Of course, this is a narrow understanding, because the gateway is much more than that, but it does not prevent us from understanding it better. The following example demonstrates how to verify the Token at the gateway and extract the user information into the Header and pass it to the downstream business system.
1. The Token is generated
After the user logs in successfully, the token is generated, and all subsequent requests carry the token. The gateway verifies the token and adds the user information to the request Header so that the downstream system can easily obtain the user information.
For demonstration purposes, three projects are involved in this example
Public project: CJS-Commons-JWT
Authentication service: CJS-auth-service
Gateway service: js-gateway-example
1.1. Token generation and verification tool class
Since token generation is in the authentication service and token verification is in the gateway service, I have written this part in the public project CJS-Commons-JWT
pom.xml
1 <? xml version="1.0" encoding="UTF-8"? > 2 3 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 <modelVersion>4.0.0</modelVersion> 6 7 <groupId> com.js. Example </groupId> 8 <artifactId> js-commons-jwt</artifactId> 9 <version>1.0-SNAPSHOT</version> 10 11 <properties> 12 < project. Build. SourceEncoding > utf-8 < / project. Build. SourceEncoding > 13 < maven.com piler. Source > 1.8 < / maven.com piler source > 14 <maven.compiler.target>1.8</maven.compiler.target> 15 </properties> 16 17 <dependencies> 18 <dependency> 19 <groupId>com.auth0</groupId> 20 <artifactId> Java -jwt</artifactId> 21 <version>3.10.0</version> 22 </dependency> 23 <dependency> 24 <groupId>org.apache.commons</groupId> 25 <artifactId>commons-lang3</artifactId> 26 <version>3.9</version> 27 </dependency> 28 <dependency> 29 <groupId>com.alibaba</groupId> 30 <artifactId>fastjson</artifactId> 31 <version>1.2.66</version> 32 </dependency> 33 </dependencies> 34 35 </project>Copy the code
JWTUtil.java
1 package com.cjs.example.utils;
2
3 import com.auth0.jwt.JWT;
4 import com.auth0.jwt.JWTVerifier;
5 import com.auth0.jwt.algorithms.Algorithm;
6 import com.auth0.jwt.exceptions.JWTDecodeException;
7 import com.auth0.jwt.exceptions.SignatureVerificationException;
8 import com.auth0.jwt.exceptions.TokenExpiredException;
9 import com.auth0.jwt.interfaces.DecodedJWT;
10 import com.cjs.example.enums.ResponseCodeEnum;
11 import com.cjs.example.exception.TokenAuthenticationException;
12
13 import java.util.Date;
14
15 /**
16 * @author ChengJianSheng
17 * @date 2020-03-08
18 */
19 public class JWTUtil {
20
21 public static final long TOKEN_EXPIRE_TIME = 7200 * 1000;
22 private static final String ISSUER = "cheng"; 23 24 /** 25 * Generate Token 26 * @param username user ID 27 * @param secretKey 28 * @return
29 */
30 public static String generateToken(String username, String secretKey) {
31 Algorithm algorithm = Algorithm.HMAC256(secretKey);
32 Date now = new Date();
33 Date expireTime = new Date(now.getTime() + TOKEN_EXPIRE_TIME);
34
35 String token = JWT.create()
36 .withIssuer(ISSUER)
37 .withIssuedAt(now)
38 .withExpiresAt(expireTime)
39 .withClaim("username", username) 40 .sign(algorithm); 41 and 42returntoken; 43} 44 45 /** 46 * Verify Token 47 * @param Token 48 * @param secretKey 49 * @return50 */ 51 public static void verifyToken(String token, String secretKey) { 52 try { 53 Algorithm algorithm = Algorithm.HMAC256(secretKey); 54 JWTVerifier jwtVerifier = JWT.require(algorithm).withIssuer(ISSUER).build(); 55 jwtVerifier.verify(token); 56 } catch (JWTDecodeException jwtDecodeException) { 57 throw new TokenAuthenticationException(ResponseCodeEnum.TOKEN_INVALID.getCode(), ResponseCodeEnum.TOKEN_INVALID.getMessage()); 58 } catch (SignatureVerificationException signatureVerificationException) { 59 throw new TokenAuthenticationException(ResponseCodeEnum.TOKEN_SIGNATURE_INVALID.getCode(), ResponseCodeEnum.TOKEN_SIGNATURE_INVALID.getMessage()); 60 } catch (TokenExpiredException tokenExpiredException) { 61 throw new TokenAuthenticationException(ResponseCodeEnum.TOKEN_EXPIRED.getCode(), ResponseCodeEnum.TOKEN_INVALID.getMessage()); 62 } catch (Exception ex) { 63 throw new TokenAuthenticationException(ResponseCodeEnum.UNKNOWN_ERROR.getCode(), ResponseCodeEnum.UNKNOWN_ERROR.getMessage()); 64} 65} 66 67 /** 68 * Extract user information from Token 69 * @param Token 70 * @return
71 */
72 public static String getUserInfo(String token) {
73 DecodedJWT decodedJWT = JWT.decode(token);
74 String username = decodedJWT.getClaim("username").asString();
75 returnusername; 76} 77 78}Copy the code
ResponseCodeEnum.java
1 package com.cjs.example.enums;
2
3 /**
4 * @author ChengJianSheng
5 * @date 2020-03-08
6 */
7 public enum ResponseCodeEnum {
8
9 SUCCESS(0, "Success"),
10 FAIL(-1, "Failure"),
11 LOGIN_ERROR(1000, "Wrong username or password"),
12 UNKNOWN_ERROR(2000, "Unknown error"),
13 PARAMETER_ILLEGAL(2001, "Invalid parameter"),
14 TOKEN_INVALID(2002, "Invalid Token"),
15 TOKEN_SIGNATURE_INVALID(2003, "Invalid signature"),
16 TOKEN_EXPIRED(2004, "Token has expired"),
17 TOKEN_MISSION(2005, "Lack of token"),
18 REFRESH_TOKEN_INVALID(2006, "Refresh Token invalid");
19
20
21 private int code;
22
23 private String message;
24
25 ResponseCodeEnum(int code, String message) {
26 this.code = code;
27 this.message = message;
28 }
29
30 public int getCode() {31return code;
32 }
33
34 public String getMessage() {35returnmessage; 36} 37 38}Copy the code
ResponseResult.java
1 package com.cjs.example;
2
3 import com.cjs.example.enums.ResponseCodeEnum;
4
5 /**
6 * @author ChengJianSheng
7 * @date 2020-03-08
8 */
9 public class ResponseResult<T> {
10
11 private int code = 0;
12
13 private String msg;
14
15 private T data;
16
17 public ResponseResult(int code, String msg) {
18 this.code = code;
19 this.msg = msg;
20 }
21
22 public ResponseResult(int code, String msg, T data) {
23 this.code = code;
24 this.msg = msg;
25 this.data = data;
26 }
27
28 public static ResponseResult success() {29return new ResponseResult(ResponseCodeEnum.SUCCESS.getCode(), ResponseCodeEnum.SUCCESS.getMessage());
30 }
31
32 public static <T> ResponseResult<T> success(T data) {
33 return new ResponseResult(ResponseCodeEnum.SUCCESS.getCode(), ResponseCodeEnum.SUCCESS.getMessage(), data);
34 }
35
36 public static ResponseResult error(int code, String msg) {
37 return new ResponseResult(code, msg);
38 }
39
40 public static <T> ResponseResult<T> error(int code, String msg, T data) {
41 return new ResponseResult(code, msg, data);
42 }
43
44 public boolean isSuccess() {45return code == 0;
46 }
47
48 public int getCode() {49return code;
50 }
51
52 public void setCode(int code) {
53 this.code = code;
54 }
55
56 public String getMsg() {57return msg;
58 }
59
60 public void setMsg(String msg) {
61 this.msg = msg;
62 }
63
64 public T getData() {65return data;
66 }
67
68 public void setData(T data) { 69 this.data = data; 71 70}}Copy the code
1.2. Token is generated
This part is in the CJS-auth-service
pom.xml
1 <? xml version="1.0" encoding="UTF-8"? > 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">4 <modelVersion>4.0.0</modelVersion> 5 <parent> 6 <groupId> org.springFramework. boot</groupId> 7 <artifactId>spring-boot-starter-parent</artifactId> 8 <version>2.2.5.RELEASE</version> 9 <relativePath/> <! -- lookup parent from repository --> 10 </parent> 11 <groupId>com.cjs.example</groupId> 12 <artifactId> js-auth-service</artifactId> 13 <version>0.0.1-SNAPSHOT</version> 14 <name> Js-auth-service </name> 15 16 <properties> 17 <java.version>1.8</java.version> 18 </properties> 19 20 <dependencies> 21 <dependency> 22 <groupId>org.springframework.boot</groupId> 23 <artifactId>spring-boot-starter-data-redis</artifactId> 24 </dependency> 25 <dependency> 26 <groupId>org.springframework.boot</groupId> 27 <artifactId>spring-boot-starter-web</artifactId> 28 </dependency> 29 30 <dependency> 31 <groupId>org.apache.commons</groupId> 32 <artifactId>commons-lang3</artifactId> 33 <version>3.9</version> 34 </dependency> 35 <dependency> 36 <groupId> Commons -codec</groupId> 37 <artifactId> Commons -codec</artifactId> 38 <version>1.14</version> 39 </dependency> 40 <dependency> 41 <groupId>org.apache.commons</groupId> 42 <artifactId> Commons -pool2</artifactId> 43 <version>2.8.0</version> 44 </dependency> 45 46 <dependency> 47 <groupId>com.cjs.example</groupId> 48 <artifactId>cjs-commons-jwt</artifactId> 49 <version>1.0-SNAPSHOT</version> 50 </dependency> 51 52 <dependency> 53 <groupId>org.projectlombok</groupId> 54 <artifactId>lombok</artifactId> 55 <optional>true</optional>
56 </dependency>
57 </dependencies>
58
59 <build>
60 <plugins>
61 <plugin>
62 <groupId>org.springframework.boot</groupId>
63 <artifactId>spring-boot-maven-plugin</artifactId>
64 </plugin>
65 </plugins>
66 </build>
67
68 </project>Copy the code
LoginController.java
1 package com.cjs.example.controller; 2 3 import com.cjs.example.ResponseResult; 4 import com.cjs.example.domain.LoginRequest; 5 import com.cjs.example.domain.LoginResponse; 6 import com.cjs.example.domain.RefreshRequest; 7 import com.cjs.example.enums.ResponseCodeEnum; 8 import com.cjs.example.utils.JWTUtil; 9 import org.apache.commons.lang3.StringUtils; 10 import org.apache.tomcat.util.security.MD5Encoder; 11 import org.springframework.beans.factory.annotation.Autowired; 12 import org.springframework.beans.factory.annotation.Value; 13 import org.springframework.data.redis.core.HashOperations; 14 import org.springframework.data.redis.core.StringRedisTemplate; 15 import org.springframework.validation.BindingResult; 16 import org.springframework.validation.annotation.Validated; 17 import org.springframework.web.bind.annotation.*; 18 19 import java.util.UUID; 20 import java.util.concurrent.TimeUnit; 21 22 /** 23 * @author ChengJianSheng 24 * @date 2020-03-08 25 */ 26 @RestController 27 public class LoginController { 28 29 /** 30 * Apollo or Nacos 31 */ 32 @Value("${secretKey:123456}") 33 private String secretKey; 34 35 @Autowired 36 private StringRedisTemplate stringRedisTemplate; 37 38 /** 39 * Login 40 */ 41 @postmapping ("/login")
42 public ResponseResult login(@RequestBody @Validated LoginRequest request, BindingResult bindingResult) {
43 if (bindingResult.hasErrors()) {
44 returnResponseResult.error(ResponseCodeEnum.PARAMETER_ILLEGAL.getCode(), ResponseCodeEnum.PARAMETER_ILLEGAL.getMessage()); 45 } 46 47 String username = request.getUsername(); 48 String password = request.getPassword(); 49 // assume that the userId is 1001. 50 String userId ="1001";
51 if ("hello".equals(username) && "world".equals(password)) {53 String Token = jwtutil. generateToken(userId, secretKey); 56 String refreshToken = uuID.randomuuid ().toString().replace()"-".""); 59 HashOperations<String, String, String>hashOperations = stringRedisTemplate.opsForHash(); 60 / /hashOperations.put(refreshToken, "token", token); 61 / /hashOperations.put(refreshToken, "user", username); 62 // stringRedisTemplate.expire(refreshToken, JWTUtil.TOKEN_EXPIRE_TIME, TimeUnit.MILLISECONDS); 63 64 /** 65 * There is no need to save Redis 66 * if the user can exit the token if it is still usable within the validity period, because the token must be associated with the user each time. 68 */ 69 70 // String key = MD5Encoder. Encode (userid.getBytes ()); 71 72 String key = userId; 73hashOperations.put(key, "token", token);
74 hashOperations.put(key, "refreshToken", refreshToken); 75 stringRedisTemplate.expire(key, JWTUtil.TOKEN_EXPIRE_TIME, TimeUnit.MILLISECONDS); 76 77 LoginResponse loginResponse = new LoginResponse(); 78 loginResponse.setToken(token); 79 loginResponse.setRefreshToken(refreshToken); 80 loginResponse.setUsername(userId); 81, 82,return ResponseResult.success(loginResponse);
83 }
84
85 returnResponseResult.error(ResponseCodeEnum.LOGIN_ERROR.getCode(), ResponseCodeEnum.LOGIN_ERROR.getMessage()); 86} 87 88 /** 89 * exit 90 */ 91 @getMapping ("/logout")
92 public ResponseResult logout(@RequestParam("userId") String userId) {
93 HashOperations<String, String, String> hashOperations = stringRedisTemplate.opsForHash();
94 String key = userId;
95 hashOperations.delete(key);
96 returnResponseResult.success(); 97} 98 99 /** 100 * Refresh Token 101 */ 102 @PostMapping("/refreshToken")
103 public ResponseResult refreshToken(@RequestBody @Validated RefreshRequest request, BindingResult bindingResult) {
104 String userId = request.getUserId();
105 String refreshToken = request.getRefreshToken();
106 HashOperations<String, String, String> hashOperations = stringRedisTemplate.opsForHash();
107 String key = userId;
108 String originalRefreshToken = hashOperations.get(key, "refreshToken");
109 if(StringUtils.isBlank(originalRefreshToken) || ! originalRefreshToken.equals(refreshToken)) { 110returnResponseResult.error(ResponseCodeEnum.REFRESH_TOKEN_INVALID.getCode(), ResponseCodeEnum.REFRESH_TOKEN_INVALID.getMessage()); 114 String newToken = jwtutil. generateToken(userId, secretKey); 114 String newToken = jwtutil. generateToken(userId, secretKey); 115hashOperations.put(key, "token", newToken); 116 stringRedisTemplate.expire(userId, JWTUtil.TOKEN_EXPIRE_TIME, TimeUnit.MILLISECONDS); 117, 118,returnResponseResult.success(newToken); 120 119}}Copy the code
HelloController.java
1 package com.cjs.example.controller;
2
3 import org.springframework.web.bind.annotation.GetMapping;
4 import org.springframework.web.bind.annotation.RequestHeader;
5 import org.springframework.web.bind.annotation.RequestMapping;
6 import org.springframework.web.bind.annotation.RestController;
7
8 /**
9 * @author ChengJianSheng
10 * @date 2020-03-08
11 */
12 @RestController
13 @RequestMapping("/hello")
14 public class HelloController {
15
16 @GetMapping("/sayHello")
17 public String sayHello(String name) {
18 return "Hello, " + name;
19 }
20
21 @GetMapping("/sayHi")
22 public String sayHi(@RequestHeader("userId") String userId) {
23 returnuserId; 24} 25 26}Copy the code
application.yml
1 server: 2 port: 8081 3 servlet: 4 context-path: /auth-server 5 spring: 6 application: 7 name: Js-auth-service 8 redis: 9 host: 127.0.0.1 10 password: 123456 11 port: 6379 12 lettuce: 13 pool: 14 max-active: 10 15 max-idle: 5 16 min-idle: 5 17 max-wait: 5000Copy the code
2. Check the Token
Both GatewayFilter and GlobalFilter work, GlobalFilter is used here
pom.xml
1 <? xml version="1.0" encoding="UTF-8"? > 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">4 <modelVersion>4.0.0</modelVersion> 5 <parent> 6 <groupId> org.springFramework. boot</groupId> 7 <artifactId>spring-boot-starter-parent</artifactId> 8 <version>2.2.5.RELEASE</version> 9 <relativePath/> <! -- lookup parent from repository --> 10 </parent> 11 <groupId>com.cms.example</groupId> 12 <artifactId> js-gateway-example</artifactId> 13 <version>0.0.1-SNAPSHOT</version> 14 <name> Js-gateway-example </name> 15 16 <properties> 17 <java.version>1.8</java.version> 18 <spring-cloud.version> hoxton. SR1</spring-cloud.version> 19 </properties> 20 21 <dependencies> 22 <dependency> 23 <groupId>org.springframework.boot</groupId> 24 <artifactId>spring-boot-starter-data-redis-reactive</artifactId> 25 </dependency> 26 <dependency> 27 <groupId>org.springframework.cloud</groupId> 28 <artifactId>spring-cloud-starter-gateway</artifactId> 29 </dependency> 30 <dependency> 31 <groupId>com.auth0</groupId> 32 <artifactId> Java -jwt</artifactId> 33 <version>3.10.0</version> 34 </dependency> 35 <dependency> 36 <groupId>com.cjs.example</groupId> 37 <artifactId>cjs-commons-jwt</artifactId> 38 <version>1.0-SNAPSHOT</version> 39 </dependency> 40 41 42 <dependency> 43 <groupId>org.projectlombok</groupId> 44 <artifactId>lombok</artifactId> 45 <optional>true</optional>
46 </dependency>
47 </dependencies>
48
49 <dependencyManagement>
50 <dependencies>
51 <dependency>
52 <groupId>org.springframework.cloud</groupId>
53 <artifactId>spring-cloud-dependencies</artifactId>
54 <version>${spring-cloud.version}</version>
55 <type>pom</type>
56 <scope>import</scope>
57 </dependency>
58 </dependencies>
59 </dependencyManagement>
60
61 <build>
62 <plugins>
63 <plugin>
64 <groupId>org.springframework.boot</groupId>
65 <artifactId>spring-boot-maven-plugin</artifactId>
66 </plugin>
67 </plugins>
68 </build>
69
70 </project>Copy the code
AuthorizeFilter.java
1 package com.cms.example.filter;
2
3 import com.alibaba.fastjson.JSON;
4 import com.cjs.example.ResponseResult;
5 import com.cjs.example.enums.ResponseCodeEnum;
6 import com.cjs.example.exception.TokenAuthenticationException;
7 import com.cjs.example.utils.JWTUtil;
8 import lombok.extern.slf4j.Slf4j;
9 import org.apache.commons.lang3.StringUtils;
10 import org.springframework.beans.factory.annotation.Autowired;
11 import org.springframework.beans.factory.annotation.Value;
12 import org.springframework.cloud.gateway.filter.GatewayFilterChain;
13 import org.springframework.cloud.gateway.filter.GlobalFilter;
14 import org.springframework.core.Ordered;
15 import org.springframework.core.io.buffer.DataBuffer;
16 import org.springframework.data.redis.core.StringRedisTemplate;
17 import org.springframework.http.HttpStatus;
18 import org.springframework.http.server.reactive.ServerHttpRequest;
19 import org.springframework.http.server.reactive.ServerHttpResponse;
20 import org.springframework.stereotype.Component;
21 import org.springframework.web.server.ServerWebExchange;
22 import reactor.core.publisher.Flux;
23 import reactor.core.publisher.Mono;
24
25 /**
26 * @author ChengJianSheng
27 * @date 2020-03-08
28 */
29 @Slf4j
30 @Component
31 public class AuthorizeFilter implements GlobalFilter, Ordered {
32
33 @Value("${secretKey:123456}") 34 private String secretKey; 35 36 // @Autowired 37 // private StringRedisTemplate stringRedisTemplate; 38 39 @Override 40 public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { 41 ServerHttpRequest serverHttpRequest = exchange.getRequest(); 42 ServerHttpResponse serverHttpResponse = exchange.getResponse(); 43 String uri = serverHttpRequest.getURI().getPath(); 44 45 // Checking the whitelist (Configuration) 46if (uri.indexOf("/auth-server/login") >= 0) {47return chain.filter(exchange);
48 }
49
50 String token = serverHttpRequest.getHeaders().getFirst("token");
51 if (StringUtils.isBlank(token)) {
52 serverHttpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
53 returngetVoidMono(serverHttpResponse, ResponseCodeEnum.TOKEN_MISSION); 57 58 try {59 jwtutil. verifyToken(Token, secretKey); 60 } catch (TokenAuthenticationException ex) { 61return getVoidMono(serverHttpResponse, ResponseCodeEnum.TOKEN_INVALID);
62 } catch (Exception ex) {
63 return getVoidMono(serverHttpResponse, ResponseCodeEnum.UNKNOWN_ERROR);
64 }
65
66 String userId = JWTUtil.getUserInfo(token);
67
68 ServerHttpRequest mutableReq = serverHttpRequest.mutate().header("userId", userId).build(); 69 ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build(); 70, 71,return chain.filter(mutableExchange);
72 }
73
74 private Mono<Void> getVoidMono(ServerHttpResponse serverHttpResponse, ResponseCodeEnum responseCodeEnum) {
75 serverHttpResponse.getHeaders().add("Content-Type"."application/json; charset=UTF-8");
76 ResponseResult responseResult = ResponseResult.error(responseCodeEnum.getCode(), responseCodeEnum.getMessage());
77 DataBuffer dataBuffer = serverHttpResponse.bufferFactory().wrap(JSON.toJSONString(responseResult).getBytes());
78 return serverHttpResponse.writeWith(Flux.just(dataBuffer));
79 }
80
81 @Override
82 public int getOrder() {83return- 100; 85 84}}Copy the code
application.yml
1 spring:
2 cloud:
3 gateway:
4 routes:
5 - id: path_route
6 uri: http://localhost:8081/auth-server/
7 filters:
8 - MyLog=true
9 predicates:
10 - Path=/auth-server/** Copy the code
I also have a custom log collection filter here
1 package com.cms.example.filter;
2
3 import org.apache.commons.logging.Log;
4 import org.apache.commons.logging.LogFactory;
5 import org.springframework.cloud.gateway.filter.GatewayFilter;
6 import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
7 import org.springframework.http.server.reactive.ServerHttpRequest;
8 import org.springframework.stereotype.Component;
9 import reactor.core.publisher.Mono;
10
11 import java.util.Arrays;
12 import java.util.List;
13
14 /**
15 * @author ChengJianSheng
16 * @date 2020-03-08
17 */
18 @Component
19 public class MyLogGatewayFilterFactory extends AbstractGatewayFilterFactory<MyLogGatewayFilterFactory.Config> {
20
21 private static final Log log = LogFactory.getLog(MyLogGatewayFilterFactory.class);
22 private static final String MY_LOG_START_TIME = MyLogGatewayFilterFactory.class.getName() + "." + "startTime";
23
24 public MyLogGatewayFilterFactory() {
25 super(Config.class);
26 }
27
28 @Override
29 public List<String> shortcutFieldOrder() {30return Arrays.asList("enabled");
31 }
32
33 @Override
34 public GatewayFilter apply(Config config) {
35 return (exchange, chain) -> {
36 if(! config.isEnabled()) { 37return chain.filter(exchange);
38 }
39 exchange.getAttributes().put(MY_LOG_START_TIME, System.currentTimeMillis());
40 return chain.filter(exchange).then(Mono.fromRunnable(() -> {
41 Long startTime = exchange.getAttribute(MY_LOG_START_TIME);
42 if(null ! = startTime) { 43 ServerHttpRequest serverHttpRequest = exchange.getRequest(); 44 StringBuilder sb = new StringBuilder(); 45 sb.append(serverHttpRequest.getURI().getRawPath()); 46 sb.append(":");
47 sb.append(serverHttpRequest.getQueryParams());
48 sb.append(":");
49 sb.append(System.currentTimeMillis() - startTime);
50 sb.append("ms"); 51 log.info(sb.toString()); 52}}) 53); 54}; 56 56 public static class Config {58 /** 59 */ 61 private Boolean enabled; 62 63 publicConfig() {
64 }
65
66 public boolean isEnabled() {67return enabled;
68 }
69
70 public void setEnabled(boolean enabled) { 71 this.enabled = enabled; 73 72}}Copy the code
Postman access to see the effect
http://localhost:8080/auth-server/hello/sayHi
http://localhost:8080/auth-server/hello/sayHello?name=aaa
3. Spring Cloud Gateway
1 @SpringBootApplication
2 public class DemogatewayApplication {
3 @Bean
4 public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
5 return builder.routes()
6 .route("path_route", r -> r.path("/get")
7 .uri("http://httpbin.org"))
8 .route("host_route", r -> r.host("*.myhost.org")
9 .uri("http://httpbin.org"))
10 .route("rewrite_route", r -> r.host("*.rewrite.org")
11 .filters(f -> f.rewritePath("/foo/(?
.*)"
."/${segment}"))
12 .uri("http://httpbin.org"))
13 .route("hystrix_route", r -> r.host("*.hystrix.org")
14 .filters(f -> f.hystrix(c -> c.setName("slowcmd")))
15 .uri("http://httpbin.org"))
16 .route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
17 .filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
18 .uri("http://httpbin.org"))
19 .route("limit_route", r -> r
20 .host("*.limited.org").and().path("/anything/**")
21 .filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))
22 .uri("http://httpbin.org")) 23 .build(); 25 24}}Copy the code
3.1. GatewayFilter Factories
Routing filters allow you to modify the incoming HTTP request or the outgoing HTTP response in some way. Route filters apply to specific routes. The Spring Cloud Gateway includes a number of built-in GatewayFilter factories.
3.1.1. AddRequestHeader GatewayFilter Factory
AddRequestHeader GatewayFilter Uses the name and value parameters.
For example: In the following example, x-request-RED: blue is added to the downstream Request header for all matching requests
1 spring:
2 cloud:
3 gateway:
4 routes:
5 - id: add_request_header_route
6 uri: https://example.org
7 filters:
8 - AddRequestHeader=X-Request-red, blue
Copy the code
As I said, AddRequestHeader takes name and value as arguments. Variables in urIs can be used in values, for example:
1 spring:
2 cloud:
3 gateway:
4 routes:
5 - id: add_request_header_route
6 uri: https://example.org
7 predicates:
8 - Path=/red/{segment}
9 filters:
10 - AddRequestHeader=X-Request-Red, Blue-{segment}Copy the code
3.1.2. AddRequestParameter GatewayFilter Factory
AddRequestParameter GatewayFilter also uses name and value parameters
For example: In the following example, red=blue will be added to the query string of the downstream request for all matching requests
1 spring:
2 cloud:
3 gateway:
4 routes:
5 - id: add_request_parameter_route
6 uri: https://example.org
7 filters:
8 - AddRequestParameter=red, blueCopy the code
Similarly, AddRequestParameter supports referencing a variable in a URI in a value, for example:
1 spring:
2 cloud:
3 gateway:
4 routes:
5 - id: add_request_parameter_route
6 uri: https://example.org
7 predicates:
8 - Host: {segment}.myhost.org
9 filters:
10 - AddRequestParameter=foo, bar-{segment}Copy the code
3.1.3. AddResponseHeader GatewayFilter Factory
AddResponseHeader GatewayFilter still uses the name and value parameters. Without further elaboration, as follows:
1 spring:
2 cloud:
3 gateway:
4 routes:
5 - id: add_response_header_route
6 uri: https://example.org
7 filters:
8 - AddResponseHeader=X-Response-Red, BlueCopy the code
3.1.4. DedupeResponseHeader GatewayFilter Factory
DedupeResponseHeader GatewayFilter takes a name parameter and an optional strategy parameter. Name can contain a whitespace separated list of header names. For example, in the following example, this will remove duplicate values in the access-Control-allow-Credentials and Access-Control-Allow-Origin response headers if both the gateway CORS logic and downstream logic add them.
1 spring:
2 cloud:
3 gateway:
4 routes:
5 - id: dedupe_response_header_route
6 uri: https://example.org
7 filters:
8 - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-OriginCopy the code
3.1.5. PrefixPath GatewayFilter Factory
PrefixPath GatewayFilter has only one prefix parameter. In the following example, for all matching requests, the request URL will be prefixed with /mypath, so that the request /hello will be forwarded to /mypath/hello
1 spring:
2 cloud:
3 gateway:
4 routes:
5 - id: prefixpath_route
6 uri: https://example.org
7 filters:
8 - PrefixPath=/mypathCopy the code
3.1.6. RequestRateLimiter GatewayFilter Factory
The RequestRateLimiter GatewayFilter uses a RateLimiter implementation to determine whether the current request is allowed to be processed. If this is not allowed, an HTTP 429 status is returned by default, indicating too many requests.
This filter takes an optional keyResolver parameter. KeyResolver is a bean that implements the keyResolver interface. In configuration, it is referenced through SpEL expressions. For example, #{@myKeyResolver} is an SpEL expression that is a reference to a bean named myKeyResolver. The default implementation of KeyResolver is PrincipalNameKeyResolver.
By default, if the KeyResolver does not find a key, the request will be rejected. You can adjust this behavior, By setting the spring. Cloud. Gateway. Filter. The request – rate – limiter. Deny – empty – the key (true or false) and Spring. Cloud. Gateway. Filter. The request – rate – limiter. Empty – key – the status – code properties.
Redis implements a RequestRateLimiter based on the Token Bucket Algorithm
The redis-rate-limiter.replenishRate attribute specifies how many requests per second are allowed by a user without any discarded requests. This is the rate at which the token bucket is filled.
The redis-rate-limiter.burstCapacity property specifies the maximum number of requests executed by the user in one second. This is how many tokens the token bucket can hold. Setting this value to zero blocks all requests.
The redis-rate-limiter. RequestedTokens property specifies how many tokens a request will cost. This is the number of tokens retrieved from the bucket per request, which defaults to 1.
A stable rate can be achieved by setting the replenishRate and burstCapacity to the same value. Temporary bursts are allowed by setting the burstCapacity to a higher replenishRate. In this case, the rate limiter needs to be retained for a period of time (according to the replenishRate) between bursts, since two consecutive bursts will result in request discard (HTTP 429- Too many requests).
The replenishRate is set to the required number of requests, requestTokens is set to the time span in seconds and burstCapacity is set to the product of the replenishRate and requestedToken. The rate limit of 1 request can be reached. For example, setting the replenishRate = 1, requestedTokens = 60, and burstCapacity = 60 will result in a limitation of 1 request per minute.
1 spring:
2 cloud:
3 gateway:
4 routes:
5 - id: requestratelimiter_route
6 uri: https://example.org
7 filters:
8 - name: RequestRateLimiter
9 args:
10 redis-rate-limiter.replenishRate: 10
11 redis-rate-limiter.burstCapacity: 20
12 redis-rate-limiter.requestedTokens: 1 Copy the code
KeyResolver
1 @Bean
2 KeyResolver userKeyResolver() {3return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user")); 4}Copy the code
The above example defines 10 requests per user per second and a token bucket with a capacity of 20, so there will only be 10 tokens available in the next second. The KeyResolver implementation simply takes the user in the request parameters, which is not recommended in a production environment.
In plain English, a KeyResolver determines which requests belong to the same user. For example, requests with the same userId in the header are considered requests from the same user.
Of course, you can also implement a RateLimiter of your own and reference it with the SpEL expression #{@myratelimiter} when configured. Such as:
1 spring:
2 cloud:
3 gateway:
4 routes:
5 - id: requestratelimiter_route
6 uri: https://example.org
7 filters:
8 - name: RequestRateLimiter
9 args:
10 rate-limiter: "#{@myRateLimiter}"
11 key-resolver: "#{@userKeyResolver}"Copy the code
Add :(Token Bucket) Token Bucket
En.wikipedia.org/wiki/Token_…
Token buckets are algorithms used in packet-switched computer networks and telecommunications networks. It can be used to check whether data transfers in the form of packets comply with defined bandwidth and burst limits (a measure of traffic inhomogeneity or variation).
The token bucket algorithm is like a fixed capacity bucket, to which tokens are usually added at a fixed rate. A token usually represents a byte. When a packet is checked to see if it meets the defined limits, the token bucket is checked to see if it contains enough tokens at the time. If there is a sufficient number of tokens, assuming that the tokens are in bytes, the number of tokens equivalent to the number of packet bytes will be removed, and the packet can be transmitted by continuing. If there are not enough tokens in the token bucket, the packet does not meet the requirements and the token number in the token bucket does not change. Unqualified packets can be processed in a number of ways:
- They may be discarded
- When enough tokens have accumulated in the bucket, they can be queued for subsequent transmission
- They can be transmitted, but are marked as nonconforming and may then be discarded if the network load is too high
PS: What this means is, imagine having a bucket and adding tokens to the bucket at a constant rate. Assume that a token is equivalent to one byte, and when a packet arrives, assume that the size of the packet is N bytes, if there are enough tokens in the bucket, i.e. the number of tokens in the bucket is greater than N, then the data can pass and n tokens are removed from the bucket. If there are not enough tokens in the bucket, depending on the situation, the packet may be discarded directly, it may wait until there are enough tokens, or it may continue transmission but be marked as unqualified. Still not popular, so, if the token bucket to imagine a bucket, token into water droplets, so this process becomes at a constant rate to drop in the bucket, when someone want to make a bowl of water, if the bowl is small, only hold 30 drop, and drop in the bucket number more than 30, then this person can make a bowl of water and then went away, Accordingly, there were 30 drops less water in the bucket after the man had finished. After a while, another man comes to fetch water. He has a bigger bowl that can hold 100 drops at a time, but there is not enough water in the bucket. At this point, he may go away, or wait until the bucket has accumulated 100 drops. Hahaha, that’s what it is, I don’t know if you have seen the water wheel……)
The token bucket algorithm can be understood simply as follows:
- One token is added to the token bucket every 1/r second
- A token bucket can hold up to B tokens. When a token arrives and the token bucket is full, it is discarded.
- When a packet of n bytes arrives:
- If there are at least N tokens in the token bucket, n tokens are removed from the token bucket and packets are sent to the network.
- If fewer than n tokens are available, no tokens are removed from the token bucket and the packet is considered unqualified.
3.1.7. RedirectTo GatewayFilter Factory
The RedirectTo GatewayFilter has two parameters: status and URL. Status should be 300 series. Without explanation, look at examples:
1 spring:
2 cloud:
3 gateway:
4 routes:
5 - id: prefixpath_route
6 uri: https://example.org
7 filters:
8 - RedirectTo=302, https://acme.orgCopy the code
3.1.8. RemoveRequestHeader GatewayFilter Factory
1 spring:
2 cloud:
3 gateway:
4 routes:
5 - id: removerequestheader_route
6 uri: https://example.org
7 filters:
8 - RemoveRequestHeader=X-Request-Foo
Copy the code
3.1.9. The RewritePath GatewayFilter Factory
1 spring:
2 cloud:
3 gateway:
4 routes:
5 - id: rewritepath_route
6 uri: https://example.org
7 predicates:
8 - Path=/foo/**
9 filters:
10 - RewritePath=/red(?<segment>/?.*), $\{segment}Copy the code
3.1.10. Default Filters
In order to add a filter, routing, and apply it to all. You can use the spring cloud. Gateway. The default – filters, the attribute value is a list of filters
1 spring:
2 cloud:
3 gateway:
4 default-filters:
5 - AddResponseHeader=X-Response-Default-Red, Default-Blue
6 - PrefixPath=/httpbinCopy the code
3.2. Global Filters
GlobalFilter applies to all routes
3.2.1. Combination order of GlobalFilter and GatewayFilter
When a request requests and matches a route, the filtering Web handler adds all instances of GlobalFilter and all route-specific instances of GatewayFilter to the filter chain. The combination of the filter chain by the org. Springframework. Core. Ordered interface, can be realized through getOrder set () method.
Because the Spring Cloud Gateway distinguishes between the “Pre” and “POST” phases of filter logic execution, the filter with the highest priority is the first in the “Pre” phase and the last in the “POST” phase.
1 @Bean
2 public GlobalFilter customFilter() {3return new CustomGlobalFilter();
4 }
5
6 public class CustomGlobalFilter implements GlobalFilter, Ordered {
7
8 @Override
9 public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
10 log.info("custom global filter");
11 return chain.filter(exchange);
12 }
13
14 @Override
15 public int getOrder() {16return- 1; 18 17}}Copy the code
Add :(Token Bucket) Token Bucket
En.wikipedia.org/wiki/Token_…
Token buckets are algorithms used in packet-switched computer networks and telecommunications networks. It can be used to check whether data transfers in the form of packets comply with defined bandwidth and burst limits (a measure of traffic inhomogeneity or variation).
The token bucket algorithm is like a fixed capacity bucket, to which tokens are usually added at a fixed rate. A token usually represents a byte. When a packet is checked to see if it meets the defined limits, the token bucket is checked to see if it contains enough tokens at the time. If there is a sufficient number of tokens, assuming that the tokens are in bytes, the number of tokens equivalent to the number of packet bytes will be removed, and the packet can be transmitted by continuing. If there are not enough tokens in the token bucket, the packet does not meet the requirements and the token number in the token bucket does not change. Unqualified packets can be processed in a number of ways:
- They may be discarded
- When enough tokens have accumulated in the bucket, they can be queued for subsequent transmission
- They can be transmitted, but are marked as nonconforming and may then be discarded if the network load is too high
PS: What this means is, imagine having a bucket and adding tokens to the bucket at a constant rate. Assume that a token is equivalent to one byte, and when a packet arrives, assume that the size of the packet is N bytes, if there are enough tokens in the bucket, i.e. the number of tokens in the bucket is greater than N, then the data can pass and n tokens are removed from the bucket. If there are not enough tokens in the bucket, depending on the situation, the packet may be discarded directly, it may wait until there are enough tokens, or it may continue transmission but be marked as unqualified. Still not popular, so, if the token bucket to imagine a bucket, token into water droplets, so this process becomes at a constant rate to drop in the bucket, when someone want to make a bowl of water, if the bowl is small, only hold 30 drop, and drop in the bucket number more than 30, then this person can make a bowl of water and then went away, Accordingly, there were 30 drops less water in the bucket after the man had finished. After a while, another man comes to fetch water. He has a bigger bowl that can hold 100 drops at a time, but there is not enough water in the bucket. At this point, he may go away, or wait until the bucket has accumulated 100 drops. Hahaha, that’s what it is, I don’t know if you have seen the water wheel……)
The token bucket algorithm can be understood simply as follows:
- One token is added to the token bucket every 1/r second
- A token bucket can hold up to B tokens. When a token arrives and the token bucket is full, it is discarded.
- When a packet of n bytes arrives:
- If there are at least N tokens in the token bucket, n tokens are removed from the token bucket and packets are sent to the network.
- If fewer than n tokens are available, no tokens are removed from the token bucket and the packet is considered unqualified.
The above content is some of my own feelings, share out welcome correction, by the way for a wave of attention, have a problem or need to learn information partners can click on Java learning sharing group to chat together oh
The author:
Big loser
Reference:
www.cnblogs.com/cjsblog/p/1…