SpringBoot 雜記——使用 JWT 做用戶認證(八)

上一篇中介紹了 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 字段了。

jwt

驗證 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("系統錯誤");
	}
}

jwt

驗證用戶 token 是一個公用的步驟,所以這一 部分的代碼應該寫到攔截器中,這裏爲了方便看代碼直接寫到業務方法中了。在代碼廠庫中的實現方式是寫在攔截器中的,感興趣的童靴可以看一下:

GitHub

PlayWithSpringBoot

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章