Spring Security Config : HttpSecurity安全配置器 RememberMeConfigurer

概述

介绍

作为一个配置HttpSecuritySecurityConfigurer,RememberMeConfigurer的配置任务如下 :

  • 配置如下安全过滤器Filter
    • RememberMeAuthenticationFilter
      • 属性authenticationManager使用共享对象AuthenticationManager
      • 属性rememberMeServices 来自
        • 外部设置的RememberMeServices对象
        • 或者缺省创建的RememberMeServices对象

外部不指定RememberMeServices时,缺省RememberMeServices的创建过程如下 :

  1. 如果外部设定了 tokenRepository, 则创建的是一个 PersistentTokenBasedRememberMeServices 对象;
  2. 如果外部没有设定 tokenRepository, 则创建的是一个 TokenBasedRememberMeServices 对象;

缺省情况下,RememberMeConfigurer的属性rememberMeServices,tokenRepository都未设置。所以缺省使用的RememberMeServices会是一个自己创建的TokenBasedRememberMeServices

继承关系

Spring Security Config : HttpSecurity安全配置器 RememberMeConfigurer

使用

    
	// HttpSecurity 安全构建器代码片段
	public RememberMeConfigurer<HttpSecurity> rememberMe() throws Exception {
		return getOrApply(new RememberMeConfigurer<>());
	}

源代码

源代码版本 Spring Security Config 5.1.4.RELEASE

package org.springframework.security.config.annotation.web.configurers;

// 省略 imports


