上一篇中介紹了 JWT(JSON WEB TOKEN),本篇則寫一下在工程中如何使用 JWT。
我們可以在官網看到有很多倉庫已經封裝好了 JWT,這裏我們選擇了第一個 java-jwt
<!-- JSONWEBTOKEN -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.1</version>
</dependency>
回憶一下 JWT 的介紹篇,第一步:在驗證用戶(登錄)的位置給用戶簽發 token,第二步:在用戶訪問需要驗證權限的資源(數據)的時候驗證 token
簽發 token
簽發 token 一般選擇在驗證用戶信息的時候,有時候一些關鍵操作需要單獨簽發 token 來驗證,比如用戶在操作一些涉及資源的銷燬,支付時需要單獨驗證用戶信息簽發一次性 token。
// 授權請求/響應頭字段
public static final String HEADER_AUTHORIZATION = "Authorization";
// secret 一定不能讓別人知道
public static final String JWT_SECRET = "YebNZYFXAL1qUjX8516Mi";
// 簽發人
public static final String JWT_ISSUER = "auth0";
/**
* 登錄
*/
@PostMapping("/login")
public Rs login() {
String username = requireStringParam("username");
String password = requireStringParam("password");
User checkByName = userService.findByName(username);
if(null == checkByName) {
throw new InvokeException(Rs.ERROR_CODE_BIZ, "該賬戶還未註冊,請先註冊!");
}
User user = new User();
user.setUserName(username);
user.setPassword(DigestUtils.md5DigestAsHex(password.getBytes()));
User loginUser = userService.login(user);
if(null == loginUser) {
// 這裏可以做一個錯誤次數放在 session 或者內存中,後面方便做登錄限制
throw new InvokeException(Rs.ERROR_CODE_UNAUTHORIZED, "用戶名或密碼錯誤。");
}
// 生成 token 信息
Algorithm algorithm = Algorithm.HMAC256(JWT_SECRET);
Date expireTime = new Date();
expireTime.setTime(expireTime.getTime() + 1000*60*30); // 半小時
String token = JWT.create()
.withIssuer(JWT_ISSUER) // 簽發人
.withExpiresAt(expireTime) // 過期時間
.withClaim("username", loginUser.getUserName()) // 當前登錄用戶名
.withClaim("id", loginUser.getId()) // 用戶編號
.sign(algorithm);
// 將簽發好的 token 放到響應頭中,當然也可放到 Cookie 或者響應對象中。
response.setHeader(HEADER_AUTHORIZATION, "Bearer " + token);
return Rs.ok(loginUser, "登錄成功");
}
當訪問這個登錄接口成功後,響應頭中就會有了 Authorization
字段了。
驗證 token
/**
* test AuthorizationInterceptor
*/
@RequestMapping(value = "/auth", method = RequestMethod.GET)
@ResponseBody
Rs auth() {
String authorization = request.getHeader(HEADER_AUTHORIZATION);
if(StringHelper.isEmpty(authorization)) {
throw new InvokeException(Rs.ERROR_CODE_UNAUTHORIZED, "no authorization.");
}
String token = authorization.replace("Bearer ", "");
Algorithm algorithm = Algorithm.HMAC256(JWT_SECRET);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(Constans.JWT_ISSUER)
.build(); //Reusable verifier instance
// 這裏的 verify(token) 會驗證 token 的簽名對不對,同時也會驗證 token 是否過期
DecodedJWT jwt = verifier.verify(token);
String username = jwt.getClaim("username").asString();
String id = jwt.getClaim("id").asString();
logger.info("[當前用戶]" + username);
return Rs.ok("授權通過!");
}
順便更新一下全局異常捕獲類
/**
* 異常捕獲處理類
*/
@ControllerAdvice
public class WebExceptionHandler {
private Logger logger = LoggerFactory.getLogger(WebExceptionHandler.class);
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Rs errorHandler(HttpServletRequest req, HttpServletResponse res, Exception e) {
logger.error("", e);
if (e instanceof InvokeException) {
InvokeException ex = (InvokeException)e;
return Rs.err(ex.getErr(), ex.getMessage());
} else if (e instanceof TokenExpiredException) {
res.setHeader(Constans.HEADER_AUTHORIZATION, null);
return Rs.err(Rs.ERROR_CODE_AUTHORIZED_TIMEOUT, "會話已過期,請重新登錄!");
} else if (e instanceof JWTVerificationException) {
return Rs.err(Rs.ERROR_CODE_UNAUTHORIZED, "驗證授權出錯!");
}
return Rs.errMsg("系統錯誤");
}
}
驗證用戶 token 是一個公用的步驟,所以這一 部分的代碼應該寫到攔截器中,這裏爲了方便看代碼直接寫到業務方法中了。在代碼廠庫中的實現方式是寫在攔截器中的,感興趣的童靴可以看一下: