Springboot token令牌驗證解決方案 在SpringBoot實現基於Token的用戶身份驗證


1.首先了解一下Token


1、token也稱作令牌,由uid+time+sign[+固定參數]組成:

  • uid: 用戶唯一身份標識
  • time: 當前時間的時間戳
  • sign: 簽名, 使用 hash/encrypt 壓縮成定長的十六進制字符串,以防止第三方惡意拼接
  • 固定參數(可選): 將一些常用的固定參數加入到 token 中是為了避免重復查數據庫

 

2.token 驗證的機制(流程)

  1. 用戶登錄校驗,校驗成功后就返回Token給客戶端。
  2. 客戶端收到數據后保存在客戶端
  3. 客戶端每次訪問API是攜帶Token到服務器端。
  4. 服務器端采用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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM