Spring Security实战(三)—— 自动登录与注销登录

  • Post author:
  • Post category:其他



目录


一、实现自动登录


1. 散列加密方案


2. 持久化令牌方案


二、注销登录


一、实现自动登录

自动登录是将用户的登录信息保存在用户浏览器的cookie中,当用户下次访问时,自动实现校验并建立登录态的一种机制。

Spring Security 提供了两种非常好的令牌:

  • 用散列算法加密用户必要的登录信息并生成令牌
  • 数据库等持久性数据存储机制用的持久化令牌

1. 散列加密方案

(1)增加自动登录功能

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(new CaptchaFilter(),UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                //开放验证码的访问权限
                .antMatchers("/captcha.jpg").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                //记住我
                .rememberMe().userDetailsService(userDetailsService);
    }

(2)在登录页中增加remember me复选框

<label><input type="checkbox" name="remember-me" id="remember-me">Remember Me</label>

(3)按照正常流程登录

在浏览器的开发者工具中查看cookie,可以看到多了一个 remember-me,这就是Spring Security默认自动登录的cookie字段。在不配置的情况下,过期时间是两个星期。

但是现在会有一个问题,每次重启服务后,这个KEY(remember-me)都会重新生成,所以每次重启服务我们还需要再进行登录,这就就谈不上自动登录了。


合理的用法是指定KEY。如:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(new CaptchaFilter(),UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                //开放验证码的访问权限
                .antMatchers("/captcha.jpg").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .rememberMe().userDetailsService(userDetailsService)
        .key("zy");//指定key
    }

这样,重启服务之后登录后,就算再一次重启服务也不需要再登录,这就实现了自动登录功能。

2. 持久化令牌方案

持久化令牌方案在交互上与散列加密方案一致,都是在用户勾选 Remember-me之后,将生成的令牌发送到用户浏览器,并在用户下次访问系统时读取该令牌进行认证。不同的是,它采用更加严谨的安全性设计。

在持久化令牌方案中,最核心的是

series



token

两个值,它们都是用MD5散列过的随机字符串。不同的是,series仅在用户使用密码重新登陆时更新,而token会在每一个新的session中都重新生成。

这样设计的

好处

首先,解决了散列加密方案中一个令牌可以同时在多端登录的问题。每个会话都会引发token的更新,即

每个token仅支持单实例登录

其次,自动登录不会导致series变更,而每次自动登录都需要同时验证series和token两个值,当该令牌还未使用过自动登陆就被盗取时,系统会在非法用户验证通过后刷新token值,此时在合法用户的浏览器中,该token值已经失效。当合法用户使用自动登录时,由于该series对应的token不同,系统可以推断该令牌可能已经被盗用,从而做出一些处理。如清理该用户的所有自动登录的令牌,并通知该用户可能已经被盗号。

(1)在数据库新建一张 persistent_logins表

CREATE TABLE persistent_logins (
    username VARCHAR(64) NOT NULL,
    series VARCHAR(64) NOT NULL,
    token VARCHAR(64) NOT NULL,
    last_used TIMESTAMP NOT NULL,
    PRIMARY KEY (series)
);

(2)配置spring security的Remember-me功能

由于需要使用持久化令牌方案,所以定制 tokenRepository,

tokenRepository() 括号里需要传入一个PersistentTokenRepository实例

,这里我们使用JdbcTokenRepositoryImpl,JdbcTokenRepositoryImpl 是基于DataSource实现对应SQL操作的类,所以我们需要指定DataSource。

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //指定dataSource
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        http
                .addFilterBefore(new CaptchaFilter(),UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                //开放验证码的访问权限
                .antMatchers("/captcha.jpg").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .rememberMe()
                .userDetailsService(userDetailsService)
        .tokenRepository(jdbcTokenRepository);
    }

(3)重启服务

重启服务并登录之后,再次重启服务,可以实现自动登录

经base64解码:

JSESSIONID=E76686FEE7637DEB4F1E5C9E8A930A6F;
Idea-b5baabb9=16077984-579b-44d2-a1a2-92f5837858ef;
remember-me=1K3%24%2FLnZb590SKpZlTlpHg%3D%3D:eduptdyzPFt8x2wQI2QLA==

冒号前的部分是series,冒号后的部分是token。当自动登录认证时,Spring Security通过series获取用户名、token、以及上一次自动登陆时间三个信息。

  • 通过用户名确认该令牌的身份
  • 通过对比token获知该令牌是否有效
  • 通过上一次自动登录的时间获知该令牌是否过期

在完整校验通过之后生成新的token。

(4)查看数据库信息

二、注销登录

认证系统往往都带有注销登录功能,Spring Security 也提供了这方面的支持。事实上,从我们编写配置类继承 WebSecurityConfigurerAdapter 的那一刻起,Spring Security 就已经为我们的系统埋入了注销的逻辑。

HttpSecurity 内的 logout() 方法以一个 LogoutConfigurer 作为配置基础,创建一个用于注销登录的过滤器。

它默认注册了一个 /logout 路由,用户通过访问该路由可以安全地注销其登录状态,包括

使HttpSession 失效、清空已配置的 Remember-me 验证,以及清空 SecurityContextHolder

,并在注销成功之后重定向到 /login?logout页面。

也可以自定义配置,如:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //指定dataSource
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        http
                .addFilterBefore(new CaptchaFilter(),UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                //开放验证码的访问权限
                .antMatchers("/captcha.jpg").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .rememberMe()
                .userDetailsService(userDetailsService)
        .tokenRepository(jdbcTokenRepository)
        .and()
        .logout()
        .logoutUrl("/myLogout")//配置接收注销请求的路由
        .logoutSuccessUrl("/login.html")//注销成功后跳转到 login.html页面
        .logoutSuccessHandler(new LogoutSuccessHandler() {
            @Override
            public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                System.out.println("用户成功退出");
            }
        })
        .invalidateHttpSession(true) //使用户的HttpSession失效
        .deleteCookies("cookie1","cookie2") //注销成功 删除指定的cookie
        .addLogoutHandler(new LogoutHandler() {
            @Override
            public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
                System.out.println("已经注销了!");
            }
        });
    }

实际上,logout的清理过程是由多个LogoutHandler流式处理的。



版权声明:本文为weixin_49561506原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。