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的用戶
DEMO測試版本:https://gitee.com/mrc1999/springbootToken
參考博客:https://www.jianshu.com/p/310d307e44c6