1. 程式人生 > >使用JWT Token進行RestFul API許可權驗證

使用JWT Token進行RestFul API許可權驗證

問題

後端提供的RestFul API一般都需要進行許可權驗證,而Web框架一般採用Cookies+Session來進行認證。但RestFul API屬於無狀態協議,而在後臺使用Session的話,由於Session本身需要服務端進行維持,這樣就破壞了Rest的無狀態性。

解決方案

拋棄Cookie+Session的認證架構,選用Token作為認證手段。在登入驗證時,後臺收集賬號資訊、IP、訪問時間等資訊,生成對應Token,通過cookies傳回客戶端。之後的每次請求API,都需要帶上Token;後臺對Token進行解碼與鑑權操作。這樣就可以在服務端本身不維護登入狀態的情況下,進行許可權認證,不會破壞RestFul的特性。

Java Web 示例

由於Web後臺框架是SpringMVC,這裡選擇使用JWT作為Token生成工具。

編寫一個Token生成工具類,完成兩個方法:Token生成以及Token驗證。

package com.example.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;

import
java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * 用於JWT Token的生成與解碼驗證 */ public class JwtTokenUtils { // 加密用的私鑰 private static String TOKEN_KEY = "keys"; /** * 生成返回給客戶端的token * @param username 登入使用者名稱 * @return token * @throws
Exception */
public static String createToken(String username) throws Exception { // Token產生時間點 Date startDate = new Date(); // 設定過期時間 Calendar now = Calendar.getInstance(); now.add(Calendar.HOUR, 24); Date expireDate = now.getTime(); Map<String, Object> map = new HashMap<String, Object>(); map.put("alg", "HS256"); map.put("typ", "JWT"); String token = JWT.create() .withHeader(map) .withClaim("username", username) .withExpiresAt(expireDate) // 過期時間 .withIssuedAt(startDate) // 簽發時間 .sign(Algorithm.HMAC256(TOKEN_KEY)); // 加密簽名演算法 return token; } /** * 驗證客戶端token合法性 * @param token * @return 解碼token,獲得的claim對 * @throws Exception */ public static Map<String, Claim> vertifyToken(String token) throws Exception { JWTVerifier verifier = JWT.require(Algorithm.HMAC256(TOKEN_KEY)).build(); DecodedJWT jwt = null; try { jwt = verifier.verify(token); } catch (Exception e) { throw new RuntimeException("token無效"); } return jwt.getClaims(); } }

這裡採用的是自定義私鑰以及使用者名稱進行混合加密,有需要可以新增IP等其他資訊。

登入驗證後,生成Token,通過Cookies返回給客戶端:

try {
    String token = JwtTokenUtils.createToken(username);
    Cookie cookie = new Cookie("token", token);
    response.addCookie(cookie);
} catch (Exception e) {
    e.printStackTrace();
    logger.error("create token error : " + username);
}

注意,使用者登出後,需要使Token失效。

自定義攔截器,對需要許可權控制的API進行攔截,驗證Token:

package com.example.interceptor;


import com.auth0.jwt.interfaces.Claim;
import com.example.service.IManagerService;
import com.example.utils.JwtTokenUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

/**
 * Access Token 攔截器
 * 驗證Api許可權
 */
@Component
public class VerifyInterceptor extends HandlerInterceptorAdapter {

    private static final Logger logger = Logger.getLogger(VerifyInterceptor.class);

    @Autowired
    private IManagerService managerService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.debug("Access token executing...");

        // 標記,用於最後返回值
        boolean flag = false;

        // 從請求報文cookies中獲取token
        String token = null;
        Cookie[] cookies = request.getCookies();
        // 結果非空時,從陣列中查詢名為token的cookie
        if (null != cookies) {
            for (Cookie cookie : cookies) {
                if ("token".equals(cookie.getName())) {
                    token = cookie.getValue();
                    break;
                }
            }
        }

        // 開發時無需token,測試or部署時需要改成false
        if (null == token || "".equals(token)) {
            System.out.println("empty token");
            flag = true;
        } else {
            try {
                Map<String, Claim> map = JwtTokenUtils.vertifyToken(token);
                String name = map.get("username").asString();
                flag = managerService.isUsernameExist(name);
            } catch (RuntimeException e) {
                e.printStackTrace();
                System.out.println("許可權認證失敗");
                logger.debug("許可權認證失敗");
            }
        }
        if (!flag) {    // 驗證失敗時設定response錯誤碼
            response.setStatus(401);
        }
        return flag;
    }
}

這樣我們的RestFul API簡易許可權驗證模組就基本完成了。