美文网首页
SpringSecurity开发基于表单的认证(六)

SpringSecurity开发基于表单的认证(六)

作者: 云师兄 | 来源:发表于2017-12-24 09:28 被阅读118次

    SpringSecurity之记住我功能

    • 记住我功能基本原理
    • 记住我功能具体实现
    • 记住我功能SpringSecurity源码分析

    记住我功能基本原理

    image.png image.png

    这篇文章有停留两周的时间没有完成了,工作忙是一方面,另一方面是由于对spring security的理解混乱,导致出现问题解决时没有头绪,为此,阅读了《spring 实战》一书中的spring security章节后才逐渐思路清晰。下面接着对这篇文章进行补充。

    首先从头对spring security的配置进行注释和整理,帮助阅读代码:

    @Configuration
    @EnableWebSecurity
    public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter{
    
        @Autowired 
        SecurityProperties securityProperties;
        
        @Autowired
        private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
        
        @Autowired
        private MyAuthenticationFailHandler myAuthenticationFailHandler;
        
        @Bean
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
        
        @Autowired
        private DataSource dataSource;
        
        @Autowired
        private UserDetailsService userDetailsService;
        
        @Bean
        public PersistentTokenRepository persistenceTokenRepository(){
            JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
            tokenRepository.setDataSource(dataSource);
            //tokenRepository.setCreateTableOnStartup(true);
            return tokenRepository;
        }
        
        /*
         * 对每个请求进行细粒度安全性控制的关键在于重载configure(HttpSecurity)方法
         * 
         * @see org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#configure(org.springframework.security.config.annotation.web.builders.HttpSecurity)
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception{
            ValidateCodeFilter validateCodeFilter =new ValidateCodeFilter();
    
            validateCodeFilter.setAuthenticationFailureHandler(myAuthenticationFailHandler);
            
            http
            
                /*
                 * 在UsernamePasswordAuthenticationFilter过滤器之前添加短信验证码过滤器
                 */
                .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                
                /*
                 * 表单登录适用于浏览器向restful api发起请求;如果是后台程序直接发起请求访问restful api,则设为HTTP BASIC模式
                 */
                .formLogin()
                .loginPage("/login.html") //跳转的登录页/authentication/require
                .loginProcessingUrl("/authentication/form") //登录时的请求
                .successHandler(myAuthenticationSuccessHandler) //表单登录成功时使用我们自己写的处理类
                .failureHandler(myAuthenticationFailHandler) //表单登录失败时使用我们自己写的处理类
                .and()
                
                /*
                 * 调用rememberMe()方法启用记住我功能,通过在cookie中存储一个token完成
                 */
                .rememberMe()
                //指定保存token的仓库,此处实现为保存到指定的数据库中
                .tokenRepository(persistenceTokenRepository())
                //tokenValiditySeconds()指定token的有效时间
                .tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())
                //指定登录用户、密码和权限
                .userDetailsService(userDetailsService) 
                .and()
                
                /*
                 * 调用HttpSecurity类的authorizeRequests()方法所返回的对象的方法来配置请求级别的安全性细节
                 */
                .authorizeRequests()
                //antMatchers()对指定路径的请求需要进行认证,这个方法以ant开头表示路径支持Ant风格的通配符,permitAll()方法运行请求没有任何的安全限制
                .antMatchers("login.html","/code/image").permitAll()
                //anyRequest()指定了匹配之外的所有请求,authenticated()要求在执行该请求时,必须已经登录了应用
                .anyRequest().authenticated()
                .and()
                
                /*
                 * 禁用CSRF(跨站请求伪造)防护功能
                 */
                .csrf().disable();
        }
    

    web安全配置类中的configure方法是配置入口点,每一项配置都应该按照上面的写法进行分类,每种配置之间都通过and方法进行连接,如以".formLogin()"开头的是表单登录相关的配置,以".rememberMe()"开头的是记住我功能的相关配置,以".authorizeRequests()"开头的是请求的安全配置。我们先回顾一下第三项以authorizeRequests()开头的请求配置。
    请求配置主要分两个方面:
    1.要配置的请求的路径是什么?为此可以使用antMatchers()方法来指定路径;而往往指定了特定的路径后,在最后使用anyRequest()表示的是除以上以外的所有请求。
    2.指定路径之后,这个路径所代表的请求该如何保护。如permitAll()方法表明对应的路径没有任何的安全限制,可以直接方法;authenticated()方法则表示只有认证成功以后才可以访问指定路径。
    根据以上两点,上述代码中的这段请求安全配置可以理解为:请求路径为"login.html"和"/code/image"可以直接访问,没有安全限制,而除此之外的其他请求必须在身份认证通过以后才能访问。

    在理解了请求的安全配置后,我们再来看记住我功能是怎么实现的。记住我的原理是在第一次登陆成功后,生成一份保存用户信息的token,一方面将这个token保存到指定的数据库中,另一方面将这个token保存到浏览器的cookie中。这样即使以后session丢失(如服务器重启)而浏览器的cookie还在,再次访问时仍可以通过cookie中的token来直接访问具体的restful api,而不需要重新进行表单登录。这就实现了记住我的功能。

    下面我们来演示一下,首先我们已经写好一个具体的restful api:

        @GetMapping("/user")
        public void helloWorld(HttpServletRequest request,HttpServletResponse response) throws Exception{
            System.out.println("hello world");
        }
    

    然后我们访问表单登录页面,http://localhost:8080/login.html,截图如下:

    login.png
    填写完正确的用户名和密码和验证码之后,点击登录,登录成功后,重新访问http://localhost:8080/user
    这时就可以成功输出hello world了。但这只能说明登录成功后,usernamepassworldauthentication filter将登录信息保存到session中了,如果我们重启服务后再直接访问http://localhost:8080/user时,就不能直接访问到restful api了,而是重定向到登录页面,因为重启了以后session就不见了,但是我们加了记住我的配置后,即使重启应用后直接访问http://localhost:8080/user也能成功,这就是因为用户信息是保存到浏览器cookie中,只要cookie中存在用户信息就可以直接访问成功,而不需要先表单登录来进行身份认证。

    相关文章

      网友评论

          本文标题:SpringSecurity开发基于表单的认证(六)

          本文链接:https://www.haomeiwen.com/subject/obvcgxtx.html