作为一个配置HttpSecurity
的SecurityConfigurer
,RememberMeConfigurer
的配置任务如下 :
key
缺省情况下,外界不指定该
key
,该key
使用缺省值UUID
随机字符串
HttpSecurity
配置如下安全过滤器Filter
RememberMeAuthenticationFilter
authenticationManager
使用安全配置器HttpSecurity
共享对象AuthenticationManager
rememberMeServices
来自
使用到了
key
,用于Remember-Me
用户登录生成RememberMeAuthenticationToken
RememberMeServices
对象RememberMeServices
对象HttpSecurity
提供一个AuthenticationProvider
RememberMeAuthenticationProvider
- 该
AuthenticationProvider
会被添加到安全配置器HttpSecurity
的共享对象AuthenticationManager
中- 使用到了
key
,用于认证Remember-Me
用户登录成功生成的RememberMeAuthenticationToken
外部不指定RememberMeServices
时,缺省RememberMeServices
的创建过程如下 :
tokenRepository
, 则创建的是一个 PersistentTokenBasedRememberMeServices
对象;tokenRepository
, 则创建的是一个 TokenBasedRememberMeServices
对象;缺省情况下,RememberMeConfigurer
的属性rememberMeServices
,tokenRepository
都未设置。所以缺省使用的RememberMeServices
会是一个自己创建的TokenBasedRememberMeServices
。
// 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 使用,
// RememberMeServices 会使用该 key 在 Remember-Me 登录成功创建 RememberMeAuthenticationToken
// 时传递到 RememberMeAuthenticationToken 对象
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;
}
}