JWT實戰


JWT實戰

JWT認證流程

先來回顧下JWT的流程,jwt是存儲在客戶端的,服務器不需要存儲jwt;客戶端每次發送請求時攜帶token,然后到服務端驗證token是否正確,是否過期,然后解碼出攜帶的用戶信息。

存在的問題 
1、Token失效問題: 
比如在瀏覽器端通過用戶名/密碼驗證獲得簽名的Token被木馬竊取。即使用戶登出了系統,黑客還是可以利用竊取的Token模擬正常請求可用它訪問服務器,而服務器端對此完全不知道,(因為JWT機制是無狀態的),直到過期,中間服務器無法控制它.

2、 app類Token的有效時間

    token的有效時間:

        1. 如果 app 是新聞類/游戲類/聊天類等需要長時間用戶粘性的. 一般可以設置1年的有效時間!

        2. 如果 app 是 支付類/銀行類的. 一般token只得有效時間比較短: 15分鍾左右!
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

我們的解決方法是: 
服務端使用Redis緩存服務器簽發給已登錄用戶的Token, 
每次客戶端發送請求時到redis中查該用戶 請求的token 和 redis存的token是否一致,不一致不允許token登錄, 
如果一致,判斷這個token是否可以用(主要防止修改密碼和注銷操作的token沒失效問題) 
最后返回用戶信息 
當用戶修改密碼和注銷時直接將redis中該用戶的Token設置失效。下次通過token登錄,會提醒token失效,要重新登錄,我們重新生成一個新的token給用戶。通過redis存儲token,實現主動控制 token過期失效的問題了。

封裝的JWT工具類 部分代碼 
生成Token碼

  /** * 生成jwt token * @param userId * @param expireTime 過期時間戳 * @return */ public static String makeToken(String userId,Date expireTime){ long nowMillis = System.currentTimeMillis(); Date now=new Date(nowMillis);//簽發時間精度:毫秒 try { Algorithm algorithm = Algorithm.HMAC256(MyConstance.JWT_SECRET); return JWT.create().withIssuer(userId).withIssuedAt(now).withExpiresAt(expireTime).sign(algorithm); } catch (UnsupportedEncodingException exception){ exception.printStackTrace(); } catch (JWTCreationException exception){ exception.printStackTrace(); } return null; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

解碼和驗證Token碼


    /** * 校驗toekn是否有效 * @param userId * @param token * @return */ public static boolean verifyToken(String userId,String token){ boolean active = true; try { Algorithm algorithm = Algorithm.HMAC256(MyConstance.JWT_SECRET);//聲明簽名所用的算法和秘鑰 JWTVerifier verifier = JWT.require(algorithm).withIssuer(userId).build(); verifier.verify(token); } catch (TokenExpiredException exception){ //System.out.println("--- token 過期"); active = false; } catch (JWTDecodeException exception){ //System.out.println("--- token 無效"); active = false; } catch (UnsupportedEncodingException exception){ //System.out.println("--- token 無效"); active = false; } catch (JWTVerificationException exception){ //System.out.println("--- token 錯誤"); active = false; } return active; } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

登錄

@ApiOperation(value = "登錄") @RequestMapping(value = "/loginWEB", method = RequestMethod.POST) public ResponseEntity<BaseResult> loginWEB(HttpServletRequest request, HttpServletResponse response, @ApiParam("用戶名") @RequestParam(value = "username", required = false) String username, @ApiParam("密碼") @RequestParam(value = "password", required = false) String password) { XkbbUser user=userService.getBy("username", username); //賬號密碼校驗業務 if(user==null) { return buildFailedInfo(ApiConstance.USER_NOT_EXIST); } if(!password.equals(user.getPassword())) { return buildFailedInfo(ApiConstance.PASSWORD_ERROR); } //獲取用戶id String userId=user.getId(); //設置token過期時間為30天之后 Date expireTime = Tool.datePlu(new Date(), 30); //生成JWT(JSon Web Token) String token=TokenUtil.makeToken(userId, expireTime); //更新緩存里面該用戶的token: // 如果已登錄,則使其Token失效 TokenUtil.updateTokenAPP(userId, token); Map<String, Object> map = new HashMap<String, Object>(); map.put("token", token);//服務器生成的token return buildSuccessInfo(map); } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

MyConstance類部分全局常量定義 
/** 
* token的緩存map名 
*/ 
public static final String KEY_TOKEN_MAP = “key_token_map”; 
/** 
* JWT生成token時加密用的secret 
*/ 
public static final String JWT_SECRET = “3MZq0BYyGcXYoXjhS4QbAM+2YdlLCwKRr2gvVJOJ”;

/** * 用戶登錄校驗 * @param userId * @param token * @return */ //到redis中查該用戶 請求的token 和 redis存的token是否一致,不一致不允許token登錄, //再次根據創建時間,判斷這個token是否可以用(主要防止出現修改密碼和注銷操作的token沒失效問題) //失效則Redis中刪除該token public static boolean isLogin(String userId,String token){ if(Tool.isNotBlank(userId)) { if(Tool.isNotBlank(token)) { //到redis中查該用戶 請求的token 和 redis存的token是否一致,不一致不允許token登錄 String existValue = (String) CacheUtil.hget(MyConstance.KEY_TOKEN_MAP, userId); //System.out.println("existValue:"+existValue); if(Tool.isNotBlank(existValue) && existValue.equals(token)) { //再次根據創建時間,判斷這個token是否可有效(主要防止出現修改密碼和注銷操作的token沒失效問題) boolean isLogin = verifyToken(userId,token); //失效則Redis中刪除該token if(!isLogin){ CacheUtil.hdel(MyConstance.KEY_TOKEN_MAP,userId); } return isLogin; } } } return false; } /** * 清除用戶緩存token * @param userId * @return */ public static String clearToken(String userId){ CacheUtil.hdel(MyConstance.KEY_TOKEN_MAP, userId); } /** * 更新用戶token * @param userId * @param value */ public static void updateToken(String userId,String value){ clearToken(userId);//清除緩存中舊的Token CacheUtil.hset(MyConstance.KEY_TOKEN_MAP, userId, value);//緩存新的Token }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM