当前位置: 首页 > 工具软件 > Spring ME > 使用案例 >

spring security 实现基于redis的 rememberMe token持久化功能

谢璞
2023-12-01

rememberMe认证流程
简单来说,认证时首先会经过UsernamePasswordAuthenticationFilter,如果开启了rememberMe,随后会有一个RememberMeAuthenticationFilter进行token认证。该filter调用一个rememberMeServices,该serviceautoLogin方法中从request中获取对应的cookie,经过解密分解为一个seriestoken。之后利用一个tokenRepository获取一个PersistentRememberMeToken对象,该对象包含了基本token、series、username和日期等信息。通过比较取出的PersistentRememberMeToken中的token与request中的token,进行验证。
因为spring security已经帮助我们完成许多处理,因此自定义实现redis持久化token,我们只需要实现一点:

  1. 一个自定义的tokenRepository,用以持久化token操作。

实现代码
一个实现了PersistentTokenRepositoryRedisPersistentRe类。
PersistentTokenRepository接口主要包括这几个方法:

    void createNewToken(PersistentRememberMeToken var1);
//创建新的token,每次采用密码账登录验证成功后都会执行这个方法。
    void updateToken(String var1, String var2, Date var3);
//更新token,每次新的会话都会更新token。其中var1代表series,var2代表新的token值。
    PersistentRememberMeToken getTokenForSeries(String var1);
//通过series获取PersistentRememberMeToken对象。
    void removeUserTokens(String var1);
//删除对应的token,采用默认的rememberMeServices时,var1是username。

RedisPersistentRe

@Component
public class RedisPersistentRe implements PersistentTokenRepository {
    private final static String USERNAME_KEY = "spring:security:rememberMe:USERNAME_KEY:";
    private final static String SERIES_KEY = "spring:security:rememberMe:SERIES_KEY:";
    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Override
    public void createNewToken(PersistentRememberMeToken persistentRememberMeToken) {
        String series = persistentRememberMeToken.getSeries();
        String key = generateKey(series,SERIES_KEY);
        String usernameKey = generateKey(persistentRememberMeToken.getUsername(),USERNAME_KEY);
        //用户只要采用账户密码重新登录,那么为了安全就有必要清除之前的token信息。deleteIfPresent方法通过
        //username查找到用户对应的series,然后删除旧的token信息。
        deleteIfPresent(usernameKey);
        HashMap<String,String > hashMap = new HashMap<>();
        hashMap.put("username",persistentRememberMeToken.getUsername());
        hashMap.put("token",persistentRememberMeToken.getTokenValue());
        hashMap.put("date",String.valueOf(persistentRememberMeToken.getDate().getTime()));
        HashOperations<String ,String ,String> hashOperations = redisTemplate.opsForHash();
        hashOperations.putAll(key,hashMap);
        redisTemplate.expire(key,1, TimeUnit.DAYS);//设置token保存期限
        stringRedisTemplate.opsForValue().set(usernameKey,series);
        redisTemplate.expire(usernameKey,1, TimeUnit.DAYS);
    }

    @Override
    public void updateToken(String s, String s1, Date date) {
        String key = generateKey(s,SERIES_KEY);
        if(redisTemplate.hasKey(key))
            redisTemplate.opsForHash().put(key,"token",s1);
    }

    @Override
    public PersistentRememberMeToken getTokenForSeries(String s) {
        String key = generateKey(s,SERIES_KEY);
        List<String> hashKeys = new ArrayList<>();
        hashKeys.add("username");
        hashKeys.add("token");
        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, s, tokenValue, time);
    }

    @Override
    public void removeUserTokens(String s) {
    //rememberMeService实现类中调用这个方法传入的参数是username,因此我们必须通过username查找到
    //对应的series,然后再通过series查找到对应的token信息再删除。
        String key = generateKey(s,USERNAME_KEY);
        deleteIfPresent(key);
    }
    
    private void deleteIfPresent(String key){
    //删除token时应该同时删除token信息,以及保存了对应的username与series对照数据。
        if(redisTemplate.hasKey(key)){
            String series = generateKey(stringRedisTemplate.opsForValue().get(key),SERIES_KEY);
            if(series!=null && redisTemplate.hasKey(series)){
                redisTemplate.delete(series);
                redisTemplate.delete(key);
            }
        }
    }
    private String generateKey(String series,String prefix) {
        return prefix + series;
    }
}

config配置:
主要是开启

.rememberMe().tokenRepository(redisPerSisRe).rememberMeCookieName("mytoken").userDetailsService(aUserDetailsService)
//其中redisPerSisRe就是上面的RedisPerSisRe类自动注入,aUserDetailsService是用户登录认证时需要的service,采用自定义密码登录时也是这个service.

完整配置:

@Configuration
@EnableWebSecurity
public class ASpringSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    AUserDetailsService aUserDetailsService;
    @Autowired
    RedisPersistentRe redisPerSisRe;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().permitAll()
                .defaultSuccessUrl("/success",true)
                .and()
                .rememberMe()
                .tokenRepository(redisPerSisRe)
                .rememberMeCookieName("mytoken")
                .userDetailsService(aUserDetailsService)
                .and()
                .csrf().disable()
                ;
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(aUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }
    

}
 类似资料: