Spring Security4实例(Java config 版) —— Remember-Me

本文源码请看这里

相关文章:
Spring Security4实例(Java config版)——ajax登录,自定义验证


Spring Security提供了两种remember-me的实现,一种是简单的使用加密来保证基于cookie的token的安全,另一种是通过数据库或其它持久化存储机制来保存生成的token。


一、简单Hash-Based Token方式

  1. 首先在登录页login.html上添加一个CheckBox控件:

    rememberMe:
    

  1. 配置WebSecurityConfig类中的configure(HttpSecurity http)方法,开启remember me功能:
@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/", "/login/**").permitAll()
                // user权限可以访问的请求
                .antMatchers("/security/user").hasRole("user")
                // admin权限可以访问的请求
                .antMatchers("/security/admin").hasRole("admin")
                //SpEL表达式:需要拥有user权限,且进行了完全认证
                .antMatchers("/user/account").access("hasRole('user') and isFullyAuthenticated()")
                // 其他地址的访问均需验证权限(需要登录)
                .anyRequest().authenticated().and()
                // 添加验证码验证
                .addFilterAt(myUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class).exceptionHandling()
                .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login_page")).and()
                // 指定登录页面的请求路径
                .formLogin().loginPage("/login_page")
                // 登陆处理路径
                .loginProcessingUrl("/login").permitAll().and()
                //退出请求的默认路径为logout,下面改为signout, 成功退出登录后的url可以用logoutSuccessUrl设置
                .logout().logoutUrl("/signout").logoutSuccessUrl("/login_page").permitAll().and()
                // 开启rememberMe,设置一个私钥专供testall项目使用,注意与下面TokenBasedRememberMeServices的key保持一致
                .rememberMe().key("testallKey").and()
                // 关闭csrf
                .csrf().disable();
    }

倒数第二行代码就是啦,还可以用tokenValiditySeconds设置一个cookie的过期时间,单位为秒;
如果没有扩展UsernamePasswordAuthenticationFilter类,定制自己的Filte(比如为了加个验证码),上面的配置应该就可以了。否则还得继续配置:

  1. 设置TokenBasedRememberMeServices Bean,可以在里面进行一些remember me的配置:
    @Bean
    public TokenBasedRememberMeServices tokenBasedRememberMeServices() {
        TokenBasedRememberMeServices tbrms = new TokenBasedRememberMeServices("testallKey", userDetailsServiceImpl());
        // 设置cookie过期时间为2天
        tbrms.setTokenValiditySeconds(60 * 60 * 24 * 2);
        // 设置checkbox的参数名为rememberMe(默认为remember-me),注意如果是ajax请求,参数名不是checkbox的name而是在ajax的data里
        tbrms.setParameter("rememberMe");
        return tbrms;
    }

注意:这时候在步骤2里设置过期时间等配置均无效,但是key("testallKey")还是要设置的。

  1. 在MyUsernamePasswordAuthenticationFilter Bean中设置RememberMeServices为上面的service,还是倒数第二行
@Bean
    public MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter() throws Exception {
        MyUsernamePasswordAuthenticationFilter myFilter = new MyUsernamePasswordAuthenticationFilter();
        myFilter.setAuthenticationManager(authenticationManagerBean());
        myFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
        myFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
        myFilter.setRememberMeServices(tokenBasedRememberMeServices());
        return myFilter;
    }

这样就可以了,下面是部分说明


  • 当用户选择了记住我成功登录后,Spring Security将会生成一个cookie发送给客户端浏览器。cookie值由如下方式组成:

base64(username+":"+expirationTime+":"+md5Hex(username+":"+expirationTime+":"+password+":"+key))

Øusername:登录的用户名。

Øpassword:登录的密码。

ØexpirationTime:token失效的日期和时间,以毫秒表示。

Økey:用来防止修改token的一个key。

这样用来实现Remember-Me功能的token只能在指定的时间内有效,且必须保证token中所包含的username、password和key没有被改变才行。需要注意的是,这样做其实是存在安全隐患的,那就是在用户获取到实现记住我功能的token后,任何用户都可以在该token过期之前通过该token进行自动登录。如果用户发现自己的token被盗用了,那么他可以通过改变自己的登录密码来立即使其所有的记住我token失效。如果希望我们的应用能够更安全一点,可以使用接下来要介绍的持久化token方式,或者不使用Remember-Me功能,因为Remember-Me功能总是有点不安全的。

  • 下面的图表阐述了校验remember me cookie过程中涉及到的不同组件:


    Spring Security4实例(Java config 版) —— Remember-Me_第1张图片
    4faa6213-743c-3975-bd65-6f2974f44640
  • TokenBasedRememberMeServices官方文档的说明

This implementation supports the simpler approach described in Section 17.2, “Simple Hash-Based Token Approach”. TokenBasedRememberMeServices generates a RememberMeAuthenticationToken, which is processed by RememberMeAuthenticationProvider. A key is shared between this authentication provider and the TokenBasedRememberMeServices. In addition, TokenBasedRememberMeServices requires A UserDetailsService from which it can retrieve the username and password for signature comparison purposes...

The beans required in an application context to enable remember-me services are as follows:














Don’t forget to add your RememberMeServices implementation to your UsernamePasswordAuthenticationFilter.setRememberMeServices() property, include the RememberMeAuthenticationProvider in your AuthenticationManager.setProviders() list, and add RememberMeAuthenticationFilter into your FilterChainProxy (typically immediately after your UsernamePasswordAuthenticationFilter).

大致翻译如下:
这个实现类就对应于17.2章节描述的那个“Simple Hash-Based Token Approach”(就是上面的简单Hash-Based Token方式)。TokenBasedRememberMeServices(以下用“它”代替) 生成了一个RememberMeAuthenticationToken(它的抽象父类AbstractRememberMeServices中的方法createSuccessfulAuthentication实现的),以供RememberMeAuthenticationProvider处理。一个key被这个AuthenticationProvider和它共享使用。此外,它还需要一个UserDetailsService 用来获取用户名和密码进行签名对比...

这个bean需要进行如下配置:
见上xml

最后别忘了,需要配置UsernamePasswordAuthenticationFilter的rememberMeServices为我们定义好的TokenBasedRememberMeServices,把RememberMeAuthenticationProvider加入AuthenticationManager的providers列表,并添加RememberMeAuthenticationFilter和UsernamePasswordAuthenticationFilter到FilterChainProxy。(通常紧接着放在UsernamePasswordAuthenticationFilter后面)。

所以上面的配置好像跟这里描述的不太一样(不过效果倒是一样),接下来按照官方文档描述的修改一下配置(上面第1步不变,从第二步开启remember me配置开始修改):


2.注释掉rememberMe(),在myUsernamePasswordAuthenticationFilter后添加rememberMeAuthenticationFilter

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/", "/login/**").permitAll()
                // user权限可以访问的请求
                .antMatchers("/security/user").hasRole("user")
                // admin权限可以访问的请求
                .antMatchers("/security/admin").hasRole("admin")
                // SpEL表达式:需要拥有user权限,且进行了完全认证
                .antMatchers("/user/account").access("hasRole('user') and isFullyAuthenticated()")
                // 其他地址的访问均需验证权限(需要登录)
                .anyRequest().authenticated().and()
                // 添加验证码验证
                .addFilterAt(myUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class).exceptionHandling()
                .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login_page")).and()
                .addFilterAt(rememberMeAuthenticationFilter(),RememberMeAuthenticationFilter.class)
                // 指定登录页面的请求路径
                .formLogin().loginPage("/login_page")
                // 登陆处理路径
                .loginProcessingUrl("/login").permitAll().and()
                // 退出请求的默认路径为logout,下面改为signout,
                // 成功退出登录后的url可以用logoutSuccessUrl设置
                .logout().logoutUrl("/signout").logoutSuccessUrl("/login_page").permitAll().and()
                // 开启rememberMe,设置一个私钥专供testall项目使用,注意与下面TokenBasedRememberMeServices的key保持一致
                //.rememberMe().key("testallKey").and()
                // 关闭csrf
                .csrf().disable();
    }

3.上面的3、4步还是需要的,此外还要加上之前没有配置的rememberMeAuthenticationProvider和rememberMeAuthenticationFilter,下面就直接把相关代码都贴上了

@Bean
    public MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter() throws Exception {
        MyUsernamePasswordAuthenticationFilter myFilter = new MyUsernamePasswordAuthenticationFilter();
        myFilter.setAuthenticationManager(authenticationManagerBean());
        myFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
        myFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
        myFilter.setRememberMeServices(tokenBasedRememberMeServices());
        return myFilter;
    }

    @Bean
    public TokenBasedRememberMeServices tokenBasedRememberMeServices() {
        TokenBasedRememberMeServices tbrms = new TokenBasedRememberMeServices("testallKey", userDetailsServiceImpl());
        // 设置cookie过期时间为2天
        tbrms.setTokenValiditySeconds(60 * 60 * 24 * 2);
        // 设置checkbox的参数名为rememberMe(默认为remember-me),注意如果是ajax请求,参数名不是checkbox的name而是在ajax的data里
        tbrms.setParameter("rememberMe");
        return tbrms;
    }

    @Bean
    public RememberMeAuthenticationProvider rememberMeAuthenticationProvider() {
        RememberMeAuthenticationProvider rmap = new RememberMeAuthenticationProvider("testallKey");
        return rmap;
    }
    
    @Bean
    public RememberMeAuthenticationFilter rememberMeAuthenticationFilter() throws Exception {
        RememberMeAuthenticationFilter myFilter = new RememberMeAuthenticationFilter(authenticationManagerBean(), tokenBasedRememberMeServices());
        return myFilter;
    }

然后尴尬来了,remember是好使了,但是退出不清除cookie了,最后只找到TokenBasedRememberMeServices的logoutHandler没有被加到logoutFilter里(参考),至于为什么(或者哪里配置的不对)还不清楚,有知道的可以告知下,暂时还是使用之前的那种配置方式吧。

二、持久化 Token方式

待更新

你可能感兴趣的:(Spring Security4实例(Java config 版) —— Remember-Me)