Spring Security Remember-me配置

scio

https://github.com/rench/scio

Remember Me 记住我

介绍

  • Spring Security框架中提供了一种基于token的记住我的功能,一般的记住我功能主要用于用户在当前浏览器中首次输入密码后,即可在以后指定的一段时间内,不用输入密码即可访问受保护的数据短点,即使session失效。

实现

  • 记住的我实现是基于浏览器对于cookie的持久化功能,在用户首次勾选记住我登录时,服务端基于当前用户登录信息构造一个加密后的token,该token在Spring中的实现主要有两种:
  1. TokenBasedRememberMeServices 生成cookie中包含用户名的token,每次请求从带入的cookie中获取username,然后根据username从userDetailsService中获取数据生成token进行比较。生成的方式如下:
username + ":" + expiryTime + ":"
        + Md5Hex(username + ":" + expiryTime + ":" + password + ":" + key)
  1. PersistentTokenBasedRememberMeServices 生成的cookie中不包含用户名,包含series和token,并存储于数据库中,同时,存储的数据中还包含了用户名和token最近访问时间。每次请求根据带入cookie中的series和token,查询数据库中的数据进行token比对,同时调用userDetailsService进行用户的状态校验。

PersistentTokenRepositoryPersistentTokenBasedRememberMeServices中存储实现。默认是InMemoryTokenRepositoryImpl,框架中附带了JdbcTokenRepositoryImpl实现,可以根据自己的实际情况增加实现,比如redis等。

  1. 基于TokenBasedRememberMeServices,可以进行扩展,对cookie进行加密等操作,默认的cookie(mp1:1554694664576:801c3f24109a468dfbeb7cf09cac0531)没有加密用户名,只是进行base64编码,可能存在泄漏的风险。

配置

  • SecurityConfig
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ScioRememberMeSecurityConfig extends WebSecurityConfigurerAdapter {
  private static final String SECRET = "scio@2019";
  @Autowired private UserDetailsService scioUserDetailsService;
  /**
   * password encoder
   *
   * @return
   */
  @Bean
  public PasswordEncoder passwordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
  }

  /**
   * RememberMeAuthenticationProvider.
   *
   * @return
   */
  @Bean
  public RememberMeAuthenticationProvider rememberMeAuthenticationProvider() {
    return new RememberMeAuthenticationProvider(SECRET);
  }

  /**
   * TokenBasedRememberMeServices.
   *
   * @return
   */
  @Bean("tokenBaseRememberMeServices")
  public TokenBasedRememberMeServices tokenBasedRememberMeServices() {
    TokenBasedRememberMeServices rememberMeServices =
        new TokenBasedRememberMeServices(SECRET, scioUserDetailsService);
    rememberMeServices.setAlwaysRemember(false);
    rememberMeServices.setCookieName("remember-me");
    rememberMeServices.setTokenValiditySeconds(AbstractRememberMeServices.TWO_WEEKS_S);
    return rememberMeServices;
  }

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(scioUserDetailsService).passwordEncoder(passwordEncoder());
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    // http.antMatcher("/api/**");
    // .antMatchers("/api/login", "/api/logout")
    http.csrf()
        .disable()
        .sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
    // permit login
    http.formLogin()
        .permitAll()
        .defaultSuccessUrl("/info")
        .and()
        .authorizeRequests()
        .anyRequest()
        .authenticated();
    http.rememberMe()
        // tokenBased, store token and user name in token data.
        .rememberMeServices(tokenBasedRememberMeServices())
        // .tokenRepository(new InMemoryTokenRepositoryImpl())
        // persistent serials/token, invalidate after delete from token store
        .and()
        .authenticationProvider(rememberMeAuthenticationProvider());
    http.headers().cacheControl();
  }
}
  • ScioUserDetailsService
/**
 * mock scio users
 *
 * @author Wang.ch
 * @date 2019-03-25 09:05:21
 */
@Configuration
public class ScioUserDetailsService implements UserDetailsService {
  /** mock users */
  private Map users = new HashMap<>();

  public ScioUserDetailsService() {
    users.put("mp1", "{noop}mp1");
    users.put("mp2", "{noop}mp2");
  }

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    if (users.containsKey(username)) {
      String noopPwd = users.get(username);
      User u = new User(username, noopPwd, Arrays.asList(new SimpleGrantedAuthority("USER")));
      return u;
    } else {
      throw new UsernameNotFoundException("user not found");
    }
  }
}

使用

  • 配置中已开启formLogin,直接访问http://localhost:8006/login,跳转到登录界面
  • 输入配这中mock的用户名和密码,mp1:mp1,勾选中Remember me on this computer,提交。
  • 观察返回的cookie值 Cookie: remember-me=bXAxOjE1NTQ3NjkxNDUwNjE6YjBmNWM4Nzk1NzVjOWMzNWIxNjhkZDJlMDg0MTliZWM; JSESSIONID=B490108E0CFE931DC624C43DCCD02D94 ,多了一个remember-me,这就是返回的token,他有一个有效时间,在关闭浏览器后重新访问 http://localhost:8006/info,依然可以访问当前登录用户的信息。
  • 跟踪 TokenBasedRememberMeServices 源码进去,能够发现会从cookie中解析remember-me字段,并对值进行解析,然后对用户信息进行匹配,然后构造 RememberMeAuthenticationToken 返回,然后调用 authenticationManager.authenticate 进行授权,使用的实例是RememberMeAuthenticationProvider

你可能感兴趣的:(Spring Security Remember-me配置)