Spring boot如何整合JWT呢?
下文笔者讲述SpringBoot整合JWT的方法及示例分享,如下所示
JWT的简介
(JWT): 是JSON Web Token的缩写 是目前最流行的跨域身份验证解决方案 JWT是一个开放标准(RFC 7519)定义的方式 用于在网络之间安全地传输信息 JWT是一个紧凑的 URL安全的手段,双方在通信时这些信息可被验证和信任, 这些信息都是经过数字签名 JWT可以使用秘密(使用HMAC算法) 或 使用RSA或ECDSA公钥/私钥对对进行签名 这些签名后的信息,不保存在服务器上,都保存在客户端的Cookie中
JWT与Cookie/session的对比
传统的Cookie和Session模式: 在传统的用户登录认证中 由于http是无状态的 所以都是采用session方式 用户登录成功 服务端会保证一个session 客户端会保存一个sessionId 客户端会把sessionId保存在cookie中 每次请求都会携带这个sessionId。 JWT方式: JWT方式校验方式更加简单便捷化 无需通过redis缓存 而是直接根据token取出保存的用户信息
JWT构成与交互流程
第一部分为头部(header) 第二部分我们称其为载荷(payload) 第三部分是签证(signature) 【中间用 . 分隔】 例:标准JWT eyJraWQiOiJ4ZTVhNkhmdnRzdWxNTm4zaXZlaGh2Y1Y1cnVhbjljZiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0. eyJzdWIiOiI2MTYzNjk0Mzk5MTQzNDMyOTYiLCJjbGllbnRfdHlwZSI6MCwiZXhwIjoxNzAzODc0MDU2LCJpYXQiOjE3MDM4NTk2NTZ9. 4pkvR3V1XzjCpEtNjcX65js37kcE5kLxIzyciwA85dQ
Jwt的头部承载两部分信息
声明类型: 这里是jwt 声明加密的算法 通常直接使用HMACSHA256,就是HS256了 { “alg”: “HS256”, “typ”: “JWT” } 然后将头部进行base64编码构成了第一部分: eyJraWQiOiJ4ZTVhNkhmdnRzdWxNTm4zaXZlaGh2Y1Y1cnVhbjljZiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0 第二部分:载荷 载荷: 即承载的意思 也就是说这里是承载消息具体内容的地方。 (你在令牌上附带的信息: 如 用户的姓名,这样以后验证令牌之后 就可以直接从这里获取信息而不用再查数据库) 内容又可以分为3种标准 标准中注册的声明 公共的声明 私有的声明 【标准声明】 iss: jwt签发者 sub: jwt所面向的用户 aud: 接收jwt的一方 exp: jwt的过期时间,这个过期时间必须要大于签发时间 nbf: 定义在什么时间之前,该jwt都是不可用的. iat: jwt的签发时间 jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。 【公共声明】 公共的声明可以添加任何的信息 一般添加用户的相关信息或其他业务需要的必要信息 但不建议添加敏感信息 因为该部分在客户端可解密 【私有声明】 私有声明是提供者和消费者所共同定义的声明 一般不建议存放敏感信息 因为base64是对称解密的 意味着该部分信息可以归类为明文信息 对Payload进行Base64加密就得到了JWT第二部分的内容 JWT的第三部分是一个签证信息 这个签证信息由三部分组成: header (base64后的) payload (base64后的) secret 第三部分需要base64加密后的header和base64加密后的payload使用 . 连接组成的字符串, 然后通过header中声明的加密方式进行加盐secret组合加密, 然后就构成了JWT的第三部分。 签名后可防止数据被修改
服务端数据验证过程
验证流程: 1.在头部信息中声明加密算法和常量,把header使用json转化为字符串 2.在载荷中声明用户信息,同时还有一些其他的内容 再次使用json 把载荷部分进行转化,转化为字符串 3.使用在header中声明的加密算法和每个项目随机生成的secret来进行加密 把第一步分字符串和第二部分的字符串进行加密 生成新的字符串。词字符串是独一无二的。 4.解密 只要客户端带着JWT来发起请求 服务端就直接使用secret进行解密
JWT缺点
安全性 性能 一次性
Spring boot与JWT整合示例
新建maven项目
例:jwttest
添加maven依赖
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2021.0.4.0</version> </dependency>
Springboot配置文件设置
server: port: 99999 spring: application: name: jwttest cloud: nacos: discovery: server-addr: localhost:8848 #Nacos server 的地址 config: jwt: # 加密密钥 secret: tigerkey # token有效时长 expire: 3600 # header 名称 header: token
添加类 config
package com.java265.springCloud.auth.config; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.Date; @Component @ConfigurationProperties(prefix = "config.jwt") @Data public class JwtConfig { /** * 密钥 */ private String secret; /** * 过期时间 */ private Long expire; /** * 头部 */ private String header; /** * 生成token * @param subject * @return */ public String createToken(String subject){ Date nowDate = new Date(); Date expireDate = new Date(nowDate.getTime() + expire * 1000); return Jwts.builder() .setHeaderParam("typ","JWT") .setSubject(subject) .setIssuedAt(nowDate) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS512,secret) .compact(); } /** * 获取token中的注册信息 * @param token * @return */ public Claims getTokenClaim(String token){ try{ return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); }catch (Exception e){ return null; } } /** * 验证token是否过期 * @param expirationTime * @return */ public boolean isTokenExpired(Date expirationTime){ if(null == expirationTime){ return true; }else{ return expirationTime.before(new Date()); } } /** * 获取token的失效时间 * @param token * @return */ public Date getExpirationDateFromToken(String token){ Claims tokenClaim = this.getTokenClaim(token); if(tokenClaim == null){ return null; }else{ return this.getTokenClaim(token).getExpiration(); } } /** * 获取token中的用户名 * @param token * @return */ public String getUserNameFromToken(String token){ return this.getTokenClaim(token).getSubject(); } /** * 获取token中发布时间 * @param token * @return */ public Date getIssuedDateFromToken(String token){ return this.getTokenClaim(token).getIssuedAt(); } }
添加controller
package com.java265.springCloud.auth.controller; import com.java265.springCloud.auth.config.JwtConfig; import com.java265.springCloud.common.entity.UserInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/auth") public class AuthController { @Autowired private JwtConfig jwtConfig; @PostMapping("/login") public Map<String,String> login(@RequestBody UserInfo userInfo){ String token = jwtConfig.createToken(userInfo.getUserAccount()); Map<String, String> map = new HashMap<String, String>(); map.put("token",token); return map; } /** * token是否过期 * @param token * @return */ @PostMapping("/isTokenExpiration") public Boolean isTokenExpiration(@RequestParam String token){ return this.jwtConfig.isTokenExpired(this.jwtConfig.getExpirationDateFromToken(token)); } }
gateway工程
新增线程类UrlThread
package com.java265.springCloud.gateway.hread; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import java.util.concurrent.Callable; public class UrlThread implements Callable<String> { private final LoadBalancerClient loadBalancerClient; public UrlThread(LoadBalancerClient loadBalancerClient) { this.loadBalancerClient = loadBalancerClient; } @Override public String call() throws Exception { ServiceInstance serviceInstance = this.loadBalancerClient.choose("auth-service"); return "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/auth/isTokenExpiration"; } }
新增GlobalBeanConf类
@Configuration public class GlobalBeanConf { @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
全局过滤器改造
//负载均衡获取微服务实例 private final LoadBalancerClient loadBalancerClient; //远程调用 private final RestTemplate restTemplate; public DrfGlobalFilter(LoadBalancerClient loadBalancerClient, RestTemplate restTemplate) { this.loadBalancerClient = loadBalancerClient; this.restTemplate = restTemplate; } //启动获取url的线程 FutureTask<String> stringFutureTask = new FutureTask<String>(new UrlThread(this.loadBalancerClient)); new Thread(stringFutureTask).start(); String url = stringFutureTask.get(); Boolean aBoolean = this.restTemplate.postForObject(url+"?token="+token, null,Boolean.class); package com.java265.springCloud.gateway.filter; import com.java265.springCloud.common.dto.TokenDTO; import com.java265.springCloud.gateway.service.AuthService; import com.java265.springCloud.gateway.thread.ValidateTokenTask; import lombok.SneakyThrows; import org.apache.commons.lang3.StringUtils; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; @Component public class DrfGlobalFilter implements GlobalFilter, Ordered { private final AuthService authService; private ExecutorService executorService; public DrfGlobalFilter(AuthService authService) { this.authService = authService; this.executorService = Executors.newFixedThreadPool(5); } @SneakyThrows @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); //如果登录请求,不用验证token String path = request.getURI().getPath(); if(!path.contains("login")){ HttpHeaders headers = request.getHeaders(); String token = headers.getFirst("token"); //token为空表示没有登录,否则已经登录 if(StringUtils.isBlank(token)){ ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); }else{ TokenDTO tokenDTO = new TokenDTO(); tokenDTO.setToken(token); //token验证不通过,返回给前端401 FutureTask<Boolean> booleanFutureTask = new FutureTask<>(new ValidateTokenTask(this.authService, token)); this.executorService.submit(booleanFutureTask); Boolean aBoolean = booleanFutureTask.get(); if(aBoolean){ ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } } } return chain.filter(exchange); } @Override public int getOrder() { return 0; } } package com.java265.springCloud.gateway.service; import com.java265.springCloud.common.dto.TokenDTO; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @FeignClient(value = "auth-service") public interface AuthService { @PostMapping("/auth/validateToken") public Boolean validateToken(TokenDTO tokenDTO); } package com.java265.springCloud.gateway.config; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import java.util.stream.Collectors; @Configuration public class GlobalConf { @Bean @ConditionalOnMissingBean public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) { return new HttpMessageConverters(converters.orderedStream().collect(Collectors.tolist())); } } package com.java265.springCloud.gateway.thread; import com.java265.springCloud.common.dto.TokenDTO; import com.java265.springCloud.gateway.service.AuthService; import java.util.concurrent.Callable; public class ValidateTokenTask implements Callable<Boolean> { private final AuthService authService; private final String token; public ValidateTokenTask(AuthService authService, String token) { this.authService = authService; this.token = token; } @Override public Boolean call() throws Exception { TokenDTO tokenDTO = new TokenDTO(); tokenDTO.setToken(this.token); return this.authService.validateToken(tokenDTO); } }
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。