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