實現基於JWT的Token登入驗證功能

Keen King發表於2019-01-20

前言

放假之前做了幾個小專案+課設,都用到了token實現登入驗證和許可權判斷,然鵝當時和同組的小夥伴也都是第一次接觸到了token,於是乎都是一臉懵逼(xjbx)的寫完了登入驗證的前後端邏輯(我寫前端,同組的小夥伴寫後端)。今天有空仔細學習了一下SpringBoot實現token認證以及和前端的互動,踩了不少坑,在這裡記錄一下

後端實現

  • 首先需要匯入jwt的包,相關的pom.xml檔案如下:
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.5.0</version>
</dependency>
複製程式碼
  • 然後開始編寫TokenUtil類,首先定義token的過期時間和私鑰
private static final long EXPIRE_TIME = 15 * 60 * 1000;
private static final String TOKEN_SECRET = "thefirsttoken123";
複製程式碼
  • 實現簽名方法: 這裡不應該使用密碼進行加密,不安全,但是是自己的小demo就這樣寫了。
/**
 * 生成簽名,15分鐘過期
 * @param **username**
* @param **password**
* @return
 */
public static String sign(String username, String password) {
    try {
        // 設定過期時間
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        // 私鑰和加密演算法
        Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
        // 設定頭部資訊
        Map<String, Object> header = new HashMap<>(2);
        header.put("Type", "Jwt");
        header.put("alg", "HS256");
        // 返回token字串
        return JWT.create()
                .withHeader(header)
                .withClaim("loginName", username)
                .withClaim("pwd", password)
                .withExpiresAt(date)
                .sign(algorithm);
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}
複製程式碼
  • 實現token的檢驗方法:
/**
 * 檢驗token是否正確
 * @param **token**
* @return
 */
public static boolean verify(String token){
    try {
        Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
        JWTVerifier verifier = JWT.require(algorithm).build();
        DecodedJWT jwt = verifier.verify(token);
        return true;
    } catch (Exception e){
        return false;
    }
}
複製程式碼

至此,工具類就編寫完成啦!

  • 登入的controller層方法 這裡獲取到前端傳送過來的請求體,取出其中的使用者名稱和密碼,和資料庫比對如果無誤的話,簽發token,並返回給前端。 (API響應結果還沒有封裝,看著有點亂,嘿嘿)
@PostMapping(value = "/login")
public Map<String, Object> login(@RequestBody SysUser sysUser){
    Map<String, Object> map = new HashMap<>();
    String username = sysUser.getUsername();
    String password = sysUser.getPassword();
    if (sysUserService.login(username, password)){
        String token = TokenUtil.sign(username,password);
        if (token != null){
            map.put("code", "10000");
            map.put("message","認證成功");
            map.put("token", token);
            return map;
        }
    }
    map.put("code", "00000");
    map.put("message","認證失敗");
    return map;
}
複製程式碼

現在服務端給客戶端簽發token的功能已經差不多實現了。 那麼客戶端如何將token應用到以後的請求中,服務端又如何識別token呢?

  • 實現服務端自定義攔截器
/**
 * 自定義token攔截器
 */
@Component
public class TokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        if (request.getMethod().equals("OPTIONS")){
            response.setStatus(HttpServletResponse.SC_OK);
            return true;
        }
        response.setCharacterEncoding("utf-8");
        String token = request.getHeader("admin-token");
        if (token != null){
            boolean result = TokenUtil.verify(token);
            if(result){
                System.out.println("通過攔截器");
                return true;
            }
        }
        System.out.println("認證失敗");
        response.getWriter().write("50000");
        return false;
    }
}
複製程式碼

TokenInterceptor實現了HandlerInterceptor介面,重寫了preHandle方法,該方法是在每個請求之前觸發執行,從request的頭裡面取出token,這裡我們統一了存放token的鍵為admin-token,驗證通過,放行,驗證不通過,返回認證失敗資訊。 這裡有一個坑,由於使用axios,每次前端傳送請求,都會先發一次預請求,也就是RequestMethod為OPTIONS不是我們常見的get、post等(關於OPTIONS的解釋,可以谷歌一下)。所有在這裡我們需要做一次判斷,如果請求方法為OPTIONS,就直接return通過。

  • 配置攔截器 對登入介面的請求不攔截
@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {
    private TokenInterceptor tokenInterceptor;

    public InterceptorConfig(TokenInterceptor tokenInterceptor) {
        this.tokenInterceptor = tokenInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        List<String> excludePath = new ArrayList<>();
        String sysUserLogin = "/api/sysUser/login";
        excludePath.add(sysUserLogin);
        registry.addInterceptor(tokenInterceptor).excludePathPatterns(excludePath);
    }
}
複製程式碼
  • 服務端解析token 現在為了之後根據token去做相關的查詢,我們需要對token進行解密,取出之前加密的loginName。然後就可以愉快的增刪查改啦~
/**
 * 從token中獲取username資訊
 * @param **token**
* @return
 */
public static String getUserName(String token){
    try {
        DecodedJWT jwt = JWT.decode(token);
        return jwt.getClaim("loginName").asString();
    } catch (JWTDecodeException e){
        e.printStackTrace();
        return null;
    }
}
複製程式碼

前端實現

前端使用vue+axios,主要是實現對axios的再封裝。 相關程式碼如下 大致邏輯就是,如果vuex中已經存在了token,那麼就把它放到請求頭中發往服務端。

// 建立axios例項
const service = axios.create({
  baseURL: process.env.BASE_API // api 的 base_url
})

// request攔截器
service.interceptors.request.use(
  config => {
    if (store.getters.token) {
      config.headers['admin-token'] = getToken() // 讓每個請求攜帶自定義token
    }
    return config
  },
  error => {
    // 出錯
    console.log(error)
    Promise.reject(error)
  }
)
複製程式碼

結語

哇在掘金的第一篇文章寫完了,嘻嘻,繼續努力!!!。

昨日今日明日日日碼不停蹄,
去年今年明年年年都是單身。
橫批:這就是命

相關文章