摘要:簽發(fā)的用戶認(rèn)證超時(shí)刷新策略這個(gè)模塊分離至項(xiàng)目權(quán)限管理系統(tǒng)與前后端分離實(shí)踐,感覺那樣太長了找不到重點(diǎn),分離出來要好點(diǎn)。這樣在有效期過后的時(shí)間段內(nèi)可以申請(qǐng)刷新。
簽發(fā)的用戶認(rèn)證token超時(shí)刷新策略
這個(gè)模塊分離至項(xiàng)目api權(quán)限管理系統(tǒng)與前后端分離實(shí)踐,感覺那樣太長了找不到重點(diǎn),分離出來要好點(diǎn)。
對(duì)于登錄的用戶簽發(fā)其對(duì)應(yīng)的jwt,我們?cè)趈wt設(shè)置他的固定有效期時(shí)間,在有效期內(nèi)用戶攜帶jwt訪問沒問題,當(dāng)過有效期后jwt失效,用戶需要重新登錄獲取新的jwt。這個(gè)體驗(yàn)不太好,好的體驗(yàn)應(yīng)該是:活躍的用戶應(yīng)該在無感知的情況下在jwt失效后獲取到新的jwt,攜帶這個(gè)新的jwt進(jìn)行訪問,而長時(shí)間不活躍的用戶應(yīng)該在jwt失效后需要進(jìn)行重新的登錄認(rèn)證。
這里就涉及到了token的超時(shí)刷新問題,解決方案看圖:
在簽發(fā)有效期為 t 時(shí)間的jwt后,把jwt用("JWT-SESSION-"+appId,jwt)的key-value形式存儲(chǔ)到redis中,有效期設(shè)置為2倍的 t 。這樣jwt在有效期過后的 t 時(shí)間段內(nèi)可以申請(qǐng)刷新token。
還有個(gè)問題是用戶攜帶過期的jwt對(duì)后臺(tái)請(qǐng)求,在可刷新時(shí)間段內(nèi)返回了新的jwt,應(yīng)該在用戶無感知的情況下返回請(qǐng)求的內(nèi)容,而不是接收一個(gè)刷新的jwt。我們是不是可以在每次request請(qǐng)求回調(diào)的時(shí)候判斷返回的是不是刷新jwt,但是判斷是之后我們是否放棄之前的用戶請(qǐng)求,如果不放棄,那是不是應(yīng)該在最開始的用戶request請(qǐng)求前先保存這個(gè)請(qǐng)求,在之后的回調(diào)中如果是返回刷新jwt,我們?cè)贁y帶這個(gè)新的jwt再請(qǐng)求一次保存好的request請(qǐng)求?但對(duì)于前端這么大量的不同請(qǐng)求,這樣是不是太麻煩了?
這困擾了我很久哎,直到我用到了angualr的HttpInterceptor哈哈哈哈哈哈哈哈哈哈哈哈哈哈。
angualr的HttpInterceptor就是前端的攔截過濾器,發(fā)起請(qǐng)求會(huì)攔截處理,接收請(qǐng)求也會(huì)攔截處理。最大的好處對(duì)每次的原始request他都會(huì)完整的保存下來,我們向后臺(tái)發(fā)生的request是他的clone。next.handle(request.clone)
繼承HttpInterceptor的AuthInterceptor,攔截response判斷是否為refresh token,是則攜帶新token再次發(fā)起保存的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 狀態(tài)碼為1005 if (event.body.meta.code === 1005) { const jwt = event.body.data.jwt; // 更新AuthorizationToken this.authService.updateAuthorizationToken(jwt); // clone request 重新發(fā)起請(qǐng)求 // 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); // 返回正常情況的可觀察對(duì)象 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("親請(qǐng)檢查網(wǎng)絡(luò)"); } }
后端簽發(fā)jwt時(shí)所做的:
/* * * @Description 這里已經(jīng)在 passwordFilter 進(jìn)行了登錄認(rèn)證 * @Param [] 登錄簽發(fā) JWT * @Return java.lang.String */ @ApiOperation(value = "用戶登錄",notes = "POST用戶登錄簽發(fā)JWT") @PostMapping("/login") public Message accountLogin(HttpServletRequest request, HttpServletResponse response) { Mapparams = RequestResponseUtil.getRequestParameters(request); String appId = params.get("appId"); // 根據(jù)appId獲取其對(duì)應(yīng)所擁有的角色(這里設(shè)計(jì)為角色對(duì)應(yīng)資源,沒有權(quán)限對(duì)應(yīng)資源) String roles = accountService.loadAccountRole(appId); // 時(shí)間以秒計(jì)算,token有效刷新時(shí)間是token有效過期時(shí)間的2倍 long refreshPeriodTime = 36000L; String jwt = JsonWebTokenUtil.issueJWT(UUID.randomUUID().toString(),appId, "token-server",refreshPeriodTime >> 2,roles,null, SignatureAlgorithm.HS512); // 將簽發(fā)的JWT存儲(chǔ)到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時(shí)所做的:
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) throws Exception { Subject subject = getSubject(servletRequest,servletResponse); // 判斷是否為JWT認(rèn)證請(qǐng)求 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")) { // 這里初始方案先拋出令牌過期,之后設(shè)計(jì)為在Redis中查詢當(dāng)前appId對(duì)應(yīng)令牌,其設(shè)置的過期時(shí)間是JWT的兩倍,此作為JWT的refresh時(shí)間 // 當(dāng)JWT的有效時(shí)間過期后,查詢其refresh時(shí)間,refresh時(shí)間有效即重新派發(fā)新的JWT給客戶端, // refresh也過期則告知客戶端JWT時(shí)間過期重新認(rèn)證 // 當(dāng)存儲(chǔ)在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)) { // 重新申請(qǐng)新的JWT // 根據(jù)appId獲取其對(duì)應(yīng)所擁有的角色(這里設(shè)計(jì)為角色對(duì)應(yīng)資源,沒有權(quán)限對(duì)應(yīng)資源) 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); // 將簽發(fā)的JWT存儲(chǔ)到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時(shí)間失效過期,jwt refresh time失效 返回jwt過期客戶端重新登錄 Message message = new Message().error(1006,"expired jwt"); RequestResponseUtil.responseWrite(JSON.toJSONString(message),servletResponse); return false; } } // 其他的判斷為JWT錯(cuò)誤無效 Message message = new Message().error(1007,"error Jwt"); RequestResponseUtil.responseWrite(JSON.toJSONString(message),servletResponse); return false; }catch (Exception e) { // 其他錯(cuò)誤 LOGGER.warn(servletRequest.getRemoteAddr()+"JWT認(rèn)證"+e.getMessage(),e); // 告知客戶端JWT錯(cuò)誤1005,需重新登錄申請(qǐng)jwt Message message = new Message().error(1007,"error jwt"); RequestResponseUtil.responseWrite(JSON.toJSONString(message),servletResponse); return false; } }else { // 請(qǐng)求未攜帶jwt 判斷為無效請(qǐng)求 Message message = new Message().error(1111,"error request"); RequestResponseUtil.responseWrite(JSON.toJSONString(message),servletResponse); return false; } }
github:
bootshiro
usthe
碼云:
bootshiro
usthe
持續(xù)更新。。。。。。
分享一波阿里云代金券快速上云
轉(zhuǎn)載請(qǐng)注明 from tomsun28
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/69182.html
摘要:實(shí)現(xiàn)目標(biāo)延長過期時(shí)間活躍用戶在過期時(shí),在用戶無感知的情況下動(dòng)態(tài)刷新,做到一直在線狀態(tài)不活躍用戶在過期時(shí),直接定向到登錄頁登錄返回字段如何簽發(fā),請(qǐng)看上一篇推文,這里不做過多介紹。如果你有更好的做法,歡迎留言告知我,謝謝啦。 前言 記錄一下前后端分離下————token超時(shí)刷新策略! 需求場景 昨天發(fā)了一篇記錄 前后端分離應(yīng)用——用戶信息傳遞 中介紹了token認(rèn)證機(jī)制,跟幾位群友討論了...
摘要:自己在前后端分離上的實(shí)踐要想實(shí)現(xiàn)完整的前后端分離,安全這塊是繞不開的,這個(gè)系統(tǒng)主要功能就是動(dòng)態(tài)管理,這次實(shí)踐包含兩個(gè)模塊基于搭建的權(quán)限管理系統(tǒng)后臺(tái)編寫的前端管理。 自己在前后端分離上的實(shí)踐 要想實(shí)現(xiàn)完整的前后端分離,安全這塊是繞不開的,這個(gè)系統(tǒng)主要功能就是動(dòng)態(tài)restful api管理,這次實(shí)踐包含兩個(gè)模塊,基于springBoot + shiro搭建的權(quán)限管理系統(tǒng)后臺(tái)bootshir...
摘要:自己在前后端分離上的實(shí)踐要想實(shí)現(xiàn)完整的前后端分離,安全這塊是繞不開的,這個(gè)系統(tǒng)主要功能就是動(dòng)態(tài)管理,這次實(shí)踐包含兩個(gè)模塊基于搭建的權(quán)限管理系統(tǒng)后臺(tái)編寫的前端管理。 自己在前后端分離上的實(shí)踐 要想實(shí)現(xiàn)完整的前后端分離,安全這塊是繞不開的,這個(gè)系統(tǒng)主要功能就是動(dòng)態(tài)restful api管理,這次實(shí)踐包含兩個(gè)模塊,基于springBoot + shiro搭建的權(quán)限管理系統(tǒng)后臺(tái)bootshir...
閱讀 783·2021-11-23 09:51
閱讀 848·2021-11-23 09:51
閱讀 2518·2021-11-15 18:01
閱讀 3877·2021-10-11 11:07
閱讀 2416·2021-09-22 15:30
閱讀 1087·2021-09-22 14:59
閱讀 1568·2019-08-30 15:55
閱讀 1764·2019-08-30 15:52