spring security 使用自定义的AuthenticationFilter,提示Invalid remember-me cookie,自动登录失败的解决方法
spring security 在使用自定义的AuthenticationFilter时,提示Invalid remember-me cookie,自动登录失败的解决方法
后台日志报错提示
Invalid remember-me cookie: Cookie token[2] contained signature '1706b276eae214cc837865d3c508cf01' but expected '84908c8a60e515d544d96550496f0daf'
我自定义了一个LoginFilter,它继承于UsernamePasswordAuthenticationFilter,用于支持json格式登录。
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
private static Logger log = LoggerFactory.getLogger(LoginFilter.class);
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
///省略实现
}
}
但自动登录时始终报错:请求时携带的名为remember-me
的cookie和服务器后台计算出的remember-me
不匹配。后来Debug了很长时间才发现, 服务端会实现化两个TokenRememberMeServices实例,每个实例的构造方法都会需要key ( 计算加密remember-me
的cookie时用到的这个key(相当于盐) ) ,如果两个key不一致,计算加密后的 remember-me 签名当然也就不一样,也就是cookie不匹配,自动登录失败。
//必须入一个key
public TokenBasedRememberMeServices(String key, UserDetailsService userDetailsService) {
super(key, userDetailsService);
}
在使用自定义的AuthenticationFilter时,要实现自动登录必须配置RememberMeServices,否则系统将使用默认使用NullRememberMeServices。NullRememberMeServices所有方法都是空的,没有实现setCookies操作,不会向客户端写入名为remember-me的cookies,也就无法实现自动登录。
在自定义的过滤器 LoginFilter#setRememberMeServices
和WebSecurityConfigurerAdapter#configure(HttpSecurity)
中需要同时设置Key,并且这个两处的Key值要相同。我之前没有在configure(HttpSecurity)方法中设key, 这里若没有设值,系统将使用随机生成的UUID作为key。这个随机的UUID肯定与setRememberMeServices方法中手动设置的keyOfRememberMe不一致,所以最终导致remember-me签名校验失败,无法自动登录。
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private ApplicationContext ctx;
//appllication.yaml 配置的remember-me key
@Value("${spring.security.remember-me.key}")
private String keyOfRememberMe;
@Autowired
DataSource dataSource;
@Autowired
UserService userService;
@Bean
public LoginFilter loginFilter() throws Exception {
LoginFilter loginFilter = new LoginFilter();
//.....省略其他配置
loginFilter.setAuthenticationManager(authenticationManagerBean());
// 使用自定义的LoginFiler后要重新设置RememberMeServices,
loginFilter.setRememberMeServices(new TokenBasedRememberMeServices(keyOfRememberMe, userService));
return loginFilter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//使用自定义的过滤器LoginFilter替代默认的UsernamePasswordAuthenticationFilter
http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
http.authorizeRequests()
//...省略其他配置
.and()
.rememberMe()
.key(keyOfRememberMe) //非常重要,和上面的setRememberMeServices中的key保持一致
.and()
.csrf()
.disable();
}
}
补充:
另外还可以在LoginFilter#setRememberMeServices
和WebSecurityConfigurerAdapter#configure(HttpSecurity)
中配置同一个RememberMeServices实例,这样就不现分别为这两个方法设置remember-me的key了。
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private ApplicationContext ctx;
//appllication.yaml 配置的remember-me key
@Value("${spring.security.remember-me.key}")
private String keyOfRememberMe;
@Autowired
DataSource dataSource;
@Autowired
UserService userService;
//配置一个RememberMeServices类型的Bean
@Bean
public RememberMeServices rememberMeServices() {
return new TokenBasedRememberMeServices(keyOfRememberMe, userService);
}
@Bean
public LoginFilter loginFilter() throws Exception {
LoginFilter loginFilter = new LoginFilter();
//.....省略其他配置
loginFilter.setAuthenticationManager(authenticationManagerBean());
// 使用配置好的RememberMeServices实例,
loginFilter.setRememberMeServices(rememberMeServices());
return loginFilter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//使用自定义的过滤器LoginFilter替代默认的UsernamePasswordAuthenticationFilter
http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
http.authorizeRequests()
//...省略其他配置
.and()
.rememberMe()
//.key(keyOfRememberMe)
//设置一个RememberMeServices实例,就不用再调用方法.key(keyOfRememberMe)
.rememberMeServices(rememberMeServices())
.and()
.csrf()
.disable();
}
}