SpringSecurity实现Remember-Me实践

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: SpringSecurity实现Remember-Me实践

【1】基于会话技术的实现

也就是基于Cookie的实现,用户信息通过某种规则进行加密然后生成一个字符串设置为cookie。


① 登录页面

这里name="remember-me"表示“记住我”的复选框,默认key是remember-me

<form action="/user/login"  method="post">
    <input type="text" name="username" />
    <input type="text" name="password" />
    <input type="checkbox" name="remember-me" />
    <input type="submit" />
</form>

② 配置类开启记住我功能

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
    }
    @Bean
    PasswordEncoder password() {
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling().accessDeniedPage("/unauth.html");
        http.formLogin()
                .loginPage("/login.html") //登录页面
                .loginProcessingUrl("/user/login") // 处理登录的请求
                .defaultSuccessUrl("/test/index",true)// 登录成功后跳转路径
                .permitAll();
        http.authorizeRequests()
                .antMatchers("/static/**","/images/**","/css/**","/js/**")//可以直接放行
                .permitAll()
                .antMatchers("/findAll").hasAuthority("admin") // 用户访问findAll 必须有 admin 权限
                .antMatchers("/find").hasAnyAuthority("admin","sale") // 用户访问 find ,拥有admin或者sale之一即可
                .antMatchers("/sale/**").hasRole("sale") // 需要用户具有sale角色
                .antMatchers("/product/**").hasAnyRole("admin","product") //用户具有admin或者product角色之一即可
                .anyRequest().authenticated();//其他请求都需要认证
        http.csrf().disable(); 
    //开启记住我
        http.rememberMe();
    }
}


如下所示,提交表单时请求参数会带上remember-me: on

提交表单后响应头会设置cookie-remember-me,默认是14天有效期。其值加密规则如下所示:

   username + ":" + expiryTime + ":"
      + Md5Hex(username + ":" + expiryTime + ":" + password + ":" + key)

从这个规则也可以看出,如果用户修改了密码那么记住我功能将会自动失效。

得到signatureValue规则如下所示(Md5Hex(username + ":" + expiryTime + ":" + password + ":" + key)):


得到最终cookie值源码如下:

protected String encodeCookie(String[] cookieTokens) {
  StringBuilder sb = new StringBuilder();
  for(int i = 0; i < cookieTokens.length; ++i) {
      try {
          sb.append(URLEncoder.encode(cookieTokens[i], StandardCharsets.UTF_8.toString()));
      } catch (UnsupportedEncodingException var5) {
          this.logger.error(var5.getMessage(), var5);
      }
      if (i < cookieTokens.length - 1) {
          sb.append(":");
      }
  }
  String value = sb.toString();
  sb = new StringBuilder(new String(Base64.getEncoder().encode(value.getBytes())));
  while(sb.charAt(sb.length() - 1) == '=') {
      sb.deleteCharAt(sb.length() - 1);
  }
  return sb.toString();
}


.

此时关闭浏览器再重新打开,可直接访问受保护的请求(请求头的Cookie会带上remember-me)。


其本质就是根据某种规则生成一个加密串(串中有用户名和密码)设置为cookie,再次请求时带上该cookie。在autoLogin方法中会解密该cookie,然后根据解密中的用户信息与数据库的数据进行对比来实现自动登录。


原理部分可以查阅一下RememberMeConfigurer、TokenBasedRememberMeServices与AbstractRememberMeServices三个类。

AbstractRememberMeServices关于自动登录的处理逻辑如下:


这种方式无疑是不安全的,将用户信息通过某种加密规则生成字符串保存到浏览器是有几率被逆向的。故而spring官方推荐使用基于数据库的实现如PersistentTokenBasedRememberMeServices。

【2】基于数据库的实现

AbstractRememberMeServices的另外一个实现是PersistentTokenBasedRememberMeServices,也就是持久化token实现。其核心支撑PersistentTokenRepository又有两个实现:基于内存的InMemoryTokenRepositoryImpl和基于数据库的JdbcTokenRepositoryImpl。前者是通过维护了private final Map<String, PersistentRememberMeToken> seriesTokens = new HashMap();这样一个map来实现,后者则依赖于数据库表。


在其源码中可以看到,默认表名与列都已定义persistent_logins (username, series, token, last_used) ,并提供了创建表的策略(如果createTableOnStartup为true)。

如下所示,注入PersistentTokenRepository 。

@Autowired 
private DataSource dataSource; 
@Bean 
public PersistentTokenRepository persistentTokenRepository(){ 
  JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); 
  // 赋值数据源 
  jdbcTokenRepository.setDataSource(dataSource); 
  // 自动创建表,第一次执行会创建,以后要执行就要删除掉! 
  jdbcTokenRepository.setCreateTableOnStartup(true); 
  return jdbcTokenRepository; 
} 


开启记住我功能并设置tokenRepository。

@Autowired 
private PersistentTokenRepository tokenRepository;
// 开启记住我功能 
http.rememberMe() 
  .tokenRepository(tokenRepository) 
  .userDetailsService(usersService);
CREATE TABLE `persistent_logins` (
  `username` varchar(64) NOT NULL,
  `series` varchar(64) NOT NULL,
  `token` varchar(64) NOT NULL,
  `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

每次自动登录后,会根据series重新生成token并更新:

其原理示意图如下所示:


目录
相关文章
|
10月前
|
XML 安全 前端开发
SpringSecurity系列(四) Spring Security 实现权限树形菜单
SpringSecurity系列(四) Spring Security 实现权限树形菜单
105 0
|
安全 Java 数据库
SpringSecurity 入门
Spring Security是Spring采用 `AOP`思想,基于 `servlet过滤器`实现的安全框架。它提供了完善的**认证机制**和**方法级的授权功能**。是一款非常优秀的权限管理框架。
103 0
|
安全 API 测试技术
shiro实战系列(十)之Subject
毫无疑问,在 Apache Shiro 中最重要的概念就是 Subject。'Subject'仅仅是一个安全术语,是指应用程序用户的特定 安全的“视图”。一个 Shiro Subject 实例代表了一个单一应用程序用户的安全状态和操作。
1779 0
|
安全 Java 数据库
SpringSecurity入门
SpringSecurity入门
119 0
|
前端开发 安全 Java
一文弄懂spring validate(上)
一文弄懂spring validate(上)
1274 0
|
前端开发 安全 Java
一文弄懂spring validate
校验参数在以前基本都是使用大量的if/else,稍微方便一点的可以使用反射+自定义注解的形式,但是复用性不是很好,并且每个人对于的自定义注解有着自己的使用习惯,不过好在spring开发了validated框架用于注解校验,可以节省很多的校验ifelse代码,这篇文章通篇介绍了如何使用spring validated。
494 0
|
Java Apache Spring
Apache Shiro In Easy Steps With Spring Boot(二)-Authenticator,Authorizer,Subject
Apache Shiro In Easy Steps With Spring Boot(二)-Authenticator,Authorizer,Subject
Apache Shiro In Easy Steps With Spring Boot(二)-Authenticator,Authorizer,Subject
|
存储 安全 Java
Spring Security-Remember Me功能实现
Spring Security-Remember Me功能实现
|
Java 网络安全 数据安全/隐私保护
【SSH快速进阶】——struts2的模型驱动—ModelDriven
上篇博客《SSH快速进阶——struts2简单的实例》中,处理用户登陆的action—LoginAction为:
【SSH快速进阶】——struts2的模型驱动—ModelDriven