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