public final class RememberMeConfigurer<H extends HttpSecurityBuilder<H>>
		extends AbstractHttpConfigurer<RememberMeConfigurer<H>, H> {
	/**
	 * The default name for remember me parameter name and remember me cookie name
	 */
	private static final String DEFAULT_REMEMBER_ME_NAME = "remember-me";
	private AuthenticationSuccessHandler authenticationSuccessHandler;
	private String key;
	private RememberMeServices rememberMeServices;
	private LogoutHandler logoutHandler;
	private String rememberMeParameter = DEFAULT_REMEMBER_ME_NAME;
	private String rememberMeCookieName = DEFAULT_REMEMBER_ME_NAME;
	private String rememberMeCookieDomain;
	private PersistentTokenRepository tokenRepository;
	private UserDetailsService userDetailsService;
	private Integer tokenValiditySeconds;
	private Boolean useSecureCookie;
	private Boolean alwaysRemember;

	/**
	 * Creates a new instance
	 */
	public RememberMeConfigurer() {
	}

	/**
	 * Allows specifying how long (in seconds) a token is valid for
	 * 设置 Remember-Me 认证令牌的有效时长,单位:秒
	 * @param tokenValiditySeconds
	 * @return RememberMeConfigurer for further customization
	 * @see AbstractRememberMeServices#setTokenValiditySeconds(int)
	 */
	public RememberMeConfigurer<H> tokenValiditySeconds(int tokenValiditySeconds) {
		this.tokenValiditySeconds = tokenValiditySeconds;
		return this;
	}

	/**
	 * Whether the cookie should be flagged as secure or not. Secure cookies can only be
	 * sent over an HTTPS connection and thus cannot be accidentally submitted over HTTP
	 * where they could be intercepted.
	 *
     * Remember-Me cookie 是否需要被标记为安全cookie。安全cookie只能通过HTTPS连接传输,
     * 不能通过 HTTP 连接传输以避免它们被拦截。
     *
	 * By default the cookie will be secure if the request is secure. If you only want to
	 * use remember-me over HTTPS (recommended) you should set this property to
	 * true.
	 *
     * 缺省情况下如果请求是安全的,cookie也是安全的。如果你只想在HTTPS中使用remember-me(推荐方案),
     * 你应该将此标志设置为 true
	 * @param useSecureCookie set to true to always user secure cookies,
	 * false to disable their use.
	 * @return the RememberMeConfigurer for further customization
	 * @see AbstractRememberMeServices#setUseSecureCookie(boolean)
	 */
	public RememberMeConfigurer<H> useSecureCookie(boolean useSecureCookie) {
		this.useSecureCookie = useSecureCookie;
		return this;
	}

	/**
	 * Specifies the UserDetailsService used to look up the UserDetails
	 * when a remember me token is valid. The default is to use the
	 * UserDetailsService found by invoking
	 * HttpSecurity#getSharedObject(Class) which is set when using
	 * WebSecurityConfigurerAdapter#configure(AuthenticationManagerBuilder).
	 * Alternatively, one can populate #rememberMeServices(RememberMeServices).
	 *
     * 设置查询 UserDetails 属性需要使用的 UserDetailsService。
     * 缺省情况下使用 HttpSecurity 安全对象池中的 UserDetailsService
     * (WebSecurityConfigurerAdapter#configure(AuthenticationManagerBuilder)配置时设置),
     * 不过用户可以通过该方法指定一个其他的 UserDetailsService
	 * @param userDetailsService the UserDetailsService to configure
	 * @return the RememberMeConfigurer for further customization
	 * @see AbstractRememberMeServices
	 */
	public RememberMeConfigurer<H> userDetailsService(
			UserDetailsService userDetailsService) {
		this.userDetailsService = userDetailsService;
		return this;
	}

	/**
	 * Specifies the PersistentTokenRepository to use. The default is to use
	 * TokenBasedRememberMeServices instead.
	 *
     * 指定要使用的 PersistentTokenRepository, 缺省使用的 PersistentTokenRepository
     * 是一个 TokenBasedRememberMeServices
	 * @param tokenRepository the PersistentTokenRepository to use
	 * @return the RememberMeConfigurer for further customization
	 */
	public RememberMeConfigurer<H> tokenRepository(
			PersistentTokenRepository tokenRepository) {
		this.tokenRepository = tokenRepository;
		return this;
	}

	/**
	 * Sets the key to identify tokens created for remember me authentication. Default is
	 * a secure randomly generated key.
	 *
	 * @param key the key to identify tokens created for remember me authentication
	 * @return the RememberMeConfigurer for further customization
	 */
	public RememberMeConfigurer<H> key(String key) {
		this.key = key;
		return this;
	}

	/**
	 * The HTTP parameter used to indicate to remember the user at time of login.
	 *
	 * @param rememberMeParameter the HTTP parameter used to indicate to remember the user
	 * @return the RememberMeConfigurer for further customization
	 */
	public RememberMeConfigurer<H> rememberMeParameter(String rememberMeParameter) {
		this.rememberMeParameter = rememberMeParameter;
		return this;
	}

	/**
	 * The name of cookie which store the token for remember me authentication. Defaults
	 * to 'remember-me'.
	 *
	 * @param rememberMeCookieName the name of cookie which store the token for remember
	 * me authentication
	 * @return the RememberMeConfigurer for further customization
	 * @since 4.0.1
	 */
	public RememberMeConfigurer<H> rememberMeCookieName(String rememberMeCookieName) {
		this.rememberMeCookieName = rememberMeCookieName;
		return this;
	}

	/**
	 * The domain name within which the remember me cookie is visible.
	 *
	 * @param rememberMeCookieDomain the domain name within which the remember me cookie
	 * is visible.
	 * @return the {@link RememberMeConfigurer} for further customization
	 * @since 4.1.0
	 */
	public RememberMeConfigurer<H> rememberMeCookieDomain(String rememberMeCookieDomain) {
		this.rememberMeCookieDomain = rememberMeCookieDomain;
		return this;
	}

	/**
	 * Allows control over the destination a remembered user is sent to when they are
	 * successfully authenticated. By default, the filter will just allow the current
	 * request to proceed, but if an AuthenticationSuccessHandler is set, it will
	 * be invoked and the doFilter() method will return immediately, thus allowing
	 * the application to redirect the user to a specific URL, regardless of what the
	 * original request was for.
	 *
     * 设置被记忆的用户 Remember-Me 认证通过时的认证处理器。通过该认证处理器,可以
     * 控制 Remember-Me 认证通过时将用户跳转到哪个页面。缺省情况下,目标过滤器
     * RememberMeAuthenticationFilter 只是放行当前请求。一旦通过该方法设置了
     * AuthenticationSuccessHandler,该AuthenticationSuccessHandler在认证成功时会被
     * 调用
	 * @param authenticationSuccessHandler the strategy to invoke immediately before
	 * returning from doFilter().
	 * @return RememberMeConfigurer for further customization
	 * @see RememberMeAuthenticationFilter#setAuthenticationSuccessHandler(AuthenticationSuccessHandler)
	 */
	public RememberMeConfigurer<H> authenticationSuccessHandler(
			AuthenticationSuccessHandler authenticationSuccessHandler) {
		this.authenticationSuccessHandler = authenticationSuccessHandler;
		return this;
	}

	/**
	 * Specify the RememberMeServices to use.
     * 外部可以指定一个要使用的 RememberMeServices,如果不指定,当前配置器对象会自己创建一个
	 * @param rememberMeServices the RememberMeServices to use
	 * @return the RememberMeConfigurer for further customizations
	 * @see RememberMeServices
	 */
	public RememberMeConfigurer<H> rememberMeServices(
			RememberMeServices rememberMeServices) {
		this.rememberMeServices = rememberMeServices;
		return this;
	}

	/**
	 * Whether the cookie should always be created even if the remember-me parameter is
	 * not set.
	 *
	 * By default this will be set to false.
	 *
	 * @param alwaysRemember set to true to always trigger remember me,
	 * false to use the remember-me parameter.
	 * @return the RememberMeConfigurer for further customization
	 * @see AbstractRememberMeServices#setAlwaysRemember(boolean)
	 */
	public RememberMeConfigurer<H> alwaysRemember(boolean alwaysRemember) {
		this.alwaysRemember = alwaysRemember;
		return this;
	}


    // SecurityConfigurer 接口约定的初始化方法
	@SuppressWarnings("unchecked")
	@Override
	public void init(H http) throws Exception {
		validateInput();
		// 创建 Remember-Me 机制要使用的 key, 
		// 该 key 会被 RememberMeServices ,RememberMeAuthenticationProvider 使用
		String key = getKey();
		// 准备  RememberMeServices , 如果外部提供了 RememberMeServices 则使用外部提供值,
		// 如果外部没有提供 RememberMeServices, 则自己创建 RememberMeServices ,
		//  创建逻辑 :
		//  1. 如果外部设定了 tokenRepository, 则创建的是一个 PersistentTokenBasedRememberMeServices 对象;
		//  2. 如果外部没有设定 tokenRepository, 则创建的是一个  TokenBasedRememberMeServices 对象;       
		RememberMeServices rememberMeServices = getRememberMeServices(http, key);
		http.setSharedObject(RememberMeServices.class, rememberMeServices);
		LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
		if (logoutConfigurer != null && this.logoutHandler != null) {
			logoutConfigurer.addLogoutHandler(this.logoutHandler);
		}

       // 创建Remember-Me机制要使用的  RememberMeAuthenticationProvider, 注意这里使用了
       // 上面创建的 key, 
		RememberMeAuthenticationProvider authenticationProvider = new RememberMeAuthenticationProvider(
				key);
		authenticationProvider = postProcess(authenticationProvider);
		http.authenticationProvider(authenticationProvider);

       // 如果缺省登录页面生成过滤器存在于共享对象池,则告诉它 Remember-Me 参数的名称
		initDefaultLoginFilter(http);
	}

    // SecurityConfigurer 接口约定的配置方法
	@Override
	public void configure(H http) throws Exception {
       // 创建目标过滤器 RememberMeAuthenticationFilter, 进行属性设置,后置处理,然后配置到安全配置器
       // http 上
		RememberMeAuthenticationFilter rememberMeFilter = new RememberMeAuthenticationFilter(
				http.getSharedObject(AuthenticationManager.class),
				this.rememberMeServices);
		if (this.authenticationSuccessHandler != null) {
			rememberMeFilter
					.setAuthenticationSuccessHandler(this.authenticationSuccessHandler);
		}
		rememberMeFilter = postProcess(rememberMeFilter);
		http.addFilter(rememberMeFilter);
	}

	/**
	 * Validate rememberMeServices and rememberMeCookieName have not been set at
	 * the same time.
	 */
	private void validateInput() {
		if (this.rememberMeServices != null && this.rememberMeCookieName != DEFAULT_REMEMBER_ME_NAME) {
			throw new IllegalArgumentException("Can not set rememberMeCookieName " +
					"and custom rememberMeServices.");
		}
	}

	/**
	 * Returns the HTTP parameter used to indicate to remember the user at time of login.
	 * @return the HTTP parameter used to indicate to remember the user
	 */
	private String getRememberMeParameter() {
		return this.rememberMeParameter;
	}

	/**
	 * If available, initializes the DefaultLoginPageGeneratingFilter shared
	 * object.
	 *
	 * @param http the HttpSecurityBuilder to use
	 */
	private void initDefaultLoginFilter(H http) {
        // 如果缺省登录页面生成过滤器存在于共享对象池,则告诉它 Remember-Me 参数的名称
		DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = http
				.getSharedObject(DefaultLoginPageGeneratingFilter.class);
		if (loginPageGeneratingFilter != null) {
			loginPageGeneratingFilter.setRememberMeParameter(getRememberMeParameter());
		}
	}

	/**
	 * Gets the RememberMeServices or creates the RememberMeServices.
	 * @param http the HttpSecurity to lookup shared objects
	 * @param key the #key(String)
	 * @return the RememberMeServices to use
	 * @throws Exception
	 */
	private RememberMeServices getRememberMeServices(H http, String key)
			throws Exception {
		if (this.rememberMeServices != null) {
           // 外部指定了  RememberMeServices 的情形
			if (this.rememberMeServices instanceof LogoutHandler
					&& this.logoutHandler == null) {
				this.logoutHandler = (LogoutHandler) this.rememberMeServices;
			}
			return this.rememberMeServices;
		}
        
       // 外部没有指定 RememberMeServices, 则当前配置器自己创建 RememberMeServices 对象 
		AbstractRememberMeServices tokenRememberMeServices = createRememberMeServices(
				http, key);
		tokenRememberMeServices.setParameter(this.rememberMeParameter);
		tokenRememberMeServices.setCookieName(this.rememberMeCookieName);
		if (this.rememberMeCookieDomain != null) {
			tokenRememberMeServices.setCookieDomain(this.rememberMeCookieDomain);
		}
		if (this.tokenValiditySeconds != null) {
			tokenRememberMeServices.setTokenValiditySeconds(this.tokenValiditySeconds);
		}
		if (this.useSecureCookie != null) {
			tokenRememberMeServices.setUseSecureCookie(this.useSecureCookie);
		}
		if (this.alwaysRemember != null) {
			tokenRememberMeServices.setAlwaysRemember(this.alwaysRemember);
		}
		tokenRememberMeServices.afterPropertiesSet();
		this.logoutHandler = tokenRememberMeServices;
		this.rememberMeServices = tokenRememberMeServices;
		return tokenRememberMeServices;
	}

	/**
	 * Creates the RememberMeServices to use when none is provided. The result is
	 * either PersistentTokenRepository (if a PersistentTokenRepository is
	 * specified, else TokenBasedRememberMeServices.
	 *
     * 如果外部没有提供 RememberMeServices, 通过该方法创建 RememberMeServices ,
     * 创建逻辑 :
     * 1. 如果外部设定了 tokenRepository, 则创建的是一个 PersistentTokenBasedRememberMeServices 对象;
     * 2. 如果外部没有设定 tokenRepository, 则创建的是一个 TokenBasedRememberMeServices 对象;
	 * @param http the HttpSecurity to lookup shared objects
	 * @param key the #key(String)
	 * @return the RememberMeServices to use
	 * @throws Exception
	 */
	private AbstractRememberMeServices createRememberMeServices(H http, String key)
			throws Exception {
		return this.tokenRepository == null
				? createTokenBasedRememberMeServices(http, key)
				: createPersistentRememberMeServices(http, key);
	}

	/**
	 * Creates TokenBasedRememberMeServices
	 * 创建 TokenBasedRememberMeServices 对象,该方法会使用到 UserDetailsService 对象
     * 和 key (参考方法#getKey)
	 * @param http the HttpSecurity to lookup shared objects
	 * @param key the #key(String)
	 * @return the TokenBasedRememberMeServices
	 */
	private AbstractRememberMeServices createTokenBasedRememberMeServices(H http,
			String key) {
		UserDetailsService userDetailsService = getUserDetailsService(http);
		return new TokenBasedRememberMeServices(key, userDetailsService);
	}

	/**
	 * Creates PersistentTokenBasedRememberMeServices
	 * 创建 PersistentTokenBasedRememberMeServices 对象,该方法会使用到 UserDetailsService 对象
     * 和 key (参考方法#getKey)
	 * @param http the HttpSecurity to lookup shared objects
	 * @param key the #key(String)
	 * @return the PersistentTokenBasedRememberMeServices
	 */
	private AbstractRememberMeServices createPersistentRememberMeServices(H http,
			String key) {
		UserDetailsService userDetailsService = getUserDetailsService(http);
		return new PersistentTokenBasedRememberMeServices(key, userDetailsService,
				this.tokenRepository);
	}

	/**
	 * Gets the UserDetailsService to use. Either the explicitly configure
	 * UserDetailsService from #userDetailsService(UserDetailsService) or
	 * a shared object from HttpSecurity#getSharedObject(Class).
	 *
     * 获取最终要使用的 UserDetailsService, 该对象必须通过方法 #userDetailsService(UserDetailsService) 
     * 由外部设定,或者已经存在于 HttpSecurity 安全构建器的共享对象池中。
     * 如果这两种方式都没有提供 UserDetailsService,则该方法会抛出异常  IllegalStateException
     * 说找不到 UserDetailsService
	 * @param http HttpSecurity to get the shared UserDetailsService
	 * @return the UserDetailsService to use
	 */
	private UserDetailsService getUserDetailsService(H http) {
		if (this.userDetailsService == null) {
			this.userDetailsService = http.getSharedObject(UserDetailsService.class);
		}
		if (this.userDetailsService == null) {
			throw new IllegalStateException("userDetailsService cannot be null. Invoke "
					+ RememberMeConfigurer.class.getSimpleName()
					+ "#userDetailsService(UserDetailsService) or see its javadoc for alternative approaches.");
		}
		return this.userDetailsService;
	}

	/**
	 * Gets the key to use for validating remember me tokens. Either the value passed into
	 * #key(String), or a secure random string if none was specified.
	 *
     * 准备一个 key, 用于验证 Remember-Me 认证令牌,
     * 该 key 使用 UUID 随机字符串
	 * @return the remember me key to use
	 */
	private String getKey() {
		if (this.key == null) {
			this.key = UUID.randomUUID().toString();
		}
		return this.key;
	}
}

参考文章

上一篇:MyBatis 之 StatementHandler 来执行 SQL 语句


下一篇:Mybatis 一二级缓存实现原理与使用指南