【Spring-Security】Re07 持久化的记住我

Security记住我功能底层实现依赖于SpringJDBC组件,如果有持久层框架的话,就由持久层框架实现

演示案例的选型,MysqlJdbc + MybatisStarter

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.2</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

如果是不同的数据库类型,就更换不同的驱动,链接参数的要求是一致的

数据源没有配置,默认使用了自带的Hikari

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3308/spring-security?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=true
    username: root
    password: 123456

配置类的更改:

package cn.zeal4j.configuration;

import cn.zeal4j.handler.CustomAccessDeniedHandler;
import cn.zeal4j.handler.FarsAuthenticationFailureHandler;
import cn.zeal4j.handler.FarsAuthenticationSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.parameters.P;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

/**
 * @author Administrator
 * @file IntelliJ IDEA Spring-Security-Tutorial
 * @create 2020 09 27 21:55
 */
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private AccessDeniedHandler accessDeniedHandler;
    @Qualifier("userDetailsServiceImpl")
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private PersistentTokenRepository persistentTokenRepository;

    @Bean
    public PersistentTokenRepository getPersistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource); // 数据源注入
        jdbcTokenRepository.setCreateTableOnStartup(true); // 由Security完成Token表的创建
        return jdbcTokenRepository;
    }

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.formLogin(). // 设置登陆行为方式为表单登陆
                // 登陆请求参数设置
                usernameParameter("username").
                passwordParameter("password").
                
                loginPage("/login.html"). // 设置登陆页面URL路径
                loginProcessingUrl("/login.action"). // 设置表单提交URL路径
                
                successForwardUrl("/main.page"). // 设置认证成功跳转URL路径 POST请求
                failureForwardUrl("/error.page");  // 设置认证失败跳转URL路径 POST请求
                
                //  successHandler(new FarsAuthenticationSuccessHandler("https://www.acfun.cn/")). // 使用自定义的重定向登陆
                //  failureHandler(new FarsAuthenticationFailureHandler("/error.html")).; // 跨域处理,不需要跳转了

        httpSecurity.authorizeRequests().
                regexMatchers(HttpMethod.POST, "正则表达式").permitAll(). // 还可以对符合正则表达式的请求方式进行要求,这个属性使用来制定请求的方式
                
                antMatchers("/**/*.js", "/**/*.css", "/**/images/*.*").permitAll(). // 静态资源放行
                
                antMatchers("/login.html").permitAll(). // 登陆页面允许任意访问
                antMatchers("/error.html").permitAll(). // 失败跳转后重定向的页面也需要被允许访问
                antMatchers("/admin.page").hasAnyAuthority("admin").
                
                /*antMatchers("/vip-01.page").hasAnyAuthority("vip-01").*/
                antMatchers("/vip-01.page").hasRole("vip-01").
                antMatchers("/ip.page").hasIpAddress("192.168.43.180").
                
                // mvcMatchers("/main.page").servletPath("/xxx").permitAll(). // mvcMatchers资源放行匹配
                // antMatchers("/xxx/main.page").permitAll(). // 或者多写MSP的前缀
                
                anyRequest().authenticated(); // 其他请求均需要被授权访问
                // anyRequest().access("@customServiceImpl.hasPermission(request, authentication)"); // 自定义Access配置

        // CSRF攻击拦截关闭
        httpSecurity.csrf().disable();
        httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler);


        // 记住我
        httpSecurity.rememberMe().userDetailsService(userDetailsService).tokenRepository(persistentTokenRepository);
    }
}

在之前的登陆页面中编写记住我选项框:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style type="text/css">
        h3,p {
            text-align: center;
        }
    </style>
</head>
<body>
    <h3>custom login page</h3>
    <form method="post" action="/login.action" >
        <p>username: <input type="text" name="username"></p>
        <p>password: <input type="password" name="password"></p>
        <p>rememberMe: <input type="checkbox" name="remember-me" value="true"></p>
        <p><input type="submit" value="login"></p>
    </form>
