摘要:簽發的用戶認證超時刷新策略這個模塊分離至項目權限管理系統與前后端分離實踐,感覺那樣太長了找不到重點,分離出來要好點。這樣在有效期過后的時間段內可以申請刷新。
簽發的用戶認證token超時刷新策略
這個模塊分離至項目api權限管理系統與前后端分離實踐,感覺那樣太長了找不到重點,分離出來要好點。
對于登錄的用戶簽發其對應的jwt,我們在jwt設置他的固定有效期時間,在有效期內用戶攜帶jwt訪問沒問題,當過有效期后jwt失效,用戶需要重新登錄獲取新的jwt。這個體驗不太好,好的體驗應該是:活躍的用戶應該在無感知的情況下在jwt失效后獲取到新的jwt,攜帶這個新的jwt進行訪問,而長時間不活躍的用戶應該在jwt失效后需要進行重新的登錄認證。
這里就涉及到了token的超時刷新問題,解決方案看圖:
在簽發有效期為 t 時間的jwt后,把jwt用("JWT-SESSION-"+appId,jwt)的key-value形式存儲到redis中,有效期設置為2倍的 t 。這樣jwt在有效期過后的 t 時間段內可以申請刷新token。
還有個問題是用戶攜帶過期的jwt對后臺請求,在可刷新時間段內返回了新的jwt,應該在用戶無感知的情況下返回請求的內容,而不是接收一個刷新的jwt。我們是不是可以在每次request請求回調的時候判斷返回的是不是刷新jwt,但是判斷是之后我們是否放棄之前的用戶請求,如果不放棄,那是不是應該在最開始的用戶request請求前先保存這個請求,在之后的回調中如果是返回刷新jwt,我們再攜帶這個新的jwt再請求一次保存好的request請求?但對于前端這么大量的不同請求,這樣是不是太麻煩了?
這困擾了我很久哎,直到我用到了angualr的HttpInterceptor哈哈哈哈哈哈哈哈哈哈哈哈哈哈。
angualr的HttpInterceptor就是前端的攔截過濾器,發起請求會攔截處理,接收請求也會攔截處理。最大的好處對每次的原始request他都會完整的保存下來,我們向后臺發生的request是他的clone。next.handle(request.clone)
繼承HttpInterceptor的AuthInterceptor,攔截response判斷是否為refresh token,是則攜帶新token再次發起保存的request:
@Injectable() export class AuthInterceptor implements HttpInterceptor { constructor(private authService: AuthService, private router: Router) {} intercept(req: HttpRequest, next: HttpHandler): Observable > { const authToken = this.authService.getAuthorizationToken(); const uid = this.authService.getUid(); let authReq: any; if (authToken != null && uid != null) { authReq = req.clone({ setHeaders: { "authorization": authToken, "appId": uid } }); } else { authReq = req.clone(); } console.log(authReq); return next.handle(authReq).pipe( mergeMap(event => { // 返回response if (event instanceof HttpResponse) { if (event.status === 200) { // 若返回JWT過期但refresh token未過期,返回新的JWT 狀態碼為1005 if (event.body.meta.code === 1005) { const jwt = event.body.data.jwt; // 更新AuthorizationToken this.authService.updateAuthorizationToken(jwt); // clone request 重新發起請求 // retry(1); authReq = req.clone({ setHeaders: { "authorization": jwt, "appId": uid } }); return next.handle(authReq); } } if (event.status === 404) { // go to 404 html this.router.navigateByUrl("/404"); } if (event.status === 500) { // go to 500 html this.router.navigateByUrl("/500"); } } console.log(event); // 返回正常情況的可觀察對象 return of(event); }), catchError(this.handleError) ); } private handleError(error: HttpErrorResponse) { if (error.error instanceof ErrorEvent) { // A client-side or network error occurred. Handle it accordingly. console.error("An error occurred:", error.error.message); } else { console.error( `Backend returned code ${error.status}, ` + `body was: ${error.error}`); } repeat(1); return new ErrorObservable("親請檢查網絡"); } }
后端簽發jwt時所做的:
/* * * @Description 這里已經在 passwordFilter 進行了登錄認證 * @Param [] 登錄簽發 JWT * @Return java.lang.String */ @ApiOperation(value = "用戶登錄",notes = "POST用戶登錄簽發JWT") @PostMapping("/login") public Message accountLogin(HttpServletRequest request, HttpServletResponse response) { Mapparams = RequestResponseUtil.getRequestParameters(request); String appId = params.get("appId"); // 根據appId獲取其對應所擁有的角色(這里設計為角色對應資源,沒有權限對應資源) String roles = accountService.loadAccountRole(appId); // 時間以秒計算,token有效刷新時間是token有效過期時間的2倍 long refreshPeriodTime = 36000L; String jwt = JsonWebTokenUtil.issueJWT(UUID.randomUUID().toString(),appId, "token-server",refreshPeriodTime >> 2,roles,null, SignatureAlgorithm.HS512); // 將簽發的JWT存儲到Redis: {JWT-SESSION-{appID} , jwt} redisTemplate.opsForValue().set("JWT-SESSION-"+appId,jwt,refreshPeriodTime, TimeUnit.SECONDS); AuthUser authUser = userService.getUserByAppId(appId); return new Message().ok(1003,"issue jwt success").addData("jwt",jwt).addData("user",authUser); }
后端refresh token時所做的:
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) throws Exception { Subject subject = getSubject(servletRequest,servletResponse); // 判斷是否為JWT認證請求 if ((null == subject || !subject.isAuthenticated()) && isJwtSubmission(servletRequest)) { AuthenticationToken token = createJwtToken(servletRequest); try { subject.login(token); // return this.checkRoles(subject,mappedValue) && this.checkPerms(subject,mappedValue); return this.checkRoles(subject,mappedValue); }catch (AuthenticationException e) { LOGGER.info(e.getMessage(),e); // 如果是JWT過期 if (e.getMessage().equals("expiredJwt")) { // 這里初始方案先拋出令牌過期,之后設計為在Redis中查詢當前appId對應令牌,其設置的過期時間是JWT的兩倍,此作為JWT的refresh時間 // 當JWT的有效時間過期后,查詢其refresh時間,refresh時間有效即重新派發新的JWT給客戶端, // refresh也過期則告知客戶端JWT時間過期重新認證 // 當存儲在redis的JWT沒有過期,即refresh time 沒有過期 String appId = WebUtils.toHttp(servletRequest).getHeader("appId"); String jwt = WebUtils.toHttp(servletRequest).getHeader("authorization"); String refreshJwt = redisTemplate.opsForValue().get("JWT-SESSION-"+appId); if (null != refreshJwt && refreshJwt.equals(jwt)) { // 重新申請新的JWT // 根據appId獲取其對應所擁有的角色(這里設計為角色對應資源,沒有權限對應資源) String roles = accountService.loadAccountRole(appId); long refreshPeriodTime = 36000L; //seconds為單位,10 hours String newJwt = JsonWebTokenUtil.issueJWT(UUID.randomUUID().toString(),appId, "token-server",refreshPeriodTime >> 2,roles,null, SignatureAlgorithm.HS512); // 將簽發的JWT存儲到Redis: {JWT-SESSION-{appID} , jwt} redisTemplate.opsForValue().set("JWT-SESSION-"+appId,newJwt,refreshPeriodTime, TimeUnit.SECONDS); Message message = new Message().ok(1005,"new jwt").addData("jwt",newJwt); RequestResponseUtil.responseWrite(JSON.toJSONString(message),servletResponse); return false; }else { // jwt時間失效過期,jwt refresh time失效 返回jwt過期客戶端重新登錄 Message message = new Message().error(1006,"expired jwt"); RequestResponseUtil.responseWrite(JSON.toJSONString(message),servletResponse); return false; } } // 其他的判斷為JWT錯誤無效 Message message = new Message().error(1007,"error Jwt"); RequestResponseUtil.responseWrite(JSON.toJSONString(message),servletResponse); return false; }catch (Exception e) { // 其他錯誤 LOGGER.warn(servletRequest.getRemoteAddr()+"JWT認證"+e.getMessage(),e); // 告知客戶端JWT錯誤1005,需重新登錄申請jwt Message message = new Message().error(1007,"error jwt"); RequestResponseUtil.responseWrite(JSON.toJSONString(message),servletResponse); return false; } }else { // 請求未攜帶jwt 判斷為無效請求 Message message = new Message().error(1111,"error request"); RequestResponseUtil.responseWrite(JSON.toJSONString(message),servletResponse); return false; } }
github:
bootshiro
usthe
碼云:
bootshiro
usthe
持續更新。。。。。。
分享一波阿里云代金券快速上云
轉載請注明 from tomsun28
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69182.html
摘要:實現目標延長過期時間活躍用戶在過期時,在用戶無感知的情況下動態刷新,做到一直在線狀態不活躍用戶在過期時,直接定向到登錄頁登錄返回字段如何簽發,請看上一篇推文,這里不做過多介紹。如果你有更好的做法,歡迎留言告知我,謝謝啦。 前言 記錄一下前后端分離下————token超時刷新策略! 需求場景 昨天發了一篇記錄 前后端分離應用——用戶信息傳遞 中介紹了token認證機制,跟幾位群友討論了...
摘要:自己在前后端分離上的實踐要想實現完整的前后端分離,安全這塊是繞不開的,這個系統主要功能就是動態管理,這次實踐包含兩個模塊基于搭建的權限管理系統后臺編寫的前端管理。 自己在前后端分離上的實踐 要想實現完整的前后端分離,安全這塊是繞不開的,這個系統主要功能就是動態restful api管理,這次實踐包含兩個模塊,基于springBoot + shiro搭建的權限管理系統后臺bootshir...
摘要:自己在前后端分離上的實踐要想實現完整的前后端分離,安全這塊是繞不開的,這個系統主要功能就是動態管理,這次實踐包含兩個模塊基于搭建的權限管理系統后臺編寫的前端管理。 自己在前后端分離上的實踐 要想實現完整的前后端分離,安全這塊是繞不開的,這個系統主要功能就是動態restful api管理,這次實踐包含兩個模塊,基于springBoot + shiro搭建的權限管理系統后臺bootshir...
閱讀 769·2021-11-23 09:51
閱讀 835·2021-11-23 09:51
閱讀 2503·2021-11-15 18:01
閱讀 3862·2021-10-11 11:07
閱讀 2396·2021-09-22 15:30
閱讀 1075·2021-09-22 14:59
閱讀 1557·2019-08-30 15:55
閱讀 1753·2019-08-30 15:52