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#setRememberMeServicesWebSecurityConfigurerAdapter#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#setRememberMeServicesWebSecurityConfigurerAdapter#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();

 }
}
posted @ 2021-05-26 18:54  蜀中孤鹰  阅读(619)  评论(0编辑  收藏  举报