赞
踩
token是服务端生成的一串字符串,以作客户端进行请求的令牌,当第一次登陆后,服务器生成一个token便将此token返回给客户端,以后客户端只要带上这个token前来请求数据即可,无需再次带上用户名和密码
SpringBoot 集成 JWT(token)
拦截器自动验证验证 token 是否过期
token 自动刷新(单个 token 刷新机制,保证活跃用户不会掉线)
标准统一的 RESTFul 返回体数据格式
异常统一拦截处理
单个 token 刷新机制(介绍):
token 距离发布token 2 个小时内的token为新生token,2-3 个小时的token为老年token
每次请求,前端带上 token,
(1)如果 token 为新 token ,服务器返回原来的 token
(2)如果 token 为老年 token,服务器返回 刷新后的新生token ,
(3)如果 token 为过期 token,服务器返回token过期 状态码 401,,请求失败, 前端重新登录
二、代码
1. 导入依赖
jwt 依赖
- <dependency>
- <groupId>com.auth0</groupId>
- <artifactId>java-jwt</artifactId>
- <version>3.10.3</version>
- </dependency>
- server:
- port: 8081
- spring:
- application:
- name: tokendemo
- # token
- token:
- privateKey: 'fdasfgdsagaxgsregdfdjyghjfhebfdgwe45ygrfbsdfshfdsag'
- yangToken: 1000000
- oldToken: 3000000000
代码结构如下
AuthWebMvcConfigurer
- @Configuration
- public class AuthWebMvcConfigurer implements WebMvcConfigurer {
- @Autowired
- AuthHandlerInterceptor authHandlerInterceptor;
-
- /**
- * 给除了 /login 的接口都配置拦截器,拦截转向到 authHandlerInterceptor
- */
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(authHandlerInterceptor)
- .addPathPatterns("/**")
- .excludePathPatterns("/login");
- }
- }
TokenTestController
- @RestController
- public class TokenTestController {
- @Autowired
- TokenUtil tokenUtil;
- /**
- * 使用 /login 请求获得 token, /login 不经过拦截器
- */
- @RequestMapping("/login")
- public String login(){
- return tokenUtil.getToken("靓仔","admin");
- }
- /**
- * 使用 /test-token 测试 token,进过拦截器
- */
- @RequestMapping("/test-token")
- public Map testToken(HttpServletRequest request){
- String token = request.getHeader("token");
- return tokenUtil.parseToken(token);
- }
-
- }
TokenAuthExpiredException
- public class TokenAuthExpiredException extends RuntimeException{
- }
AuthHandlerInterceptor
- @Slf4j
- @Component
- public class AuthHandlerInterceptor implements HandlerInterceptor {
- @Autowired
- TokenUtil tokenUtil;
- @Value("${token.privateKey}")
- private String privateKey;
- @Value("${token.yangToken}")
- private Long yangToken;
- @Value("${token.oldToken}")
- private Long oldToken;
- /**
- * 权限认证的拦截操作.
- */
- @Override
- public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
- log.info("=======进入拦截器========");
- // 如果不是映射到方法直接通过,可以访问资源.
- if (!(object instanceof HandlerMethod)) {
- return true;
- }
- //为空就返回错误
- String token = httpServletRequest.getHeader("token");
- if (null == token || "".equals(token.trim())) {
- return false;
- }
- log.info("==============token:" + token);
- Map<String, String> map = tokenUtil.parseToken(token);
- String userId = map.get("userId");
- String userRole = map.get("userRole");
- long timeOfUse = System.currentTimeMillis() - Long.parseLong(map.get("timeStamp"));
- //1.判断 token 是否过期
- //年轻 token
- if (timeOfUse < yangToken) {
- log.info("年轻 token");
- }
- //老年 token 就刷新 token
- else if (timeOfUse >= yangToken && timeOfUse < oldToken) {
- httpServletResponse.setHeader("token",tokenUtil.getToken(userId,userRole));
- }
- //过期 token 就返回 token 无效.
- else {
- throw new TokenAuthExpiredException();
- }
- //2.角色匹配.
- if ("user".equals(userRole)) {
- log.info("========user账户============");
- return true;
- }
- if ("admin".equals(userRole)) {
- log.info("========admin账户============");
- return true;
- }
- return false;
- }
-
- }
GlobalExceptionHandler
- @Slf4j
- @ControllerAdvice
- public class GlobalExceptionHandler {
- /**
- * 用户 token 过期
- * @return
- */
- @ExceptionHandler(value = TokenAuthExpiredException.class)
- @ResponseBody
- public String tokenExpiredExceptionHandler(){
- log.warn("用户 token 过期");
- return "用户 token 过期";
- }
- }
TokenUtil
- @Component
- public class TokenUtil {
- @Value("${token.privateKey}")
- private String privateKey;
-
- /**
- * 加密token.
- */
- public String getToken(String userId, String userRole) {
- //这个是放到负载payLoad 里面,魔法值可以使用常量类进行封装.
- String token = JWT
- .create()
- .withClaim("userId" ,userId)
- .withClaim("userRole", userRole)
- .withClaim("timeStamp", System.currentTimeMillis())
- .sign(Algorithm.HMAC256(privateKey));
- return token;
- }
-
- /**
- * 解析token.
- * (优化可以用常量固定魔法值+使用DTO在 mvc 之前传输数据,而不是 map,这里因为篇幅原因就不做了)
- * {
- * "userId": "3412435312",
- * "userRole": "ROLE_USER",
- * "timeStamp": "134143214"
- * }
- */
- public Map<String, String> parseToken(String token) {
- HashMap<String, String> map = new HashMap<>();
- DecodedJWT decodedjwt = JWT.require(Algorithm.HMAC256(privateKey))
- .build().verify(token);
- Claim userId = decodedjwt.getClaim("userId");
- Claim userRole = decodedjwt.getClaim("userRole");
- Claim timeStamp = decodedjwt.getClaim("timeStamp");
- map.put("userId", userId.asString());
- map.put("userRole", userRole.asString());
- map.put("timeStamp", timeStamp.asLong().toString());
- return map;
- }
- }
访问
localhost:8081/login
效果:
将 1 测试得到的 token 放到 header 里面测试 token是否可用
访问
localhost:8081/test-token
测试全局异常拦截类拦截到 TokenAuthExpiredException
异常,然后返回提示。
将过期时间调小,修改 application.yaml 文件,3 秒钟就过期
- server:
- port: 8081
- spring:
- application:
- name: tokendemo
- # token
- token:
- privateKey: 'fdasfgdsagaxgsregdfdjyghjfhebfdgwe45ygrfbsdfshfdsag'
- yangToken: 1000
- oldToken: 3000
重启应用测试:
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。