使用JWT Token進行RestFul API許可權驗證
阿新 • • 發佈:2018-12-25
問題
後端提供的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簡易許可權驗證模組就基本完成了。