精华内容
下载资源
问答
  • 比如,我们可以在业务网关上做日志收集、Token校验等等,当然这么理解很狭隘,因为网关的能力远不止如此,但是不妨碍我们更好地理解它。下面的例子演示了,如何在网关校验Token,并提取用户信息放到Header中传给下游...

    在我看来,在某些场景下,网关就像是一个公共方法,把项目中的都要用到的一些功能提出来,抽象成一个服务。比如,我们可以在业务网关上做日志收集、Token校验等等,当然这么理解很狭隘,因为网关的能力远不止如此,但是不妨碍我们更好地理解它。下面的例子演示了,如何在网关校验Token,并提取用户信息放到Header中传给下游业务系统。

    1. 生成Token

    用户登录成功以后,生成token,此后的所有请求都带着token。网关负责校验token,并将用户信息放入请求Header,以便下游系统可以方便的获取用户信息。

    c8702a64f93f10df3d6a68bfea6b0393.png
    0e150583d61c9b5aa5e61388a4560d8a.png

    为了方便演示,本例中涉及三个工程

    公共项目:cjs-commons-jwt

    认证服务:cjs-auth-service

    网关服务:cjs-gateway-example

    1.1. Token生成与校验工具类

    因为生成token在认证服务中,token校验在网关服务中,因此,我把这一部分写在了公共项目cjs-commons-jwt中

    pom.xml

     1 <?xml version="1.0" encoding="UTF-8"?> 2  3  5     4.0.0 6  7     com.cjs.example 8     cjs-commons-jwt 9     1.0-SNAPSHOT10 11     12         UTF-813         1.814         1.815     16 17     18         19             com.auth020             java-jwt21             3.10.022         23         24             org.apache.commons25             commons-lang326             3.927         28         29             com.alibaba30             fastjson31             1.2.6632         33     34 35 

    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 ChengJianSheng17  * @date 2020-03-0818  */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      * 生成Token26      * @param username 用户标识(不一定是用户名,有可能是用户ID或者手机号什么的)27      * @param secretKey28      * @return29      */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 42         return token;43     }44 45     /**46      * 校验Token47      * @param token48      * @param secretKey49      * @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      * 从Token中提取用户信息69      * @param token70      * @return71      */72     public static String getUserInfo(String token) {73         DecodedJWT decodedJWT = JWT.decode(token);74         String username = decodedJWT.getClaim("username").asString();75         return username;76     }77 78 }

    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, "成功"),10     FAIL(-1, "失败"),11     LOGIN_ERROR(1000, "用户名或密码错误"),12     UNKNOWN_ERROR(2000, "未知错误"),13     PARAMETER_ILLEGAL(2001, "参数不合法"),14     TOKEN_INVALID(2002, "无效的Token"),15     TOKEN_SIGNATURE_INVALID(2003, "无效的签名"),16     TOKEN_EXPIRED(2004, "token已过期"),17     TOKEN_MISSION(2005, "token缺失"),18     REFRESH_TOKEN_INVALID(2006, "刷新Token无效");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() {31         return code;32     }33 34     public String getMessage() {35         return message;36     }37 38 }

    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 {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() {29         return new ResponseResult(ResponseCodeEnum.SUCCESS.getCode(), ResponseCodeEnum.SUCCESS.getMessage());30     }31 32     public static  ResponseResult 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  ResponseResult error(int code, String msg, T data) {41         return new ResponseResult(code, msg, data);42     }43 44     public boolean isSuccess() {45         return code == 0;46     }47 48     public int getCode() {49         return code;50     }51 52     public void setCode(int code) {53         this.code = code;54     }55 56     public String getMsg() {57         return msg;58     }59 60     public void setMsg(String msg) {61         this.msg = msg;62     }63 64     public T getData() {65         return data;66     }67 68     public void setData(T data) {69         this.data = data;70     }71 }

    1.2. 生成token

    这一部分在cjs-auth-service中

    pom.xml

     1 <?xml version="1.0" encoding="UTF-8"?> 2  4     4.0.0 5      6         org.springframework.boot 7         spring-boot-starter-parent 8         2.2.5.RELEASE 9         10     11     com.cjs.example12     cjs-auth-service13     0.0.1-SNAPSHOT14     cjs-auth-service15 16     17         1.818     19 20     21         22             org.springframework.boot23             spring-boot-starter-data-redis24         25         26             org.springframework.boot27             spring-boot-starter-web28         29 30         31             org.apache.commons32             commons-lang333             3.934         35         36             commons-codec37             commons-codec38             1.1439         40         41             org.apache.commons42             commons-pool243             2.8.044         45 46         47             com.cjs.example48             cjs-commons-jwt49             1.0-SNAPSHOT50         51 52         53             org.projectlombok54             lombok55             true56         57     58 59     60         61             62                 org.springframework.boot63                 spring-boot-maven-plugin64             65         66     67 68 

    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 或 Nacos 31      */ 32     @Value("${secretKey:123456}") 33     private String secretKey; 34  35     @Autowired 36     private StringRedisTemplate stringRedisTemplate; 37  38     /** 39      * 登录 40      */ 41     @PostMapping("/login") 42     public ResponseResult login(@RequestBody @Validated LoginRequest request, BindingResult bindingResult) { 43         if (bindingResult.hasErrors()) { 44             return ResponseResult.error(ResponseCodeEnum.PARAMETER_ILLEGAL.getCode(), ResponseCodeEnum.PARAMETER_ILLEGAL.getMessage()); 45         } 46  47         String username = request.getUsername(); 48         String password = request.getPassword(); 49         //  假设查询到用户ID是1001 50         String userId = "1001"; 51         if ("hello".equals(username) && "world".equals(password)) { 52             //  生成Token 53             String token = JWTUtil.generateToken(userId, secretKey); 54  55             //  生成刷新Token 56             String refreshToken = UUID.randomUUID().toString().replace("-", ""); 57  58             //  放入缓存 59             HashOperations 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              * 如果可以允许用户退出后token如果在有效期内仍然可以使用的话,那么就不需要存Redis 66              * 因为,token要跟用户做关联的话,就必须得每次都带一个用户标识, 67              * 那么校验token实际上就变成了校验token和用户标识的关联关系是否正确,且token是否有效 68              */ 69  70 //            String key = MD5Encoder.encode(userId.getBytes()); 71  72             String key = userId; 73             hashOperations.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         return ResponseResult.error(ResponseCodeEnum.LOGIN_ERROR.getCode(), ResponseCodeEnum.LOGIN_ERROR.getMessage()); 86     } 87  88     /** 89      * 退出 90      */ 91     @GetMapping("/logout") 92     public ResponseResult logout(@RequestParam("userId") String userId) { 93         HashOperations hashOperations = stringRedisTemplate.opsForHash(); 94         String key = userId; 95         hashOperations.delete(key); 96         return ResponseResult.success(); 97     } 98  99     /**100      * 刷新Token101      */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 hashOperations = stringRedisTemplate.opsForHash();107         String key = userId;108         String originalRefreshToken = hashOperations.get(key, "refreshToken");109         if (StringUtils.isBlank(originalRefreshToken) || !originalRefreshToken.equals(refreshToken)) {110             return ResponseResult.error(ResponseCodeEnum.REFRESH_TOKEN_INVALID.getCode(), ResponseCodeEnum.REFRESH_TOKEN_INVALID.getMessage());111         }112 113         //  生成新token114         String newToken = JWTUtil.generateToken(userId, secretKey);115         hashOperations.put(key, "token", newToken);116         stringRedisTemplate.expire(userId, JWTUtil.TOKEN_EXPIRE_TIME, TimeUnit.MILLISECONDS);117 118         return ResponseResult.success(newToken);119     }120 }

    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 ChengJianSheng10  * @date 2020-03-0811  */12 @RestController13 @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         return userId;24     }25 26 }

    application.yml

     1 server: 2   port: 8081 3   servlet: 4     context-path: /auth-server 5 spring: 6   application: 7     name: cjs-auth-service 8   redis: 9     host: 127.0.0.110     password: 12345611     port: 637912     lettuce:13       pool:14         max-active: 1015         max-idle: 516         min-idle: 517         max-wait: 5000

    2. 校验Token

    GatewayFilter和GlobalFilter都可以,这里用GlobalFilter

    pom.xml

     1 <?xml version="1.0" encoding="UTF-8"?> 2  4     4.0.0 5      6         org.springframework.boot 7         spring-boot-starter-parent 8         2.2.5.RELEASE 9         10     11     com.cms.example12     cjs-gateway-example13     0.0.1-SNAPSHOT14     cjs-gateway-example15 16     17         1.818         Hoxton.SR119     20 21     22         23             org.springframework.boot24             spring-boot-starter-data-redis-reactive25         26         27             org.springframework.cloud28             spring-cloud-starter-gateway29         30         31             com.auth032             java-jwt33             3.10.034         35         36             com.cjs.example37             cjs-commons-jwt38             1.0-SNAPSHOT39         40 41 42         43             org.projectlombok44             lombok45             true46         47     48 49     50         51             52                 org.springframework.cloud53                 spring-cloud-dependencies54                 ${spring-cloud.version}55                 pom56                 import57             58         59     60 61     62         63             64                 org.springframework.boot65                 spring-boot-maven-plugin66             67         68     69 70 

    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 ChengJianSheng27  * @date 2020-03-0828  */29 @Slf4j30 @Component31 public class AuthorizeFilter implements GlobalFilter, Ordered {32 33     @Value("${secretKey:123456}")34     private String secretKey;35 36 //    @Autowired37 //    private StringRedisTemplate stringRedisTemplate;38 39     @Override40     public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {41         ServerHttpRequest serverHttpRequest = exchange.getRequest();42         ServerHttpResponse serverHttpResponse = exchange.getResponse();43         String uri = serverHttpRequest.getURI().getPath();44 45         //  检查白名单(配置)46         if (uri.indexOf("/auth-server/login") >= 0) {47             return chain.filter(exchange);48         }49 50         String token = serverHttpRequest.getHeaders().getFirst("token");51         if (StringUtils.isBlank(token)) {52             serverHttpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);53             return getVoidMono(serverHttpResponse, ResponseCodeEnum.TOKEN_MISSION);54         }55 56         //todo 检查Redis中是否有此Token57 58         try {59             JWTUtil.verifyToken(token, secretKey);60         } catch (TokenAuthenticationException ex) {61             return 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 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     @Override82     public int getOrder() {83         return -100;84     }85 }

    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/** 

    这里我还自定义了一个日志收集过滤器

     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 ChengJianSheng16  * @date 2020-03-0817  */18 @Component19 public class MyLogGatewayFilterFactory extends AbstractGatewayFilterFactory {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     @Override29     public List shortcutFieldOrder() {30         return Arrays.asList("enabled");31     }32 33     @Override34     public GatewayFilter apply(Config config) {35         return (exchange, chain) -> {36             if (!config.isEnabled()) {37                 return 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         };55     }56 57     public static class Config {58         /**59          * 是否开启60          */61         private boolean enabled;62 63         public Config() {64         }65 66         public boolean isEnabled() {67             return enabled;68         }69 70         public void setEnabled(boolean enabled) {71             this.enabled = enabled;72         }73     }74 } 

    用Postman访问就能看到效果

    http://localhost:8080/auth-server/hello/sayHihttp://localhost:8080/auth-server/hello/sayHello?name=aaa

    497d5949737dc5cb9165017df8416e40.png
    e1ecb65c144860dfa2d24c912a6b5b70.png

    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 -> r20                 .host("*.limited.org").and().path("/anything/**")21                 .filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))22                 .uri("http://httpbin.org"))23             .build();24     }25 }

    3.1. GatewayFilter Factories

    路由过滤器允许以某种方式修改输入的HTTP请求或输出的HTTP响应。路由过滤器适用于特定路由。Spring Cloud Gateway包括许多内置的GatewayFilter工厂。

    3.1.1. AddRequestHeader GatewayFilter Factory

    AddRequestHeader GatewayFilter 采用name和value参数。

    例如:下面的例子,对于所有匹配的请求,将在下游请求头中添加 X-Request-red:blue

    1 spring:2   cloud:3     gateway:4       routes:5       - id: add_request_header_route6         uri: https://example.org7         filters:8         - AddRequestHeader=X-Request-red, blue 

    刚才说了,AddRequestHeader采用name和value作为参数。而URI中的变量可以用在value中,例如:

     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}

    3.1.2. AddRequestParameter GatewayFilter Factory

    AddRequestParameter GatewayFilter 也是采用name和value参数

    例如:下面的例子,对于所有匹配的请求,将会在下游请求的查询字符串中添加 red=blue

    1 spring:2   cloud:3     gateway:4       routes:5       - id: add_request_parameter_route6         uri: https://example.org7         filters:8         - AddRequestParameter=red, blue

    同样,AddRequestParameter也支持在value中引用URI中的变量,例如:

     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}

    3.1.3. AddResponseHeader GatewayFilter Factory

    AddResponseHeader GatewayFilter 依然采用name和value参数。不在赘述,如下:

    1 spring:2   cloud:3     gateway:4       routes:5       - id: add_response_header_route6         uri: https://example.org7         filters:8         - AddResponseHeader=X-Response-Red, Blue

    3.1.4. DedupeResponseHeader GatewayFilter Factory

    DedupeResponseHeader GatewayFilter 采用一个name参数和一个可选的strategy参数。name可以包含以空格分隔的header名称列表。例如:下面的例子,如果在网关CORS逻辑和下游逻辑都将它们添加的情况下,这将删除Access-Control-Allow-Credentials和Access-Control-Allow-Origin响应头中的重复值。

    1 spring:2   cloud:3     gateway:4       routes:5       - id: dedupe_response_header_route6         uri: https://example.org7         filters:8         - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin

    3.1.5. PrefixPath GatewayFilter Factory

    PrefixPath GatewayFilter 只有一个prefix参数。下面的例子,对于所有匹配的请求,将会在请求url上加上前缀/mypath,因此请求/hello在被转发后的url变成/mypath/hello

    1 spring:2   cloud:3     gateway:4       routes:5       - id: prefixpath_route6         uri: https://example.org7         filters:8         - PrefixPath=/mypath

    3.1.6. RequestRateLimiter GatewayFilter Factory

    RequestRateLimiter GatewayFilter 用一个RateLimiter实现来决定当前请求是否被允许处理。如果不被允许,默认将返回一个HTTP 429状态,表示太多的请求。

    这个过滤器采用一个可选的keyResolver参数。keyResolver是实现了KeyResolver接口的一个bean。在配置中,通过SpEL表达式引用它。例如,#{@myKeyResolver}是一个SpEL表达式,它是对名字叫myKeyResolver的bean的引用。KeyResolver默认的实现是PrincipalNameKeyResolver。

    默认情况下,如果KeyResolver没有找到一个key,那么请求将会被拒绝。你可以调整这种行为,通过设置spring.cloud.gateway.filter.request-rate-limiter.deny-empty-key (true or false) 和 spring.cloud.gateway.filter.request-rate-limiter.empty-key-status-code属性。

    Redis基于 Token Bucket Algorithm (令牌桶算法)实现了一个RequestRateLimiter

    redis-rate-limiter.replenishRate 属性指定一个用户每秒允许多少个请求,而没有任何丢弃的请求。这是令牌桶被填充的速率。

    redis-rate-limiter.burstCapacity 属性指定用户在一秒钟内执行的最大请求数。这是令牌桶可以容纳的令牌数。将此值设置为零将阻止所有请求。

    redis-rate-limiter.requestedTokens 属性指定一个请求要花费多少个令牌。这是每个请求从存储桶中获取的令牌数,默认为1。

    通过将replenishRate和burstCapacity设置成相同的值可以实现稳定的速率。通过将burstCapacity设置为高于replenishRate,可以允许临时突发。 在这种情况下,速率限制器需要在两次突发之间保留一段时间(根据replenishRate),因为两个连续的突发将导致请求丢弃(HTTP 429-太多请求)。

    通过将replenishRate设置为所需的请求数,将requestTokens设置为以秒为单位的时间跨度并将burstCapacity设置为replenishRate和requestedToken的乘积。可以达到1个请求的速率限制。 例如:设置replenishRate = 1,requestedTokens = 60和burstCapacity = 60将导致限制为每分钟1个请求。

     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: 1011             redis-rate-limiter.burstCapacity: 2012             redis-rate-limiter.requestedTokens: 1 

    KeyResolver

    1 @Bean2 KeyResolver userKeyResolver() {3     return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));4 }

    上面的例子,定义了每个用户每秒运行10个请求,令牌桶的容量是20,那么,下一秒将只剩下10个令牌可用。KeyResolver实现仅仅只是简单取请求参数中的user,当然在生产环境中不推荐这么做。

    说白了,KeyResolver就是决定哪些请求属于同一个用户的。比如,header中userId相同的就认为是同一个用户的请求。

    当然,你也可以自己实现一个RateLimiter,在配置的时候用SpEL表达式#{@myRateLimiter}去引用它。例如:

     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}"

    补充:(Token Bucket)令牌桶

    https://en.wikipedia.org/wiki/Token_bucket

    令牌桶是在分组交换计算机网络和电信网络中使用的算法。它可以用来检查数据包形式的数据传输是否符合定义的带宽和突发性限制(对流量不均匀性或变化的度量)。

    令牌桶算法就好比是一个的固定容量桶,通常以固定速率向其中添加令牌。一个令牌通常代表一个字节。当要检查数据包是否符合定义的限制时,将检查令牌桶以查看其当时是否包含足够的令牌。如果有足够数量的令牌,并假设令牌以字节为单位,那么,与数据包字节数量等效数量的令牌将被删除,并且该数据包可以通过继续传输。如果令牌桶中的令牌数量不够,则数据包不符合要求,并且令牌桶的令牌数量不会变化。不合格的数据包可以有多种处理方式:

    它们可能会被丢弃当桶中积累了足够的令牌时,可以将它们加入队列进行后续传输它们可以被传输,但被标记为不符合,如果网络负载过高,可能随后被丢弃

    (PS:这句话的意思是说,想象有一个桶,以固定速率向桶中添加令牌。假设一个令牌等效于一个字节,当一个数据包到达时,假设这个数据包的大小是n字节,如果桶中有足够多的令牌,即桶中令牌的数量大于n,则该数据可以通过,并且桶中要删除n个令牌。如果桶中令牌数不够,则根据情况该数据包可能直接被丢弃,也可能一直等待直到令牌足够,也可能继续传输,但被标记为不合格。还是不够通俗,这样,如果把令牌桶想象成一个水桶的话,令牌想象成水滴的话,那么这个过程就变成了以恒定速率向水桶中滴水,当有人想打一碗水时,如果这个人的碗很小,只能装30滴水,而水桶中水滴数量超过30,那么这个人就可以打一碗水,然后就走了,相应的,水桶中的水在这个人打完以后自然就少了30滴。过了一会儿,又有一个人来打水,他拿的碗比较大,一次能装100滴水,这时候桶里的水不够,这个时候他可能就走了,或者在这儿等着,等到桶中积累了100滴的时候再打。哈哈哈,就是酱紫,不知道大家见过水车没有……)

    令牌桶算法可以简单地这样理解:

    每 1/r 秒有一个令牌被添加到令牌桶令牌桶最多可以容纳 b 个令牌。当一个令牌到达时,令牌桶已经满了,那么它将会被丢弃。当一个 n 字节大小的数据包到达时:如果令牌桶中至少有n个令牌,则从令牌桶中删除n个令牌,并将数据包发送到网络。如果可用的令牌少于n个,则不会从令牌桶中删除任何令牌,并且将数据包视为不合格。 

    3.1.7. RedirectTo GatewayFilter Factory

    RedirectTo GatewayFilter 有两个参数:status 和 url。status应该是300系列的。不解释,看示例:

    1 spring:2   cloud:3     gateway:4       routes:5       - id: prefixpath_route6         uri: https://example.org7         filters:8         - RedirectTo=302, https://acme.org

    3.1.8. RemoveRequestHeader GatewayFilter Factory

    1 spring:2   cloud:3     gateway:4       routes:5       - id: removerequestheader_route6         uri: https://example.org7         filters:8         - RemoveRequestHeader=X-Request-Foo

    3.1.9. 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}

    3.1.10. Default Filters

    为了添加一个过滤器,并将其应用到所有路由上,你可以使用spring.cloud.gateway.default-filters,这个属性值是一个过滤器列表

    1 spring:2   cloud:3     gateway:4       default-filters:5       - AddResponseHeader=X-Response-Default-Red, Default-Blue6       - PrefixPath=/httpbin

    3.2. Global Filters

    GlobalFilter应用于所有路由

    3.2.1. GlobalFilter与GatewayFilter组合的顺序

    当一个请求请求与匹配某个路由时,过滤Web处理程序会将GlobalFilter的所有实例和GatewayFilter的所有特定于路由的实例添加到过滤器链中。该组合的过滤器链由org.springframework.core.Ordered接口排序,可以通过实现getOrder()方法进行设置。

    由于Spring Cloud Gateway区分过滤器逻辑执行的“pre”和“post”阶段,因此,优先级最高的过滤器在“pre”阶段是第一个,在“post”阶段是最后一个。

     1 @Bean 2 public GlobalFilter customFilter() { 3     return new CustomGlobalFilter(); 4 } 5  6 public class CustomGlobalFilter implements GlobalFilter, Ordered { 7  8     @Override 9     public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {10         log.info("custom global filter");11         return chain.filter(exchange);12     }13 14     @Override15     public int getOrder() {16         return -1;17     }18 }

    补充:(Token Bucket)令牌桶

    https://en.wikipedia.org/wiki/Token_bucket

    令牌桶是在分组交换计算机网络和电信网络中使用的算法。它可以用来检查数据包形式的数据传输是否符合定义的带宽和突发性限制(对流量不均匀性或变化的度量)。

    令牌桶算法就好比是一个的固定容量桶,通常以固定速率向其中添加令牌。一个令牌通常代表一个字节。当要检查数据包是否符合定义的限制时,将检查令牌桶以查看其当时是否包含足够的令牌。如果有足够数量的令牌,并假设令牌以字节为单位,那么,与数据包字节数量等效数量的令牌将被删除,并且该数据包可以通过继续传输。如果令牌桶中的令牌数量不够,则数据包不符合要求,并且令牌桶的令牌数量不会变化。不合格的数据包可以有多种处理方式:

    它们可能会被丢弃当桶中积累了足够的令牌时,可以将它们加入队列进行后续传输它们可以被传输,但被标记为不符合,如果网络负载过高,可能随后被丢弃

    (PS:这句话的意思是说,想象有一个桶,以固定速率向桶中添加令牌。假设一个令牌等效于一个字节,当一个数据包到达时,假设这个数据包的大小是n字节,如果桶中有足够多的令牌,即桶中令牌的数量大于n,则该数据可以通过,并且桶中要删除n个令牌。如果桶中令牌数不够,则根据情况该数据包可能直接被丢弃,也可能一直等待直到令牌足够,也可能继续传输,但被标记为不合格。还是不够通俗,这样,如果把令牌桶想象成一个水桶的话,令牌想象成水滴的话,那么这个过程就变成了以恒定速率向水桶中滴水,当有人想打一碗水时,如果这个人的碗很小,只能装30滴水,而水桶中水滴数量超过30,那么这个人就可以打一碗水,然后就走了,相应的,水桶中的水在这个人打完以后自然就少了30滴。过了一会儿,又有一个人来打水,他拿的碗比较大,一次能装100滴水,这时候桶里的水不够,这个时候他可能就走了,或者在这儿等着,等到桶中积累了100滴的时候再打。哈哈哈,就是酱紫,不知道大家见过水车没有……)

    令牌桶算法可以简单地这样理解:

    每 1/r 秒有一个令牌被添加到令牌桶令牌桶最多可以容纳 b 个令牌。当一个令牌到达时,令牌桶已经满了,那么它将会被丢弃。当一个 n 字节大小的数据包到达时:如果令牌桶中至少有n个令牌,则从令牌桶中删除n个令牌,并将数据包发送到网络。如果可用的令牌少于n个,则不会从令牌桶中删除任何令牌,并且将数据包视为不合格。 

    展开全文
  • 文章目录前言一、jwt是什么?JWT的构成headerplayload标准中注册的声明 (建议但不强制使用) :公共的声明 :私有的声明 :signature二、使用步骤1.引入库2.jwtutil封装新建拦截拦截器配置controller统一返回...


    前言

    JWT是json web token缩写。它将用户信息加密到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证token的正确性,只要正确即通过验证。


    提示:以下是本篇文章正文内容,下面案例可供参考

    一、jwt是什么?

    Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

    JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    

    JWT的构成

    第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

    header

    jwt的头部承载两部分信息:

    声明类型,这里是jwt

    • 声明加密的算法 通常直接使用 HMAC SHA256
    • 完整的头部就像下面这样的JSON:
    {
      'typ': 'JWT',
      'alg': 'HS256'
    }
    

    然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

    	eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
    

    playload

    载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

    标准中注册的声明

    • 公共的声明
    • 私有的声明

    标准中注册的声明 (建议但不强制使用) :

    • iss: jwt签发者
    • sub: jwt所面向的用户
    • aud: 接收jwt的一方
    • exp: jwt的过期时间,这个过期时间必须要大于签发时间
    • nbf: 定义在什么时间之前,该jwt都是不可用的.
    • iat: jwt的签发时间
    • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

    公共的声明 :

    公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

    私有的声明 :

    私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

    定义一个payload:

    {
    “sub”: “1234567890”,
    “name”: “John Doe”,
    “admin”: true
    }

    然后将其进行base64加密,得到Jwt的第二部分。

    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
    

    signature

    jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

    header (base64后的)
    payload (base64后的)
    secret
    这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

      eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    

    二、使用步骤

    新建springboot 项目

    1.引入库

    代码如下(示例):

            <dependency>
                <groupId>com.auth0</groupId>
                <artifactId>java-jwt</artifactId>
                <version>3.11.0</version>
            </dependency>
    

    2.jwtutil封装

    代码如下(示例):

    public class JWTUtil {
        private static final Logger log = LoggerFactory.getLogger(JWTUtil.class);
        private final static long EXPIRE_TIME = 2 * 3600 * 1000L;
    
    
        private final static String SECRET = "sdfksdakfsdkjafklajiejekjf";
    
        /**
         * 生成 token
         *
         * @param username 用户名
         * @return token
         */
        public static String jwtCreate(String username) {
            try {
                Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
                Algorithm algorithm = Algorithm.HMAC256(SECRET);
                return JWT.create()
                        .withClaim("username", username)
                        .withExpiresAt(date)
                        .sign(algorithm);
            } catch (Exception e) {
                log.error("error:{}", e.getMessage());
                return null;
            }
        }
    
        /**
         * 校验 token是否正确
         *
         * @param token token
         * @return
         */
        public static boolean verify(String token) {
            try {
                Algorithm algorithm = Algorithm.HMAC256(SECRET);
                JWTVerifier verifier = JWT.require(algorithm)
                        .build();
                verifier.verify(token);
                return true;
            } catch (Exception e) {
                log.info("token is invalid");
                return false;
            }
        }
    
        /**
         * 获取用户名
         *
         * @param token
         * @return
         */
        public static String getUsername(String token) {
            try {
                DecodedJWT jwt = JWT.decode(token);
                return jwt.getClaim("username").asString();
            } catch (JWTDecodeException e) {
                log.error("error:{}", e.getMessage());
                return null;
            }
        }
    
    
        public static void main(String[] args) {
            String hello = jwtCreate("hello");
            System.out.println(hello);
            System.out.println(verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDU2MjQ4OTksInVzZXJuYW1lIjoiaGVsbG8ifQ.Yd6VkK3xyfU3It6brU9yyNU1WelbFX4HaAoqILDQbFw"));
            System.out.println(getUsername(hello));
         
        }
    
    
    }
    

    新建拦截

    public class JwtIntercepter implements HandlerInterceptor {
    	//主要功能拦截请求验证token
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String token = request.getHeader("token");
            if (StringUtils.isEmpty(token)) {
                throw new IllegalArgumentException("token 不能为空");
            }
            if (!JWTUtil.verify(token)) {
                throw new IllegalArgumentException("token 验证失败,请重新登录!");
            }
            RequestContext.set(token);
            return true;
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            RequestContext.remove();
        }
    }
    
    
    public class RequestContext {
        private final static ThreadLocal<String> LOCAL = new ThreadLocal<>();
    
        public static void set(String value) {
            LOCAL.set(value);
        }
    
        public static String get() {
            return LOCAL.get();
        }
    
        public static void remove() {
            LOCAL.remove();
        }
    }
    
    

    拦截器配置

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new JwtIntercepter())
            		// 拦截所有请求
                    .addPathPatterns("/**")
                    //不需要的拦截请求
                    .excludePathPatterns("/login");
        }
    	// 跨域配置
        @Override
        public void addCorsMappings(CorsRegistry registry) {
    
            registry.addMapping("/**")
                    .allowCredentials(true)
                    .allowedHeaders("*")
                    .allowedOrigins("*")
                    .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
                    .maxAge(3600);
        }
    }
    
    

    controller

    
    @RestController
    @RequestMapping(value = "/")
    public class TestController {
    
    
        @Autowired
        private UserService userService;
    
        @GetMapping("/info")
        public Result<User> test() {
            User info = userService.info();
            return Result.ok(200, "success", info);
        }
    
    
        @PostMapping(value = "/login")
        public Result<?> login(@RequestBody UserDTO userDTO) {
            User user = userService.login(userDTO);
            if (user != null) {
        
                Map<String, Object> data = new HashMap<>();
                data.put("user", user);
                data.put("token", JWTUtil.jwtCreate(user.getUsername()));
                return Result.ok(200, "success", data);
            }
            return Result.ok(400, "fail", null);
        }
    
    }
    
    
    

    统一返回

    public class Result<T> {
        private int code;
        private String message;
        private T payload;
    
        public int getCode() {
            return code;
        }
    
        public void setCode(int code) {
            this.code = code;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        public T getPayload() {
            return payload;
        }
    
        public void setPayload(T payload) {
            this.payload = payload;
        }
    
        private Result<T> code(int code) {
            this.code = code;
            return this;
        }
    
        private Result<T> message(String message) {
            this.message = message;
            return this;
        }
    
        private Result<T> payload(T payload) {
            this.payload = payload;
            return this;
        }
    
        public static <T> Result<T> ok(int code, String message, T payload) {
            return new Result<T>().code(code).message(message).payload(payload);
        }
    }
    
    

    userservice

    @Service
    public class UserService {
        public static User user = new User();
    
        static {
            user.setUsername("admin");
            user.setPassword("****");
            user.setSex("1");
            user.setAddress("第四大厦");
        }
    
        public User login(UserDTO userDTO) {
            //模拟登录
            if (userDTO.getUsername().equals("admin") && userDTO.getPassword().equals("123456")) {
                return user;
            }
            return null;
        }
    
        public User info() {
            String token = RequestContext.get();
            String username = JWTUtil.getUsername(token);
            if (username.equals(user.getUsername())) {
                return user;
            }
            return null;
        }
    }
    

    异常捕获


    @RestControllerAdvice
    public class GExceptionHandler {
        @ExceptionHandler(Exception.class)
        public Result<?> error(Exception e) {
            return Result.ok(4000, e.getMessage(), "{}");
        }
    }
    
    
    GET http://localhost:8080/info
    token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDU4NTg4MzYsInVzZXJuYW1lIjoiYWRtaW4ifQ.s2R8gE18p5Uk3GtSnurxf17YuyODcaUIjaadwBZjuH0
    
    ------------------------------------------------------
    GET http://localhost:8080/info
    
    HTTP/1.1 200 
    Vary: Origin
    Vary: Access-Control-Request-Method
    Vary: Access-Control-Request-Headers
    Content-Type: application/json
    Transfer-Encoding: chunked
    Date: Fri, 20 Nov 2020 05:56:28 GMT
    Keep-Alive: timeout=60
    Connection: keep-alive
    
    {
      "code": 4000,
      "message": "token 验证失败,请重新登录!",
      "payload": "{}"
    }
    
    

    测试

    login

    POST http://localhost:8080/login
    Content-Type: application/json
    
    {
      "username": "admin",
      "password": "123456"
    }
    
    
    ------------------------------------------------------
    POST http://localhost:8080/login
    
    HTTP/1.1 200 
    Vary: Origin
    Vary: Access-Control-Request-Method
    Vary: Access-Control-Request-Headers
    Content-Type: application/json
    Transfer-Encoding: chunked
    Date: Fri, 20 Nov 2020 05:53:56 GMT
    Keep-Alive: timeout=60
    Connection: keep-alive
    
    {
      "code": 200,
      "message": "success",
      "payload": {
        "user": {
          "username": "admin",
          "password": "******",
          "sex": "1",
          "address": "第四大厦"
        },
        "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDU4NTg4MzYsInVzZXJuYW1lIjoiYWRtaW4ifQ.s2R8gE18p5Uk3GtSnurxf17YuyODcaUIjaadwBZjuH0"
      }
    }
    

    info

    GET http://localhost:8080/info
    token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDU4NTg4MzYsInVzZXJuYW1lIjoiYWRtaW4ifQ.s2R8gE18p5Uk3GtSnurxf17YuyODcaUIjaadwBZjuH0
    
    
    ------------------------------------------------------
    GET http://localhost:8080/info
    
    HTTP/1.1 200 
    Vary: Origin
    Vary: Access-Control-Request-Method
    Vary: Access-Control-Request-Headers
    Content-Type: application/json
    Transfer-Encoding: chunked
    Date: Fri, 20 Nov 2020 05:55:29 GMT
    Keep-Alive: timeout=60
    Connection: keep-alive
    
    {
      "code": 200,
      "message": "success",
      "payload": {
        "username": "admin",
        "password": "******",
        "sex": "1",
        "address": "第四大厦"
      }
    }
    

    总结

    以上就是今天要讲的内容,本文仅仅简单介绍了jwt的使用,结合了springboot拦截器做校验token。

    个人博客

    展开全文
  • 一、什么是JWT JWT全称JSON Web Token,由三部分组成: header(头)、payload(载体)、signature(签名)。 随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的...

    转载自:
    https://www.jianshu.com/p/e34a579c63a0

    一、什么是JWT

    JWT全称JSON Web Token,由三部分组成: header(头)、payload(载体)、signature(签名)。 随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用户信息,随着之后jwt的出现,校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。

    • header
      JWT第一部分是header,header主要包含两个部分,alg指加密类型,可选值为HS256、RSA等等,typ=JWT为固定值,表示token的类型。
    • Payload
      JWT第二部分是payload,payload是token的详细内容,一般包括iss (发行者), exp (过期时间), sub(用户信息), aud (接收者),以及其他信息,详细介绍请参考官网,也可以包含自定义字段。
    {
        "iat": 1493090001,
        "name": "张三"
    }
    
    iss:Issuer,发行者
    sub:Subject,主题
    aud:Audience,观众
    exp:Expiration time,过期时间
    nbf:Not before
    iat:Issued at,发行时间
    jti:JWT ID
    
    • signature
      JWT第二部分是signature,这部分的内容是这样计算得来的:
    1、EncodeString = Base64(header).Base64(payload)
    2、最终token = HS256(EncodeString,"秘钥")
    

    签名是用于验证消息在传递过程中有没有被更改,并且,对于使用私钥签名的token,它还可以验证JWT的发送方是否为它所称的发送方。

    二、JSON Web Tokens是如何工作的

    无论何时用户想要访问受保护的路由或者资源的时候,用户代理(通常是浏览器)都应该带上JWT,典型的,通常放在Authorization header中,用Bearer schema。

    header应该看起来是这样的:

    Authorization: Bearer

    服务器上的受保护的路由将会检查Authorization header中的JWT是否有效,如果有效,则用户可以访问受保护的资源。如果JWT包含足够多的必需的数据,那么就可以减少对某些操作的数据库查询的需要,尽管可能并不总是如此。

    如果token是在授权头(Authorization header)中发送的,那么跨源资源共享(CORS)将不会成为问题,因为它不使用cookie。

    - 验证过程

    1. 签名验证

    当接收方接收到一个JWT的时候,首先要对这个JWT的完整性进行验证,这个就是签名认证。它验证的方法其实很简单,只要把header做base64url解码,就能知道JWT用的什么算法做的签名,然后用这个算法,再次用同样的逻辑对header和payload做一次签名,并比较这个签名是否与JWT本身包含的第三个部分的串是否完全相同,只要不同,就可以认为这个JWT是一个被篡改过的串,自然就属于验证失败了。接收方生成签名的时候必须使用跟JWT发送方相同的密钥,意味着要做好密钥的安全传递或共享

    1. 载体验证

    iss(Issuser):如果签发的时候这个claim的值是“a.com”,验证的时候如果这个claim的值不是“a.com”就属于验证失败

    sub(Subject):如果签发的时候这个claim的值是“liuyunzhuge”,验证的时候如果这个claim的值不是“liuyunzhuge”就属于验证失败

    aud(Audience):如果签发的时候这个claim的值是“[‘b.com’,‘c.com’]”,验证的时候这个claim的值至少要包含b.com,c.com的其中一个才能验证通过

    exp(Expiration time):如果验证的时候超过了这个claim指定的时间,就属于验证失败;nbf(Not Before):如果验证的时候小于这个claim指定的时间,就属于验证失败

    iat(Issued at):它可以用来做一些maxAge之类的验证,假如验证时间与这个claim指定的时间相差的时间大于通过maxAge指定的一个值,就属于验证失败

    jti(JWT ID):如果签发的时候这个claim的值是“1”,验证的时候如果这个claim的值不是“1”就属于验证失败

    **注意:**在验证一个JWT的时候,签名认证是每个实现库都会自动做的,但是payload的认证是由使用者来决定的。因为JWT里面可能不会包含任何一个标准的claim,所以它不会自动去验证这些claim。

    以登录认证来说,在签发JWT的时候,完全可以只用sub跟exp两个claim,用sub存储用户的id,用exp存储它本次登录之后的过期时间,然后在验证的时候仅验证exp这个claim,以实现会话的有效期管理。

    三、实战DEMO

    • 验证流程

    1.用户携带用户名和密码请求访问
    2.服务器校验用户凭据
    3.应用提供一个token给客户端
    4.客户端存储token,并且在随后的每一次请求中都带着它
    5.服务器校验token并返回数据

    • 注意
    1. 每一次请求都需要token
    2. Token应该放在请求header中
    3. 我们还需要将服务器设置为接受来自所有域的请求,用Access-Control-Allow-Origin: *

    1、引入相关pom

            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt-api</artifactId>
                <version>0.10.5</version>
            </dependency>
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt-impl</artifactId>
                <version>0.10.5</version>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt-jackson</artifactId>
                <version>0.10.5</version>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>com.auth0</groupId>
                <artifactId>java-jwt</artifactId>
                <version>3.2.0</version>
            </dependency>
    

    2、编写JWT生成解析工具类

    public class JWTUtils {
        public static String createJWT(String id,String subject,long ttlMillis,SecretKey key){
            //获取签名算法
            SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
            long nowMillis = System.currentTimeMillis();
            Date now = new Date(nowMillis);
    
            JwtBuilder builder = Jwts.builder()
                    .setId(id)
                    .setSubject(subject)
                    .setIssuer("user")
                    .setIssuedAt(now)
                    .signWith(key,signatureAlgorithm);
            if(ttlMillis >= 0){
                long expMillis = nowMillis + ttlMillis;
                Date expDate = new Date(expMillis);
                //设置过期时间
                builder.setExpiration(expDate);
            }
            return builder.compact();
    
        }
    
    
        public static Claims parseJWT(String jwt,SecretKey keySpec){
            return Jwts.parser()
                    .setSigningKey(keySpec)
                    .parseClaimsJws(jwt)
                    .getBody();
        }
    
        public static ResultCode validateJWT(String jwt){
            ResultCode checkResult = new ResultCode();
            Claims claims = null;
            try {
                claims = parseJWT(jwt);
                checkResult.setSuccess(true);
                checkResult.setClaims(claims);
            } catch (ExpiredJwtException e) {
                checkResult.setErrCode("解析失败");
                checkResult.setSuccess(false);
            }
            return checkResult;
        }
        public static void main(String[] args) {
            //生成密匙
            SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
    
            String jwt = JWTUtils.createJWT("1","test",10000,key);
            System.out.println(jwt);
            System.out.println(JWTUtils.parseJWT(jwt,key));
        }
    }
    

    3、控制器层代码

    • 登陆逻辑
        @RequestMapping("/login")
        public String login(用户信息){
            //在这里进行用户身份校验
            ......
            ......
            //校验身份成功生成token返回
            String jwt = JWTUtils.createJWT(用户信息);
            return jwt;
        }
    
    • 所有需要用户身份验证的接口逻辑
        //这里只大致写一些,具体验证失败原因以及返回格式问题可以根据自身细调
    
        public String toIndex(HttpServletRequest request){
            //获取token
            String authorization = request.getHeader("authorization");
            if(authorization == null){
                //返回登陆
                return "需要登陆";
            }else{
                //进行校验
                try{
                    Claims claims = JWTUtils.parseJWT(authorization);
                    return "验证成gong";
                }catch (Exception e){
                    e.printStackTrace();
                    return "验证失败";
                }
            }
    
        }
    

    3、客户端代码

    • 输入用户信息,申请登陆
        $.ajax({
            url:'http://localhost:8080/demo/login',
            type:'GET',
            data:userLoginData,
            success:function(res) {
                localStorage.setItem("token",res)
            },
            error:function (res) {
                alert(res)
    
            }
    
        })
    
    • 当请求需要身份验证接口时
    var token = localStorage.getItem("token");
    
    $.ajax({
            url:'http://localhost:8080/demo/toIndex',
            type:'GET',
            header:{
                'Authorization':token
            },
            beforeSend : function(request) {
                request.setRequestHeader("Authorization",token );
            },
            success:function(res) {
              
            },
        })
    

    四、参考连接

    https://github.com/jwtk/jjwt#install-jdk-maven
    http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
    https://www.jianshu.com/p/f25d62305c70
    https://www.cnblogs.com/cjsblog/p/9277677.html
    https://www.jianshu.com/p/fe67b4bb6f2c

    作者:我有一只喵喵
    链接:https://www.jianshu.com/p/e34a579c63a0
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    展开全文
  • 不要问为什么,粘贴就完事了!!! 1.先来自定义一个注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Login { } 其中注解@Retention可以用来修饰注解,...

    不要问为什么,粘贴就完事了!!!

    1.先来自定义一个注解

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Login {
    }
    

    其中注解@Retention可以用来修饰注解,是注解的注解,称为元注解。
    按生命周期来划分可分为3类:
    1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
    2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
    3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
    这3个生命周期分别对应于:Java源文件(.java文件) —> .class文件 —> 内存中的字节码。
    那怎么来选择合适的注解生命周期呢?
    首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。

    2,来一个拦截器类

    @Component
    public class AuthorizationInterceptor extends HandlerInterceptorAdapter {
        @Autowired
        private TokenService tokenService;
    
        public static final String USER_KEY = "userId";
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
           
        	//设置跨域--开始
        	HttpServletResponse httpResponse = (HttpServletResponse) response;
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
                setHeader(httpRequest,httpResponse);
                return true;
            }
            //设置跨域--结束
            
        	Login annotation;
            if(handler instanceof HandlerMethod) {
                annotation = ((HandlerMethod) handler).getMethodAnnotation(Login.class);
            }else{
                return true;
            }
    
            if(annotation == null){
                return true;
            }
    
            //从header中获取token
            String token = request.getHeader("token");
            //如果header中不存在token,则从参数中获取token
            if(StringUtils.isBlank(token)){
                token = request.getParameter("token");
            }
    
            //token为空
            if(StringUtils.isBlank(token)){
                throw new RenException(ErrorCode.TOKEN_NOT_EMPTY);
            }
    
            //查询token信息
            TokenEntity tokenEntity = tokenService.getByToken(token);
            if(tokenEntity == null || tokenEntity.getExpireDate().getTime() < System.currentTimeMillis()){
                throw new RenException(ErrorCode.TOKEN_INVALID);
            }
    
            //设置userId到request里,后续根据userId,获取用户信息
            request.setAttribute(USER_KEY, tokenEntity.getUserId());
    
            return true;
        }
        
        /**
         * 为response设置header,实现跨域
         */
        private void setHeader(HttpServletRequest request,HttpServletResponse response){
            //跨域的header设置
            response.setHeader("Access-control-Allow-Origin", request.getHeader("Origin"));
            response.setHeader("Access-Control-Allow-Methods", request.getMethod());
            response.setHeader("Access-Control-Allow-Credentials", "true");
            response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
            //防止乱码,适用于传输JSON数据
            response.setHeader("Content-Type","application/json;charset=UTF-8");
        }
        
    }
    
    

    上面有些是数据库的操作,其中注解:@component (把普通pojo实例化到spring容器中,相当于配置文件中的
    ),一般作用于组件,preHandle:在方法被调用前执行。在该方法中可以做类似校验的功能。如果返回true,则继续调用下一个拦截器。如果返回false,则中断执行。

    3.写一个请求拦截类,让拦截器生效

    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
        @Autowired
        private AuthorizationInterceptor authorizationInterceptor;
        @Autowired
        private LoginUserHandlerMethodArgumentResolver loginUserHandlerMethodArgumentResolver;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(authorizationInterceptor).addPathPatterns("/api/**");
        }
    
        @Override
        public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
            argumentResolvers.add(loginUserHandlerMethodArgumentResolver);
        }
    }
    
    

    这里拦截了所有请求前面有/api的请求

    至此自定义注解拦截操作已经做完,在有/api/ * * 的请求,只要加上@Login注解都需要tonken验证,不然就会抛出异常

    展开全文
  • 网上借个图按微信要求:确认此次GET请求来自微信服务器,则原样返回echostr参数内容都按要求来了,但怎么都不行,总是提示:Token校验失败,请检查确认仔细检查了配置好几次,都没发现能有什么问题下面代码官方...
  • Jwt生成token以及token过期校验

    千次阅读 2019-12-06 09:48:02
    1 什么是JJWT ​ JJWT一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。 2 JJWT快速...
  • 要说PHP不会的话,什么都不用说!拿过来看一看,学一学,不就会了吗?再说了编程语言都想通的,逻辑上都一样的!本着这个想法,开始了我的微信平台开发之旅! 首先需要注册微信平台号,百度有教程 再者需要...
  • 自定义注解校验Token

    千次阅读 2018-08-24 11:35:26
    小伙伴在系统开发过程中,会遇到部分接口需要授权才能请求的问题,通常的做法服务端在客户端登录成功后 ,按一定规则生成token,返回给客户端。然后客户端在请求需要授权的接口时,带上此token。那么问题来了,...
  • 什么是 Token 令牌

    2020-07-22 11:55:20
    在计算机身份认证中令牌(临时)的意思,在词法分析中标记的意思. ...密保令牌(Security token),或者硬件令牌,例如U盾,或者叫做认证令牌或者加密令牌,一种计算机身份校验的物理设备 会话令牌(Se...
  • 什么是token

    2020-08-09 14:24:31
    百度百科解释 Token, 令牌,代表执行某些操作的权利的对象 密保令牌(Security token),或者硬件令牌,例如U盾,或者叫做认证令牌或者加密令牌,一种...Token是服务端生成的一串字符串,以作客户端进行请求的一个
  • 一、什么是JWT (1)JWT起源 (2)基于token的鉴权机制 (3)JWT的结构? (4)如何应用 (5)总结 一、什么是JWT Json web token (JWT), 为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519)...
  • 前后端分离后,如果客户端使用的原生应用(iOS,安卓),我们就无法使用cookie和session机制,所以我们使用另一套方案tokentoken机制:1....3.前端再次访问request请求携带token串 (一般放在http的请求头里面)4.后端...
  • 判断登录用户是否合法(虽然你你,但是你还需要门票(token)才能进入) 请求的唯一性(防止恶意重复提交–爬虫、网络攻击等) 二:工作流程 不加校验: 获取一个列表:...
  • 什么是token Token是服务端⽣成的⼀串字符串,作为客户端进⾏请求的⼀个令牌。当⽤户登录后,服务器⽣成⼀ 个Token返回给客户端,之后客户端只需带上这个Token来请求数据即可,⽆需每次都输⼊⽤户名和 密码来鉴权。...
  • 前言 在web中,我们经常说session,token,cookie。这三个内容,究竟啥区别,为什么会有这三个内容呢?这就是我们今天想要讨论的。 故事,美好的开始 ...琪琪: 那个倒是没用过,我原先了解token做的,然...
  • token和jwt存在什么区别

    万次阅读 热门讨论 2018-12-14 22:44:07
    token和jwt存在什么区别 结论: 相同: 都访问资源的令牌, 都可以记录用户信息,都只有验证成功后 区别: ​ 服务端验证客户端发来的token信息要进行数据的查询操作;JWT验证客户端发来的token新戏就不用,...
  • 去年我写了一篇介绍 jwt 的文章。 文章指出如果没有特别的用户注销及单用户多设备登录的需求,可以使用 jwt,而 jwt 的最大的特征就是无状态,且不加密。 除了用户登录方面外,...至于为什么前端写的简陋,完全因...
  • token

    2018-11-15 23:06:55
    首先说下,怎么碰到这个问题的,面试的一家公司问我TOKEN是什么?一脸懵逼… 一.什么是token Token 的中文有人翻译成 “令牌”,意思就是,你拿着这个令牌,才能过一些关卡.一般用来做登录校验或则身份验证. 二.传统的...
  • 什么是 JSON Web Token(JWT)?JSON Web Token (JWT) 作为一个开放的标准 (RFC 7519) 定义了一种简洁自包含的方法用于通信双方之间以 JSON 对象的形式安全的传递信息。因为有数字签名,所以这些通信的信息能够被校验...
  • 最近在网上和朋友聊天,发现他在数据接口中校验登录状态用的还是session,在我及时劝说和科普之后,他最终决定改用JWT,那么接下来我们就聊一聊数据接口应该怎么管理登录状态以及什么是JWTJWT官网访问地址...
  • 最近做一个前后分离的项目,负责后端的开发,首先基本框架的搭建,还有就是安全校验的选择,因为shiro对权限的良好控制,决定shrio+jwt框架。 同时也有一些疑问 jwt安全吗,拿到jwttoken可以直接访问系统吗 过期...
  • cookie:登录后服务端生成的sessionid,并在http请求里返回到...token:同样登录后服务端返回一个token,客户端保存起来,在以后http请求里手动的加入到请求头里,服务端根据token 进行身份的校验。浏览器不会自动
  • SpringSecurity代码实现JWT接口权限授予与校验通过笔者前两篇文章的说明,相信大家已经知道JWT是什么,怎么用,该如何结合Spring Security使用。那么本节就用代码来具体的实现一下JWT登录认证及鉴权的流程。为了大...
  • 需要在页面上有一个地方存放token,以表单提交的方式提供给后台,后台可以校验表单中的token和cookie中的token是否一致,一致则继续校验token,不一致直接返回; 既然页面有token了为什么cookie还要额外存放一份,...
  • token鉴权

    千次阅读 2020-12-17 11:36:07
    一、什么是token? 其实token就是一个令牌,通俗一点可以称为‘暗号’,在一些数据传递之前,要先进行暗号的核对,不同的暗号(令牌)之前被授权不同的数据操作。 二、token的运行机制。 #mermaid-svg-...

空空如也

空空如也

1 2 3 4 5 ... 10
收藏数 200
精华内容 80
关键字:

token校验是什么