Java設置token有效期的5個應用場景(雙token實現)
token的簡介和生成校驗已經在前面分享過,有需要的小伙伴可以先進行回顧。
前言:
Token最常見的應用場景之一就是身份驗證。在傳統(tǒng)的身份驗證方式中,用戶需要輸入用戶名和密碼才能登錄系統(tǒng),這種方式容易被破解和盜用。而使用token方式進行身份驗證,可以有效防止用戶身份信息被盜用。
當用戶進行身份驗證后,服務器會生成一個token并將其返回給客戶端,客戶端可以使用token來訪問受保護的資源,而不需要重新輸入用戶名和密碼。這種方式不僅可以提高安全性,還可以提高用戶體驗。
場景一:網吧計時
場景分析:
嚴格規(guī)定登陸時長,超時則跳轉登陸頁面,必須重新輸入密碼才能繼續(xù)使用
對token的要求:
登陸成功后,服務器生成的token需要攜帶時間戳(token時間戳=當前時間+有效時長),后臺定制一個有效期時長,在網吧計時收費場景中有效范圍就是可以上機的時長。
每次請求都要校驗token是否過期( 時間戳是否小于現在時間)
token也只用存在瀏覽器緩存即可,減少服務器端的存儲壓力。
實操:
javaWebToken為例:
<!-- 引入jwt --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.8.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.8.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.8.0</version> </dependency>
/** * @description: 生成token * @param: userInfo 用戶手機號和用戶Id * @return: java.lang.String 返回token **/ public static String getToken(String userPhone) { try{ //從當前時間算起,再加上有效時長30分鐘 Date date = new Date(System.currentTimeMillis() + 30*60*1000); //用秘鑰生成簽名 Algorithm algorithm = Algorithm.HMAC256('P1ooisyGFJhgzrctMOofvaHLuiNFOmktedw'); //默認頭部+載荷(手機號/id)+過期日期+簽名=jwt String jwtToken= JWT.create() .withClaim("userPhone", userPhone) .withClaim("userId", "xxxxxxx") .withExpiresAt(date) .sign(algorithm); return jwtToken; }catch (Exception e){ log.error("用戶{}的token生成異常:{}",userPhone,e); return null; } }
驗證token有效期:
// 判斷 token 是否過期 public static String isExpire(String token) { DecodedJWT jwt = JWT.decode(token);//解碼token // 如果token的有效期小于當前時間,則表示已過期,為true boolean isExpire = jwt.getExpiresAt().getTime() < System.currentTimeMillis(); if(isExpire){ return jwt.getClaim("userPhone").asString();//獲取token攜帶的數據 }else{ return null; } }
攔截請求,開始驗證token
public class JwtToken implements AuthenticationToken { /** * JWT的字符token */ private String token; public JwtToken(String token) { this.token = token; } @Override public Object getPrincipal() { return token; } @Override public Object getCredentials() { return token; } } @Component public class ShiroRealm extends AuthorizingRealm { /** * @Title: doGetAuthenticationInfo * @description: 校驗token是否正確 * @param: auth * @return: org.apache.shiro.authc.AuthenticationInfo **/ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException { try{ String token = (String) auth.getCredentials(); String userPhone=JwtUtil.isExpire(token); return new SimpleAuthenticationInfo(userPhone, token, getName()); }catch(Exception e){ throw new AuthenticationException("驗證token失敗"); } } }
總結:
優(yōu)點:
- 嚴格規(guī)定登陸時長,簡單來說就是安全性高。
- 代碼邏輯簡單,token過期了就重定向到登陸頁面,不需要做延時等處理。
缺點:
- 時間一到就跳轉到登陸頁面,對用戶來說非常突然,某種程度上說用戶體驗非常不好。
- 用戶有可能停留在頁面不做任何操作,因此客戶端必須定期主動的給服務器發(fā)請求(或使用消息隊列),以便及時發(fā)現校驗token過期。
場景二: Esxi系統(tǒng)頁面、jumpServer
場景分析:
Esxi系統(tǒng)頁面、jumpServer的web終端頁面等,超過一段時間內不操作(例如0.5小時)自動退出登錄,再想繼續(xù)操作需要重新登錄。
對token的要求:
和場景一類似,區(qū)別是增加了一個判斷:每次請求都會判斷token是否快要過期(例如設置token還有10分鐘過期)。
如果將要過期,則重置token有效期(服務器發(fā)個新token給客戶端);如果已經過期,需要重新登錄。
實操:
重新生成一個新的token,前端收到新的token后把舊token丟棄(前端代碼略)
總結:
優(yōu)點:
- 用戶一直在使用頁面,token就會被一直重置,對活躍用戶友好。
- token有效期短,人離開一段時間就無法繼續(xù)使用軟件,這個設計安全性較高。
- 用戶在token過期后再次操作才會要求再次登陸,也就是說,不需要客戶端時時給服務器發(fā)消息驗證token是否有效,減少網絡開銷。
缺點:
- 如果有效期設計的短,用戶操作也不頻繁的情況下,會導致用戶頻繁登陸,體驗較差。合理設置有效期非常重要。
場景三:微信、支付寶等app
場景分析:
微信、支付寶等手機app,我們一旦安裝并登陸以后,除了涉及資金或信息安全的場景需要輸入一些密碼,基本上我們打開app就能用,不會讓我們重復登陸。
對token的要求:
token有效期設置的很長(3個月、6個月、一年等)
每次請求還是會驗證token有效期,但如果token過期,則發(fā)消息給客戶端。
客戶端收到消息以后,給服務器發(fā)送重置token的請求。
總結:
適用于安全性有保證的場景(例如手機App:手機有鎖屏碼等安全機制,涉及到金錢等重要信息還有其他驗證方式)
優(yōu)點:
- 用戶只需要登陸一次,用戶體驗很好
缺點:
- 客戶端可以發(fā)送重置token的請求,故token一直有效,手機鎖屏被破解,任何人都能使用,也是個安全隱患。
- 只用登陸一次,用戶很有可能忘記密碼,想要避免用戶體驗差,必須綁定手機號,支持驗證碼登陸。
場景四:語雀等pc應用程序(雙token)
場景分析:
場景四和場景二類似,區(qū)別是用戶長時間不使用的情況下才會被強制用戶登錄。
例如“語雀”等應用程序,長時間不使用是會被要求重新登錄的。
我們每個人都安裝過一些使用頻率不高的軟件,這些軟件在產品設計時就決定了用戶的使用頻率和周期,那他們是如何界定“一直在使用的活躍用戶”和“長時間不使用的非活躍用戶”呢?
還是靠設置token有效期,有效期設置的長一些,例如3個月或6個月不使用才算非活躍用戶。
但是如何沿用場景二的token要求,設置有效期長的token,會留下很大的安全隱患:token一旦被黑客截獲后長時間可以被使用,還不會被服務器察覺。
解決方案:雙token
什么是雙token?以現有的短token的基礎上再增加一個長token,形成兩個token校驗有效期的模式。
短token可以防止被截獲后無休止使用,所以還要使用有效期短的token用來驗證有效期。
而新增加的長token,它的有效期用來區(qū)分“活躍用戶”和“非活躍用戶”,用來實現短token過期后,活躍用戶系統(tǒng)自動給重置token,非活躍用戶需要重新登錄。
對token的要求:
用戶登錄成功后,服務器生成一短一長兩個token返回給客戶端,客戶端每次請求服務器攜帶的是短token。
如果服務器發(fā)現短token過期,則通知給客戶端,此時客戶端攜帶長token給服務器校驗。
如果長token未過期,表示用戶為“活躍用戶”,服務器重置短token和長token發(fā)給客戶端。
如果長token過期,表示用戶為“非活躍用戶”,用戶需要重新登錄。
總結:
優(yōu)點:
- 幫助使用頻率不高的軟件區(qū)別“活躍用戶”和“非活躍用戶”,提升“活躍用戶”的體驗,保證“非活躍用戶”的信息安全
缺點:
- 刷新token期間,原有的token不能用,在并發(fā)情況下會導致其他問題。
場景五:提升響應速度(redis)
場景分析:
場景一到四的共同點:①token內置了時間戳,②服務器端不存儲token
場景五是對以上以上四個場景在驗證速度上的優(yōu)化,親測使用redis可以提高2-4倍的驗證速度。
對token的要求:
token內不設置時間戳,而是將token存在redis中(例如:鍵為token,值為用戶信息),并設置token存在redis中的保存時間。
客戶端仍然保存著token,每次請求都攜帶token。服務器接到請求,把token當做鍵去redis中拿數據:
如果能拿出數據,則說名token沒過期,如果拿不到,則說明token過期了。
想要重置token有效期,直接根據鍵重置數據在redis中的有效期。
實操:
<!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>3.0.0</version> </dependency>
redis工具類
/** * Redis工具類 */ @Component public final class RedisUtil<V> { @Autowired private RedisTemplate<String, String> redisTemplate; /** * 普通緩存獲取 * @param key 鍵 * @return 值 */ public String get(String key) { return key == null ? null : (String) redisTemplate.opsForValue().get(key); } /** * 根據key 獲取過期時間 * @param key 鍵 不能為null * @return 時間(秒) 返回0代表為永久有效 */ public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * HashSet 并設置時間 * @param key 鍵 * @param map 對應多個鍵值 * @param time 時間(秒) * @return true成功 false失敗 */ public boolean hmset(String key, Map<String, Object> map, long time) { try { redisTemplate.opsForHash().putAll(key, map); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } }
攔截請求,開始驗證token
public class ShiroRealm extends AuthorizingRealm { @Autowired private RedisUtil redisUtil; /** * @Title: doGetAuthenticationInfo * @description: 校驗token是否正確 * @param: auth * @return: org.apache.shiro.authc.AuthenticationInfo **/ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException { //不使用token驗證來校驗token是否可用,改成把token存redis中,在redis中設置數據有效期 String token = (String) auth.getCredentials(); if (StringUtils.isEmpty(token)) { throw new AuthenticationException(Constant.TOKEN_EXPIRED); } //判斷是否能從redis中用token拿到過期時間 try{ Long times=redisUtil.getExpire(token); //如果還有0.5小時過期,就刷新token在redis中的有效時間(有效期設置為2小時) if(1800>times){ redisUtil.expire(token,7200); } String userId =redisUtil.get(token); //獲取key中的用戶id return new SimpleAuthenticationInfo(userId, token, getName()); }catch(Exception e){ throw new AuthenticationException("驗證token失敗"); } } }
驗證redis校驗速度
//token時間戳校驗 long startTime=System.currentTimeMillis(); //獲取開始時間 for(int i=0;i<99;i++){ String userPhone = JwtUtil.isExpire(token); } long endTime=System.currentTimeMillis(); //獲取結束時間 System.out.println("用時間戳方式校驗100次token是否有效: "+(endTime-startTime)+"毫秒");
//redis校驗 long startTime=System.currentTimeMillis(); //獲取開始時間 for(int i=0;i<99;i++){ if(StringUtils.isEmpty(redisUtil.get(token))){ return null; } } long endTime=System.currentTimeMillis(); //獲取結束時間 System.out.println("用redis設置過期時間方式校驗100次token是否有效: "+(endTime-startTime)+"毫秒");
總結:
優(yōu)點:
- 服務器生成了token就直接存到redis中,token是不會被攔截篡改的,因此默認token是正確的,也就減少了驗證token是否正確這一步驟
- 重置了token,token本身也不會變,減少客戶端處理廢棄token、再存儲新token的邏輯
- redis的數據存在內存中,IO速度快
缺點:
- 依賴第三方軟件,需要搭建redis服務器,考慮到redis掛了軟件也不能使用,還要搭建redis集群
思想升華:
每種設置token有效期的方案都有對應的場景,拋開場景談方案是狹隘的。再學習計算機的過程中,我發(fā)現無論是磁盤調度方式、內存存儲方式、raid0-6,所有方式方法的誕生都和當時的場景相關,并且往往在時間和空間上面進行取舍。所以沒有最優(yōu)的方案,只有當下相對合適的方案。
到此這篇關于Java設置token有效期的5個應用場景(雙token實現)的文章就介紹到這了,更多相關Java設置token有效期內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring Security Oauth2.0 實現短信驗證碼登錄示例
本篇文章主要介紹了Spring Security Oauth2.0 實現短信驗證碼登錄示例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-01-01SpringBoot配置GlobalExceptionHandler全局異常處理器案例
這篇文章主要介紹了SpringBoot配置GlobalExceptionHandler全局異常處理器案例,通過簡要的文章說明如何去進行配置以及使用,需要的朋友可以參考下2021-06-06java生成csv文件亂碼的解決方法示例 java導出csv亂碼
這篇文章主要介紹了java生成csv文件亂碼的解決方法,大家可以直接看下面的示例2014-01-01