一、用Redis持久化remember-me中所需要的数据
今天用springboot集成springsecurity时,开启remember-me功能时,发现springsecurity提供了两个持久化数据的方式1. InMemoryTokenRepositoryImpl 2.JdbcTokenRepositoryImpl ,这两个实现类,准确的说第一个实现类是存入内存,个人感觉不算真正意义上的持久化。
JdbcTokenRepositoryImpl 这个是Springsecurity实现自动登录,官网的用法,连接mysql数据库。
但是今天做持久化的时候想到能否用redis做自动登录,因为redis的速度比mysql快,而且自动登录开启过后每次登录都要去访问mysql数据库,感觉对服务器的压力增加,速度也相对较慢
二、实现步骤
1. 实现PersistentTokenRepository接口
因为JdbcTokenRepositoryImpl 实现了PersistentTokenRepository接口,进行数据持久化,所有我们要用redis持久化数据需要实现此接口
public interface PersistentTokenRepository {
void createNewToken(PersistentRememberMeToken var1);
void updateToken(String var1, String var2, Date var3);
PersistentRememberMeToken getTokenForSeries(String var1);
void removeUserTokens(String var1);
}
2.编写我们自己的PersistentTokenRepository
/**
* @author XuJiaLin
* @date 2021/7/21 10:10
*/
@Component
public class MyPersistentTokenRepository implements PersistentTokenRepository {
@Autowired
private RedisTemplate redisTemplate;
//令牌过期时间
private final static long TOKEN_VALID_DAYS=14;
//创建令牌--参数传入对应的信息
@Override
public void createNewToken(PersistentRememberMeToken token) {
//token包括username,series, tokenValue,date 4个属性;
//生成一个存储Token信息的Key
String key = generateKey(token.getSeries());
//生成一个存储series的key,因为下面removeToken传入的参数为username,所以用username生成一个key来获取唯一的series
//先存储usernamekey
String usernamekey=generateKey(token.getUsername());
redisTemplate.opsForValue().set(usernamekey,token.getSeries());
redisTemplate.expire(usernamekey,TOKEN_VALID_DAYS, TimeUnit.DAYS);
//创建一个hashmap
Map<String,String> map=new HashMap<>();
map.put("username",token.getUsername());
map.put("tokenValue",token.getTokenValue());
map.put("date",String.valueOf(token.getDate().getTime()));
map.put("series",token.getSeries());
//将Token数据存入redis
redisTemplate.opsForHash().putAll(key,map);
redisTemplate.expire(key,TOKEN_VALID_DAYS, TimeUnit.DAYS);
}
//更新令牌
@Override
public void updateToken(String series, String tokenValue, Date date) {
String key = generateKey(series);
if (redisTemplate.hasKey(key))
{
redisTemplate.opsForHash().put(key,"tokenValue",tokenValue);
redisTemplate.opsForHash().put(key,"date",String.valueOf(date.getTime()));
}
}
//获取Token通过series
@Override
public PersistentRememberMeToken getTokenForSeries(String series) {
String key = generateKey(series);
//创建一个ArrayList用来获取多个value
List<String> hashKeys = new ArrayList<>();
hashKeys.add("username");
hashKeys.add("tokenValue");
hashKeys.add("date");
List<String> hashValues = redisTemplate.opsForHash().multiGet(key, hashKeys);
String username = hashValues.get(0);
String tokenValue = hashValues.get(1);
String date = hashValues.get(2);
if (null == username || null == tokenValue || null == date) {
return null;
}
Long timestamp = Long.valueOf(date);
Date time = new Date(timestamp);
return new PersistentRememberMeToken(username, series, tokenValue, time);
}
//移除对应的Token
@Override
public void removeUserTokens(String username) {
//因为传入的是username,所以我们先用刚才创建的usernamekey获取到series的值
String Usernamekey=generateKey(username);
Object o = redisTemplate.opsForValue().get(Usernamekey);
String key=generateKey(String.valueOf(o));
if (o!=null){
redisTemplate.delete(Usernamekey);
redisTemplate.delete(key);
}
}
//生成对应的唯一key
private String generateKey(String series) {
return "Spring:security:token" + series;
}
}
series是每个用户唯一的,而且在用户访问服务端时不会发生变化,只有在用户重新使用用户名和密码登录时才会重新生成,而token在每次用户自动登录后都会更新,所以我们用series生成的key不会发生变化,只有当用户重新登录后,会删除以前的数据库的数据
3.在配置类添加我们自己的repository
//注入我们自己的repository
@Autowired
private MyPersistentTokenRepository repository;
//开启自动登录,添加自己的repository
http.rememberMe().tokenRepository(repository).
userDetailsService(new UserDetailsServiceImpl());
注意:在添加自己的repository时还需要注入UserDetailsService的实现类,在进行自动登录时如果没注入实现类就会报UserDetailsService is required错误
4. 我出现的错误–NullPointerException
我注入UserDetailsService的实现类后,在关闭程序重新运行时,在进行登录操作,发现自定义登录失败,而且给我报了个NullPointerException,然后Springsecurity自动清楚了cookie和数据库里面的数据,然后我进行debug,发现我在UserDetailsServiceImpl中进行加密的BCryptPasswordEncoder竟然没有了,为空,第一次登录都可以进行加密,从IOC容器中获得我自己的BCryptPasswordEncoder,但是在自动登录阶段就为null了,我也是小白,debug看了一下,没找出原因,但是我对代码进行了修改,就行了
if (pw == null) {
password = new BCryptPasswordEncoder().encode("123");
} else {
password = pw.encode("123");
}
我也是小白,可能有什么写的不对的地方,希望指出改正,大家一起讨论,一起进步