1.首先了解一下Token
1、token也称作令牌,由uid+time+sign[+固定参数]组成:
- uid: 用户唯一身份标识
- time: 当前时间的时间戳
- sign: 签名, 使用 hash/encrypt 压缩成定长的十六进制字符串,以防止第三方恶意拼接
- 固定参数(可选): 将一些常用的固定参数加入到 token 中是为了避免重复查数据库
2.token 验证的机制(流程)
- 用户登录校验,校验成功后就返回Token给客户端。
- 客户端收到数据后保存在客户端
- 客户端每次访问API是携带Token到服务器端。
- 服务器端采用filter过滤器校验。校验成功则返回请求数据,校验失败则返回错误码
3.使用SpringBoot搭建基于token验证
3.1 引入 POM 依赖
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency>
3.2 新建一个拦截器配置 用于拦截前端请求 实现 WebMvcConfigurer
1 /*** 2 * 新建Token拦截器 3 * @Title: InterceptorConfig.java 4 * @author MRC 5 * @date 2019年5月27日 下午5:33:28 6 * @version V1.0 7 */ 8 @Configuration 9 public class InterceptorConfig implements WebMvcConfigurer { 10 @Override 11 public void addInterceptors(InterceptorRegistry registry) { 12 registry.addInterceptor(authenticationInterceptor()) 13 .addPathPatterns("/**"); // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录 14 } 15 @Bean 16 public AuthenticationInterceptor authenticationInterceptor() { 17 return new AuthenticationInterceptor();// 自己写的拦截器 18 }
//省略其他重写方法
19 20 }
3.3 新建一个 AuthenticationInterceptor 实现HandlerInterceptor接口 实现拦截还是放通的逻辑
1 public class AuthenticationInterceptor implements HandlerInterceptor { 2 @Autowired 3 UserService userService; 4 @Override 5 public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception { 6 String token = httpServletRequest.getHeader("token");// 从 http 请求头中取出 token 7 // 如果不是映射到方法直接通过 8 if(!(object instanceof HandlerMethod)){ 9 return true; 10 } 11 HandlerMethod handlerMethod=(HandlerMethod)object; 12 Method method=handlerMethod.getMethod(); 13 //检查是否有passtoken注释,有则跳过认证 14 if (method.isAnnotationPresent(PassToken.class)) { 15 PassToken passToken = method.getAnnotation(PassToken.class); 16 if (passToken.required()) { 17 return true; 18 } 19 } 20 //检查有没有需要用户权限的注解 21 if (method.isAnnotationPresent(UserLoginToken.class)) { 22 UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class); 23 if (userLoginToken.required()) { 24 // 执行认证 25 if (token == null) { 26 throw new RuntimeException("无token,请重新登录"); 27 } 28 // 获取 token 中的 user id 29 String userId; 30 try { 31 userId = JWT.decode(token).getAudience().get(0); 32 } catch (JWTDecodeException j) { 33 throw new RuntimeException("401"); 34 } 35 User user = userService.findUserById(userId); 36 if (user == null) { 37 throw new RuntimeException("用户不存在,请重新登录"); 38 } 39 // 验证 token 40 JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build(); 41 try { 42 jwtVerifier.verify(token); 43 } catch (JWTVerificationException e) { 44 throw new RuntimeException("401"); 45 } 46 return true; 47 } 48 } 49 return true; 50 } 51 52 @Override 53 public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { 54 55 } 56 @Override 57 public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { 58 59 } 60 }
3.4 新建两个注解 用于标识请求是否需要进行Token 验证
/*** * 用来跳过验证的 PassToken * @author MRC * @date 2019年4月4日 下午7:01:25 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface PassToken { boolean required() default true; }
/** * 用于登录后才能操作 * @author MRC * @date 2019年4月4日 下午7:02:00 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface UserLoginToken { boolean required() default true; }
3.5 新建一个Server 用于下发Token
/*** * token 下发 * @Title: TokenService.java * @author MRC * @date 2019年5月27日 下午5:40:25 * @version V1.0 */ @Service("TokenService") public class TokenService { public String getToken(User user) { Date start = new Date(); long currentTime = System.currentTimeMillis() + 60* 60 * 1000;//一小时有效时间 Date end = new Date(currentTime); String token = ""; token = JWT.create().withAudience(user.getId()).withIssuedAt(start).withExpiresAt(end) .sign(Algorithm.HMAC256(user.getPassword())); return token; } }
3.6 新建一个工具类 用户从token中取出用户Id
1 /* 2 * @author MRC 3 * @date 2019年4月5日 下午1:14:53 4 * @version 1.0 5 */ 6 public class TokenUtil { 7 8 public static String getTokenUserId() { 9 String token = getRequest().getHeader("token");// 从 http 请求头中取出 token 10 String userId = JWT.decode(token).getAudience().get(0); 11 return userId; 12 } 13 14 /** 15 * 获取request 16 * 17 * @return 18 */ 19 public static HttpServletRequest getRequest() { 20 ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder 21 .getRequestAttributes(); 22 return requestAttributes == null ? null : requestAttributes.getRequest(); 23 } 24 }
3.7 新建一个简单的控制器 用于验证
@RestController public class UserApi { @Autowired UserService userService; @Autowired TokenService tokenService; // 登录 @GetMapping("/login") public Object login(User user, HttpServletResponse response) { JSONObject jsonObject = new JSONObject(); User userForBase = new User(); userForBase.setId("1"); userForBase.setPassword("123"); userForBase.setUsername("mrc"); if (!userForBase.getPassword().equals(user.getPassword())) { jsonObject.put("message", "登录失败,密码错误"); return jsonObject; } else { String token = tokenService.getToken(userForBase); jsonObject.put("token", token); Cookie cookie = new Cookie("token", token); cookie.setPath("/"); response.addCookie(cookie); return jsonObject; } } /*** * 这个请求需要验证token才能访问 * * @author: MRC * @date 2019年5月27日 下午5:45:19 * @return String 返回类型 */ @UserLoginToken @GetMapping("/getMessage") public String getMessage() { // 取出token中带的用户id 进行操作 System.out.println(TokenUtil.getTokenUserId()); return "你已通过验证"; } }
3.8 开始测试
## 成功登陆后保存token到前端cookie 以后的请求带上token即可区别是哪个用户的请求!
我们下一个请求在请求的时候带上这个token试试
成功通过验证! 我们看一下后端控制台打印的结果!
打印出带这个token的用户