【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的注入