Remember-Me 功能工作流程如下:
总结:remember-me 只有在 JSESSIONID 失效和前面的过滤器认证失败或者未进行认证时才发挥作用。此时,只要 remember-me 的 Cookie 不过期,我们就不需要填写登录表单,就能实现再次登录,并且 remember-me 自动登录成功之后,会生成新的 Token 替换旧的 Token,相应 Cookie 的 Max-Age 也会重置。
在用户选择“记住我”登录并成功认证后,Spring Security将默认会生成一个名为 remember-me 的 Cookie 存储 Token 并发送给浏览器;用户注销登录后,该 Cookie 的 Max-Age 会被设置为 0,即删除该 Cookie。Token 值由下列方式组合而成
base64(username + ":" + expirationTime + ":" + md5Hex(username + ":" + expirationTime + ":" + password + ":" + key))
http.remember()
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>\
<version>2.3.12.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
<version>2.3.12.RELEASEversion>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.3.4version>
dependency>
@Component
public class UserDetailServiceImpl implements UserDetailsService {
@Resource
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User(username, passwordEncoder.encode("123"), AuthorityUtils.NO_AUTHORITIES);
}
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.and()
.rememberMe()
// 指定在登录时“记住我”的 HTTP 参数,默认为 remember-me
.rememberMeCookieName("remember-me")
// 设置 Token 有效期为 15s,(默认是2周内免登录)
.tokenValiditySeconds(15)
.tokenRepository(new InMemoryTokenRepositoryImpl())
// 指定 UserDetailsService 对象
.userDetailsService(userDetailsService)
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
}
此时登录页中会出现**“记住我”**按钮,且提交表单后也有对应的参数信息,登录成功后会在浏览器中存储对应的cookie信息
Token与用户的对应关系是在内存中存储的,当我们重启应用之后所有的Token都将消失,即:所有的用户必须重新登陆。为此,Spring Security还给我们提供了一种将Token存储到数据库中的方式,重启应用也不受影响
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private DataSource dataSource;
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置记住密码
http.csrf().disable()
.formLogin()
.loginPage("/toLogin")
.loginProcessingUrl("/login")
.and()
.rememberMe()
// 修改请求参数名。 默认是remember-me
.rememberMeParameter("remember-me")
// 设置记住我有效时间。单位是秒。默认是14天
.tokenValiditySeconds(14*24*60*60)
// 修改remember me的cookie名称。默认是remember-me
.rememberMeCookieName("remember-me")
// 配置用户登录标记的持久化工具对象。
.tokenRepository(persistentTokenRepository)
// 配置自定义的UserDetailsService接口实现类对象
.userDetailsService(userDetailsService)
.and()
.authorizeRequests()
.antMatchers("/toLogin").permitAll()
.anyRequest()
.authenticated();
}
@Bean
public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
// 设置数据库
jdbcTokenRepository.setDataSource(dataSource);
// 是否启动项目时创建保存token信息的数据表
jdbcTokenRepository.setCreateTableOnStartup(false);
return jdbcTokenRepository;
}
}
注意:JdbcTokenRepositoryImpl中有建表语句
在cookie未失效之前,无论是重开浏览器或者重启项目,用户都无需再次登录就可以访问系统资源了
http.logout()
注意
Spring Security默认以 POST 方式请求访问/logout注销登录,以 POST 方式请求的原因是为了防止 csrf(跨站请求伪造),如果想使用 GET 方式的请求,则需要关闭 csrf 防护。
/**
* 继承 SimpleUrlLogoutSuccessHandler 处理器,该类是 logoutSuccessUrl() 方法使用的成功注销登录处理器
*/
@Component
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
String xRequestedWith = request.getHeader("x-requested-with");
// 判断前端的请求是否为 ajax 请求
if ("XMLHttpRequest".equals(xRequestedWith)) {
// 成功注销登录,响应 JSON 数据
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("注销登录成功");
}else {
// 以下配置等同于在 http.logout() 后配置 logoutSuccessUrl("/login/page?logout")
// 设置默认的重定向路径
super.setDefaultTargetUrl("/login/page?logout");
// 调用父类的 onLogoutSuccess() 方法
super.onLogoutSuccess(request, response, authentication);
}
}
}
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
//...
@Autowired
private CustomLogoutSuccessHandler logoutSuccessHandler; // 自定义成功注销登录处理器
//...
/**
* 定制基于 HTTP 请求的用户访问控制
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//...
// 开启注销登录功能
http.logout()
// 用户注销登录时访问的 url,默认为 /logout
.logoutUrl("/logout")
// 用户成功注销登录后重定向的地址,默认为 loginPage() + ?logout
//.logoutSuccessUrl("/login/page?logout")
// 不再使用 logoutSuccessUrl() 方法,使用自定义的成功注销登录处理器
.logoutSuccessHandler(logoutSuccessHandler)
// 指定用户注销登录时删除的 Cookie
.deleteCookies("JSESSIONID")
// 用户注销登录时是否立即清除用户的 Session,默认为 true
.invalidateHttpSession(true)
// 用户注销登录时是否立即清除用户认证信息 Authentication,默认为 true
.clearAuthentication(true);
}
//...
}