/* * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.security.config.annotation.web.configurers; import java.util.UUID; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.RememberMeAuthenticationProvider; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.RememberMeServices; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices; import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter; import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; /** * Configures Remember Me authentication. This typically involves the user checking a box * when they enter their username and password that states to "Remember Me". * * <h2>Security Filters</h2> * * The following Filters are populated * * <ul> * <li>{@link RememberMeAuthenticationFilter}</li> * </ul> * * <h2>Shared Objects Created</h2> * * The following shared objects are populated * * <ul> * <li> * {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)} * is populated with a {@link RememberMeAuthenticationProvider}</li> * <li>{@link RememberMeServices} is populated as a shared object and available on * {@link HttpSecurity#getSharedObject(Class)}</li> * <li>{@link LogoutConfigurer#addLogoutHandler(LogoutHandler)} is used to add a logout * handler to clean up the remember me authentication.</li> * </ul> * * <h2>Shared Objects Used</h2> * * The following shared objects are used: * * <ul> * <li>{@link AuthenticationManager}</li> * <li>{@link UserDetailsService} if no {@link #userDetailsService(UserDetailsService)} * was specified.</li> * <li>{@link DefaultLoginPageGeneratingFilter} - if present will be populated with * information from the configuration</li> * </ul> * * @author Rob Winch * @author EddĂș MelĂ©ndez * @since 3.2 */ 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 * * @param tokenValiditySeconds * @return {@link 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. * <p> * 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 * {@code true}. * * @param useSecureCookie set to {@code true} to always user secure cookies, * {@code false} to disable their use. * @return the {@link RememberMeConfigurer} for further customization * @see AbstractRememberMeServices#setUseSecureCookie(boolean) */ public RememberMeConfigurer<H> useSecureCookie(boolean useSecureCookie) { this.useSecureCookie = useSecureCookie; return this; } /** * Specifies the {@link UserDetailsService} used to look up the {@link UserDetails} * when a remember me token is valid. The default is to use the * {@link UserDetailsService} found by invoking * {@link HttpSecurity#getSharedObject(Class)} which is set when using * {@link WebSecurityConfigurerAdapter#configure(AuthenticationManagerBuilder)}. * Alternatively, one can populate {@link #rememberMeServices(RememberMeServices)}. * * @param userDetailsService the {@link UserDetailsService} to configure * @return the {@link RememberMeConfigurer} for further customization * @see AbstractRememberMeServices */ public RememberMeConfigurer<H> userDetailsService( UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; return this; } /** * Specifies the {@link PersistentTokenRepository} to use. The default is to use * {@link TokenBasedRememberMeServices} instead. * * @param tokenRepository the {@link PersistentTokenRepository} to use * @return the {@link 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 {@link 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 {@link 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 {@link 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 {@code AuthenticationSuccessHandler} is set, it will * be invoked and the {@code 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. * * @param authenticationSuccessHandler the strategy to invoke immediately before * returning from {@code doFilter()}. * @return {@link RememberMeConfigurer} for further customization * @see RememberMeAuthenticationFilter#setAuthenticationSuccessHandler(AuthenticationSuccessHandler) */ public RememberMeConfigurer<H> authenticationSuccessHandler( AuthenticationSuccessHandler authenticationSuccessHandler) { this.authenticationSuccessHandler = authenticationSuccessHandler; return this; } /** * Specify the {@link RememberMeServices} to use. * @param rememberMeServices the {@link RememberMeServices} to use * @return the {@link 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. * <p> * By default this will be set to {@code false}. * * @param alwaysRemember set to {@code true} to always trigger remember me, * {@code false} to use the remember-me parameter. * @return the {@link RememberMeConfigurer} for further customization * @see AbstractRememberMeServices#setAlwaysRemember(boolean) */ public RememberMeConfigurer<H> alwaysRemember(boolean alwaysRemember) { this.alwaysRemember = alwaysRemember; return this; } @SuppressWarnings("unchecked") @Override public void init(H http) throws Exception { validateInput(); String key = getKey(); 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); } RememberMeAuthenticationProvider authenticationProvider = new RememberMeAuthenticationProvider( key); authenticationProvider = postProcess(authenticationProvider); http.authenticationProvider(authenticationProvider); initDefaultLoginFilter(http); } @Override public void configure(H http) throws Exception { 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 {@link DefaultLoginPageGeneratingFilter} shared * object. * * @param http the {@link HttpSecurityBuilder} to use */ private void initDefaultLoginFilter(H http) { DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = http .getSharedObject(DefaultLoginPageGeneratingFilter.class); if (loginPageGeneratingFilter != null) { loginPageGeneratingFilter.setRememberMeParameter(getRememberMeParameter()); } } /** * Gets the {@link RememberMeServices} or creates the {@link RememberMeServices}. * @param http the {@link HttpSecurity} to lookup shared objects * @param key the {@link #key(String)} * @return the {@link RememberMeServices} to use * @throws Exception */ private RememberMeServices getRememberMeServices(H http, String key) throws Exception { if (this.rememberMeServices != null) { if (this.rememberMeServices instanceof LogoutHandler && this.logoutHandler == null) { this.logoutHandler = (LogoutHandler) this.rememberMeServices; } return this.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 {@link RememberMeServices} to use when none is provided. The result is * either {@link PersistentTokenRepository} (if a {@link PersistentTokenRepository} is * specified, else {@link TokenBasedRememberMeServices}. * * @param http the {@link HttpSecurity} to lookup shared objects * @param key the {@link #key(String)} * @return the {@link 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 {@link TokenBasedRememberMeServices} * * @param http the {@link HttpSecurity} to lookup shared objects * @param key the {@link #key(String)} * @return the {@link TokenBasedRememberMeServices} */ private AbstractRememberMeServices createTokenBasedRememberMeServices(H http, String key) { UserDetailsService userDetailsService = getUserDetailsService(http); return new TokenBasedRememberMeServices(key, userDetailsService); } /** * Creates {@link PersistentTokenBasedRememberMeServices} * * @param http the {@link HttpSecurity} to lookup shared objects * @param key the {@link #key(String)} * @return the {@link PersistentTokenBasedRememberMeServices} */ private AbstractRememberMeServices createPersistentRememberMeServices(H http, String key) { UserDetailsService userDetailsService = getUserDetailsService(http); return new PersistentTokenBasedRememberMeServices(key, userDetailsService, this.tokenRepository); } /** * Gets the {@link UserDetailsService} to use. Either the explicitly configure * {@link UserDetailsService} from {@link #userDetailsService(UserDetailsService)} or * a shared object from {@link HttpSecurity#getSharedObject(Class)}. * * @param http {@link HttpSecurity} to get the shared {@link UserDetailsService} * @return the {@link 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 * {@link #key(String)}, or a secure random string if none was specified. * * @return the remember me key to use */ private String getKey() { if (this.key == null) { this.key = UUID.randomUUID().toString(); } return this.key; } }