一、用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错误

redis info 没有logfile redis is not empty_spring boot

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");
        }

我也是小白,可能有什么写的不对的地方,希望指出改正,大家一起讨论,一起进步