org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer.java

Source

/*
 * 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
 *
 *      https://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 Melndez
 * @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;
    }
}