基于 Redis 的 JWT令牌失效處理方案(實(shí)現(xiàn)步驟)
應(yīng)用場(chǎng)景
當(dāng)用戶(hù)登錄狀態(tài)到登出狀態(tài)時(shí),對(duì)應(yīng)的JWT的令牌需要設(shè)置為失效狀態(tài),這時(shí)可以使用基于 Redis 的黑名單方案來(lái)實(shí)現(xiàn)JWT令牌失效。
基于 Redis 的黑名單方案
當(dāng)用戶(hù)需要登出系統(tǒng)時(shí),將用戶(hù)攜帶的Token進(jìn)行解析,解碼出JWT令牌,取出對(duì)應(yīng)的 UUID 和 過(guò)期時(shí)間 ,用過(guò)期的時(shí)間減去當(dāng)前的時(shí)間,計(jì)算出這個(gè)Key的過(guò)期時(shí)間,再以這兩個(gè)字段拼接作為 Key 并設(shè)置好過(guò)期時(shí)間存儲(chǔ)到 Redis 中,如果有黑客拿竊取出來(lái)的JWT令牌進(jìn)行登錄,只要判斷這個(gè)JWT令牌是否在黑名單就可以。
實(shí)現(xiàn)步驟
1.獲得攜帶的Token解析并取出JWT令牌的代碼
這段代碼實(shí)現(xiàn)了對(duì)指定 JWT 的驗(yàn)證和使令牌失效的操作。
- 首先,通過(guò)調(diào)用 convertToken(headerToken) 方法將傳入的頭部令牌 headerToken 轉(zhuǎn)換成實(shí)際的 JWT 字符串 token。
- 然后,使用 HMAC256 算法和預(yù)設(shè)的密鑰 key 創(chuàng)建一個(gè)算法實(shí)例 algorithm。
- 接下來(lái),使用算法實(shí)例 algorithm 構(gòu)建一個(gè) JWT 驗(yàn)證器 jwtVerifier。這個(gè)驗(yàn)證器將用于驗(yàn)證 JWT 的有效性。
- 在 try-catch 塊中,首先通過(guò)調(diào)用 jwtVerifier.verify(token) 方法對(duì) JWT 進(jìn)行驗(yàn)證。如果驗(yàn)證成功,則返回一個(gè) DecodedJWT 對(duì)象 verify,其中包含了 JWT 的解碼信息,如令牌的唯一標(biāo)識(shí)符(ID)和過(guò)期時(shí)間等。
- 接著,調(diào)用 deleteToken(verify.getId(), verify.getExpiresAt()) 方法來(lái)刪除指定令牌,并將其加入到黑名單中進(jìn)行失效處理。這里使用了 verify 對(duì)象中的 ID 和過(guò)期時(shí)間作為參數(shù)。
- 最后,如果在驗(yàn)證 JWT 過(guò)程中發(fā)生了 JWTVerificationException 異常,即 JWT 驗(yàn)證失敗,則捕獲該異常,并返回 false 表示令牌失效操作失敗。
/**
* 讓指定Jwt令牌失效
* @param headerToken 請(qǐng)求頭中攜帶的令牌
* @return 是否操作成功
*/
public boolean invalidateJwt(String headerToken){
String token = this.convertToken(headerToken);
Algorithm algorithm = Algorithm.HMAC256(key);
JWTVerifier jwtVerifier = JWT.require(algorithm).build();
try {
DecodedJWT verify = jwtVerifier.verify(token);
return deleteToken(verify.getId(), verify.getExpiresAt());
} catch (JWTVerificationException e) {
return false;
}
}2.檢查指定 UUID 的令牌是否為無(wú)效的(已加入黑名單)
這段代碼用于檢查指定 UUID 的令牌是否為無(wú)效的(已加入黑名單),通過(guò)判斷 Redis 數(shù)據(jù)庫(kù)中是否存在相應(yīng)的鍵來(lái)決定令牌的有效性。如果鍵存在,則表示令牌已失效;如果鍵不存在,則表示令牌仍然有效。
/**
* 驗(yàn)證Token是否被列入Redis黑名單
* @param uuid 令牌ID
* @return 是否操作成功
*/
private boolean isInvalidToken(String uuid){
return Boolean.TRUE.equals(template.hasKey(Const.JWT_BLACK_LIST + uuid));
}3.將Token列入Redis黑名單中
這段代碼實(shí)現(xiàn)了對(duì)指定令牌的刪除和加入黑名單的操作,用于管理令牌的有效性和安全性。
- 首先,通過(guò)調(diào)用 isInvalidToken(uuid) 方法來(lái)檢查指定的 UUID 是否為無(wú)效的令牌。如果 isInvalidToken 方法返回 true,則說(shuō)明該令牌無(wú)效,此時(shí)直接返回 false,不執(zhí)行后續(xù)操作。
- 獲取當(dāng)前時(shí)間 now,然后計(jì)算令牌的過(guò)期時(shí)間與當(dāng)前時(shí)間的差值,并取最大值作為令牌的失效時(shí)間 expire。這里使用了 Math.max 方法來(lái)確保失效時(shí)間不會(huì)小于 0。
- 最后,通過(guò) Redis 的 template 對(duì)象調(diào)用 opsForValue().set() 方法,將指定 UUID 的令牌加入到名為 Const.JWT_BLACK_LIST + uuid 的鍵中,并設(shè)置過(guò)期時(shí)間為 expire 毫秒。這樣就將該令牌加入到了黑名單中,使其在一定時(shí)間后失效。
- 最終,方法返回 true 表示成功刪除令牌并將其加入黑名單。
/**
* 將Token列入Redis黑名單中
* @param uuid 令牌ID
* @param time 過(guò)期時(shí)間
* @return 是否操作成功
*/
private boolean deleteToken(String uuid, Date time){
if(this.isInvalidToken(uuid))
return false;
Date now = new Date();
long expire = Math.max(time.getTime() - now.getTime(), 0);
template.opsForValue().set(Const.JWT_BLACK_LIST + uuid, "", expire, TimeUnit.MILLISECONDS);
return true;
}public final class Const {
//JWT令牌
public final static String JWT_BLACK_LIST = "jwt:blacklist:";
public final static String JWT_FREQUENCY = "jwt:frequency:";
}對(duì)應(yīng)完整的代碼如下:
@Component
public class JwtUtils {
@Autowired
private StringRedisTemplate template;
@Value("${spring.security.jwt.key}")
String key;
@Value("${spring.security.jwt.expire}")
int expire;
/**
* 讓指定Jwt令牌失效
* @param headerToken 請(qǐng)求頭中攜帶的令牌
* @return 是否操作成功
*/
public boolean invalidateJwt(String headerToken){
String token = this.convertToken(headerToken);
Algorithm algorithm = Algorithm.HMAC256(key);
JWTVerifier jwtVerifier = JWT.require(algorithm).build();
try {
DecodedJWT verify = jwtVerifier.verify(token);
return deleteToken(verify.getId(), verify.getExpiresAt());
} catch (JWTVerificationException e) {
return false;
}
}
/**
* 將Token列入Redis黑名單中
* @param uuid 令牌ID
* @param time 過(guò)期時(shí)間
* @return 是否操作成功
*/
private boolean deleteToken(String uuid, Date time){
if(this.isInvalidToken(uuid))
return false;
Date now = new Date();
long expire = Math.max(time.getTime() - now.getTime(), 0);
template.opsForValue().set(Const.JWT_BLACK_LIST + uuid, "", expire, TimeUnit.MILLISECONDS);
return true;
}
/**
* 驗(yàn)證Token是否被列入Redis黑名單
* @param uuid 令牌ID
* @return 是否操作成功
*/
private boolean isInvalidToken(String uuid){
return Boolean.TRUE.equals(template.hasKey(Const.JWT_BLACK_LIST + uuid));
}
public DecodedJWT resolveJwt(String headerToken) {
String token = this.convertToken(headerToken);
if (token == null) {
return null;
}
Algorithm algorithm = Algorithm.HMAC256(key);
JWTVerifier jwtVerifier = JWT.require(algorithm).build();
try {
DecodedJWT verify = jwtVerifier.verify(token);
if(this.isInvalidToken(verify.getId())) return null;
Date expireAt = verify.getExpiresAt();
return new Date().after(expireAt) ? null : verify;
} catch (JWTVerificationException e) {
return null;
}
}
public UserDetails toUser(DecodedJWT jwt) {
Map<String, Claim> claims = jwt.getClaims();
return User.withUsername(claims.get("name").asString())
.password("********")
.authorities(claims.get("authorities").asArray(String.class))
.build();
}
public Integer toId(DecodedJWT jwt) {
Map<String, Claim> claims = jwt.getClaims();
return claims.get("id").asInt();
}
public String createJwt(UserDetails details, int id, String username) {
Algorithm algorithm = Algorithm.HMAC256(key);
Date expire = this.expireTime();
return JWT.create()
.withJWTId(UUID.randomUUID().toString())
.withClaim("id", id)
.withClaim("name", username)
.withClaim("authorities", details.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList())
.withExpiresAt(expire)
.withIssuedAt(new Date())
.sign(algorithm);
}
public Date expireTime() {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.HOUR, expire * 24);
return calendar.getTime();
}
private String convertToken(String headerToken) {
if(headerToken == null || !headerToken.startsWith("Bearer ")) {
return null;
}
return headerToken.substring(7);
}
}到此這篇關(guān)于基于 Redis 的 JWT令牌失效方案的文章就介紹到這了,更多相關(guān)Redis JWT令牌失效內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 如何使用jwt+redis實(shí)現(xiàn)單點(diǎn)登錄
- 使用Redis實(shí)現(xiàn)JWT令牌主動(dòng)失效機(jī)制
- SpringSecurity+Redis+Jwt實(shí)現(xiàn)用戶(hù)認(rèn)證授權(quán)
- Shiro整合Springboot和redis,jwt過(guò)程中的錯(cuò)誤shiroFilterChainDefinition問(wèn)題
- jwt+redis實(shí)現(xiàn)登錄認(rèn)證的示例代碼
- springboot+springsecurity+mybatis+JWT+Redis?實(shí)現(xiàn)前后端離實(shí)戰(zhàn)教程
- SpringBoot整合SpringSecurity和JWT和Redis實(shí)現(xiàn)統(tǒng)一鑒權(quán)認(rèn)證
- SpringSecurity+jwt+redis基于數(shù)據(jù)庫(kù)登錄認(rèn)證的實(shí)現(xiàn)
- java實(shí)現(xiàn)認(rèn)證與授權(quán)的jwt與token+redis,哪種方案更好用?
相關(guān)文章
React實(shí)現(xiàn)組件之間通信的幾種常用方法
在?React?中,組件之間的通信是構(gòu)建復(fù)雜應(yīng)用程序的核心部分,良好的組件間通信能夠提高代碼的可維護(hù)性和可讀性,同時(shí)能夠高效地管理應(yīng)用狀態(tài),在這篇博客中,我們將探討?React中幾種常用的組件通信方法,并提供示例代碼來(lái)幫助你理解,需要的朋友可以參考下2025-02-02
redis實(shí)現(xiàn)簡(jiǎn)單隊(duì)列
這篇文章主要為大家詳細(xì)介紹了redis實(shí)現(xiàn)簡(jiǎn)單隊(duì)列的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
淺談redis五大數(shù)據(jù)結(jié)構(gòu)和使用場(chǎng)景
這篇文章主要介紹了淺談redis五大數(shù)據(jù)結(jié)構(gòu)和使用場(chǎng)景,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
Redis內(nèi)部數(shù)據(jù)結(jié)構(gòu)Dict的實(shí)現(xiàn)方法
這篇文章主要介紹了Redis內(nèi)部數(shù)據(jù)結(jié)構(gòu)Dict的實(shí)現(xiàn)方法,本篇文章所述的dict在Redis中最主要的作用就是用于維護(hù)Redis數(shù)據(jù)庫(kù)中所有Key、value映射的數(shù)據(jù)結(jié)構(gòu),需要的朋友可以參考下2022-05-05
Redis常見(jiàn)分布鎖的原理和實(shí)現(xiàn)
這篇文章主要介紹了Redis常見(jiàn)分布鎖的原理和實(shí)現(xiàn),文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-08-08
Redis中的數(shù)據(jù)結(jié)構(gòu)跳表詳解
跳表是一種基于并聯(lián)的鏈表結(jié)構(gòu),用于在有序元素序列中快速查找元素的數(shù)據(jù)結(jié)構(gòu),本文給大家介紹Redis中的數(shù)據(jù)結(jié)構(gòu)跳表,感興趣的朋友跟隨小編一起看看吧2024-06-06
Springboot/Springcloud項(xiàng)目集成redis進(jìn)行存取的過(guò)程解析
大家都知道Redis支持五種數(shù)據(jù)類(lèi)型:string(字符串),hash(哈希),list(列表),set(集合),zset(sorted set:有序集合),本文重點(diǎn)給大家介紹Springboot/Springcloud項(xiàng)目集成redis進(jìn)行存取的過(guò)程,需要的朋友參考下吧2021-12-12