</body>
</html>

现在启动项目,会发现数据库多了一张persistent_logins表

登陆访问勾选记住我,会插入用户的令牌和信息

这时候关闭浏览器再打开,不需要验证就可以直接到首页了

持续时间是永久的,因为Token表的记录一直存在,除非记录被删除

或者调用HttpSecurity的方法,设置失效时间:

package cn.zeal4j.configuration;

import cn.zeal4j.handler.CustomAccessDeniedHandler;
import cn.zeal4j.handler.FarsAuthenticationFailureHandler;
import cn.zeal4j.handler.FarsAuthenticationSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.parameters.P;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

/**
 * @author Administrator
 * @file IntelliJ IDEA Spring-Security-Tutorial
 * @create 2020 09 27 21:55
 */
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private AccessDeniedHandler accessDeniedHandler;
    @Qualifier("userDetailsServiceImpl")
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private PersistentTokenRepository persistentTokenRepository;

    @Bean
    public PersistentTokenRepository getPersistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource); // 数据源注入
        jdbcTokenRepository.setCreateTableOnStartup(true); // 由Security完成Token表的创建
        return jdbcTokenRepository;
    }

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.formLogin(). // 设置登陆行为方式为表单登陆
                // 登陆请求参数设置
                usernameParameter("username").
                passwordParameter("password").

                loginPage("/login.html"). // 设置登陆页面URL路径
                loginProcessingUrl("/login.action"). // 设置表单提交URL路径

                successForwardUrl("/main.page"). // 设置认证成功跳转URL路径 POST请求
                failureForwardUrl("/error.page");  // 设置认证失败跳转URL路径 POST请求

                //  successHandler(new FarsAuthenticationSuccessHandler("https://www.acfun.cn/")). // 使用自定义的重定向登陆
                //  failureHandler(new FarsAuthenticationFailureHandler("/error.html")).; // 跨域处理,不需要跳转了

        httpSecurity.authorizeRequests().
                regexMatchers(HttpMethod.POST, "正则表达式").permitAll(). // 还可以对符合正则表达式的请求方式进行要求,这个属性使用来制定请求的方式

                antMatchers("/**/*.js", "/**/*.css", "/**/images/*.*").permitAll(). // 静态资源放行

                antMatchers("/login.html").permitAll(). // 登陆页面允许任意访问
                antMatchers("/error.html").permitAll(). // 失败跳转后重定向的页面也需要被允许访问
                antMatchers("/admin.page").hasAnyAuthority("admin").

                /*antMatchers("/vip-01.page").hasAnyAuthority("vip-01").*/
                antMatchers("/vip-01.page").hasRole("vip-01").
                antMatchers("/ip.page").hasIpAddress("192.168.43.180").

                // mvcMatchers("/main.page").servletPath("/xxx").permitAll(). // mvcMatchers资源放行匹配
                // antMatchers("/xxx/main.page").permitAll(). // 或者多写MSP的前缀

                anyRequest().authenticated(); // 其他请求均需要被授权访问
                // anyRequest().access("@customServiceImpl.hasPermission(request, authentication)"); // 自定义Access配置

        // CSRF攻击拦截关闭
        httpSecurity.csrf().disable();
        httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler);


        // 记住我
        httpSecurity.rememberMe().
                tokenValiditySeconds(60). // 设置Token有效时间, 以秒为单位取值
                userDetailsService(userDetailsService).
                tokenRepository(persistentTokenRepository);
    }
}

除了使用持久化Token管理,另外一个是MemToken就是保存在内存中,应用程序来管理的

因为Token持久化接口仅有这两个实现,或者我们也可以自己写实现:

数据表的生成在Jdbc令牌持久化接口实现类中定义好了SQL语句,自动管理:

侧重点还需要这三个Bean的注入

原文地址:https://www.cnblogs.com/mindzone/p/13747690.html