Redis優(yōu)化token校驗(yàn)主動(dòng)失效的實(shí)現(xiàn)方案
場(chǎng)景:
在普通的token頒發(fā)和校驗(yàn)中 當(dāng)用戶發(fā)現(xiàn)自己賬號(hào)和密碼被暴露了時(shí)修改了登錄密碼后舊的token仍然可以通過系統(tǒng)校驗(yàn)直至token到達(dá)失效時(shí)間,這肯定是不安全的,所以系統(tǒng)需要token主動(dòng)失效的一種能力
解決方案:
我們可以使用redis來實(shí)現(xiàn)redis主動(dòng)失效的的功能
需求實(shí)現(xiàn)邏輯:
在每次用戶登錄后頒發(fā)token的同時(shí)往redis數(shù)據(jù)庫(kù)中存儲(chǔ)一份頒發(fā)給用戶的token
每次每次用戶請(qǐng)求時(shí)除了解析token外還需要查詢r(jià)edis中是否有當(dāng)前token有則校驗(yàn)通過,沒有則校驗(yàn)失敗
每次用戶修改密碼后刪除redis中當(dāng)前用所攜帶的token,從而使舊token無法通過token校驗(yàn)
代碼實(shí)現(xiàn)
pom.xml中添加redis坐標(biāo)
<!--redis坐標(biāo)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置文件application.yml中配置redis相關(guān)信息
spring:
data:
redis:
host: redis所在的ip,本機(jī)的redis服務(wù)填localhost
port: redis服務(wù)端口,默認(rèn)6379
password: redis中設(shè)置的密碼,沒有就不需要頒發(fā)token時(shí)往redis中存儲(chǔ)token
//UserController中添加私有屬性stringRedisTemplate并實(shí)例化 @Autowired private StringRedisTemplate stringRedisTemplate; //登錄接口中把token存到redis 過期時(shí)間3小時(shí) stringRedisTemplate.opsForValue().set(token,token,3, TimeUnit.HOURS);
登錄時(shí)校驗(yàn)是否redis中有當(dāng)前token
package org.example.intercopters;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.utils.JwtUtil;
import org.example.utils.ThreadLocalUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Map;
@Component //注冊(cè)攔截器 將其放入ioc容器中
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate redisTemplate;
//創(chuàng)建登錄身份校驗(yàn)攔截器
@Override //請(qǐng)求開始前觸發(fā)的攔截方法
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//令牌驗(yàn)證
String token = request.getHeader("Authorization");
//去除token的前綴標(biāo)記"Bearer "
var newToken = token.contains("Bearer ")?token.substring("Bearer ".length()):token;
try {
**//從redis獲取相同的token
String redisToken = redisTemplate.opsForValue().get(newToken);
if(redisToken == null){
//redis失效
throw new RuntimeException("token失效");
}**
Map<String, Object> claims = JwtUtil.parseToken(token);
//把用戶信息存儲(chǔ)到ThreadLocal中,tomcat會(huì)在每次接口請(qǐng)求時(shí)創(chuàng)建一個(gè)線程 而ThreadLocal中存儲(chǔ)的數(shù)據(jù)是線程安全的
ThreadLocalUtil.set(claims);
//放行
return true;
} catch (Exception e) {
//設(shè)置響應(yīng)狀態(tài)碼
response.setStatus(401);
//設(shè)置響應(yīng)字符集和響應(yīng)內(nèi)容
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");
String errorMessage = "未登錄";
response.getWriter().write("{\"error\": \"" + errorMessage + "\"}");
//不放行
return false;
}
}
@Override //請(qǐng)求完成后觸發(fā)的攔截方法
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//清空ThreadLocal中的數(shù)據(jù)
ThreadLocalUtil.remove();
}
}
修改密碼后刪除redis中的token
//修改密碼接口中刪除redis中對(duì)應(yīng)的token ValueOperations<String, String> operations = stringRedisTemplate.opsForValue(); operations.getOperations().delete(newToken);
在本地開發(fā)中如果使用本地redis每次開發(fā)前還得去啟動(dòng)本地redis,就很麻煩,如果你有一個(gè)云服務(wù)器就可以本地連云服務(wù)器redis就不用每次去啟動(dòng)redis了,下面是實(shí)現(xiàn)教程:
前提是讀者已經(jīng)安裝好了Redis。
因?yàn)閯倢W(xué)Linux不久,敲過一些命令,算不上熟悉,對(duì)網(wǎng)絡(luò)防火墻那一塊也不熟悉。
因此雖然在項(xiàng)目中已經(jīng)開啟了Redis服務(wù),也寫好配置類:
#redis spring.redis.host=x.x.x.x spring.redis.port=6379 spring.redis.database= 0 spring.redis.timeout=1800000
```java
@Configuration
@EnableCaching // 開啟緩存處理,使用redis,將字典的數(shù)據(jù)緩存到其中!
public class RedisConfig {
private RedisCacheManager build;
/**
* 自定義key規(guī)則
*
* @return
*/
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
/**
* 設(shè)置RedisTemplate規(guī)則
*
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解決查詢緩存轉(zhuǎn)換異常的問題
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修飾符范圍,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化輸入的類型,類必須是非final修飾的,final修飾的類,比如String,Integer等會(huì)跑出異常
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//序列號(hào)key value
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 設(shè)置CacheManager緩存規(guī)則
*
* @param factory
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解決查詢緩存轉(zhuǎn)換異常的問題
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解決亂碼的問題),過期時(shí)間600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
但還是啟動(dòng)不了。
在這里梳理一下:
- 你先要去云服務(wù)器(這里以騰訊云為例)到防火墻里面添加端口,我就以默認(rèn)的6379為例。

- 然后去你的linux上面查看你的防護(hù)墻有沒有打開和防火墻有沒有添加對(duì)應(yīng)的端口號(hào)。命令如下
#查看linux上面防火墻的狀態(tài) systemctl status firewalld.service #如果是running的,就需要關(guān)了 systemctl stop firewalld.service [root@VM-4-12-centos ~]# firewall-cmd --query-port=6379/tcp#查詢 no [root@VM-4-12-centos ~]# firewall-cmd --add-port=6379/tcp#添加 success
- 最后你還需要去你的redis.conf文件查看你的保護(hù)有沒有打開,默認(rèn)的端口號(hào)有沒有注釋掉:
做以下的修改,你就可以通過你的項(xiàng)目連接到Redis。
# bind 127.0.0.1 ::1 protected-mode no port 6379
到此這篇關(guān)于Redis優(yōu)化token校驗(yàn)主動(dòng)失效的實(shí)現(xiàn)方案的文章就介紹到這了,更多相關(guān)Redis token校驗(yàn)主動(dòng)失效內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解用Redis實(shí)現(xiàn)Session功能
本篇文章主要介紹了用Redis實(shí)現(xiàn)Session功能,具有一定的參考價(jià)值,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。2016-12-12
redis cluster集群模式下實(shí)現(xiàn)批量可重入鎖
本文主要介紹了使用redis cluster集群版所遇到的問題解決方案及redis可重入鎖是否會(huì)有死鎖的問題等,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02
讓Redis在你的系統(tǒng)中發(fā)揮更大作用的幾點(diǎn)建議
Redis在很多方面與其他數(shù)據(jù)庫(kù)解決方案不同:它使用內(nèi)存提供主存儲(chǔ)支持,而僅使用硬盤做持久性的存儲(chǔ);它的數(shù)據(jù)模型非常獨(dú)特,用的是單線程。另一個(gè)大區(qū)別在于,你可以在開發(fā)環(huán)境中使用Redis的功能,但卻不需要轉(zhuǎn)到Redis2014-06-06
Redis概述及l(fā)inux安裝redis的詳細(xì)教程
這篇文章主要介紹了Redis概述及l(fā)inux安裝redis的詳細(xì)教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10

