/*
 * 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".
 *
 *
Security Filters

 *
 * The following Filters are populated
 *
 *

 *
{@link RememberMeAuthenticationFilter}

 *

 *
 *
Shared Objects Created

 *
 * The following shared objects are populated
 *
 *

 *

 * {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
 * is populated with a {@link RememberMeAuthenticationProvider}

 *
{@link RememberMeServices} is populated as a shared object and available on
 * {@link HttpSecurity#getSharedObject(Class)}

 *
{@link LogoutConfigurer#addLogoutHandler(LogoutHandler)} is used to add a logout
 * handler to clean up the remember me authentication.

 *

 *
 *
Shared Objects Used

 *
 * The following shared objects are used:
 *
 *

 *
{@link AuthenticationManager}

 *
{@link UserDetailsService} if no {@link #userDetailsService(UserDetailsService)}
 * was specified.

 *
{@link DefaultLoginPageGeneratingFilter} - if present will be populated with
 * information from the configuration

 *

 *
 * @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.
	 *

	 * 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.
	 *

	 * 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;
	}
}


/*
 * Copyright 2002-2017 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.web.server.authentication;

import java.util.function.Function;

import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;

import reactor.core.publisher.Mono;

/**
 * A {@link WebFilter} that performs authentication of a particular request. An outline of the logic:
 *
 *

 *

 *     A request comes in and if it does not match {@link #setRequiresAuthenticationMatcher(ServerWebExchangeMatcher)},
 *     then this filter does nothing and the {@link WebFilterChain} is continued. If it does match then...
 *

 *

 *     An attempt to convert the {@link ServerWebExchange} into an {@link Authentication} is made. If the result is
 *     empty, then the filter does nothing more and the {@link WebFilterChain} is continued. If it does create an
 *     {@link Authentication}...
 *

 *

 *     The {@link ReactiveAuthenticationManager} specified in
 *     {@link #AuthenticationWebFilter(ReactiveAuthenticationManager)} is used to perform authentication.
 *

 *

 *     If authentication is successful, {@link ServerAuthenticationSuccessHandler} is invoked and the authentication
 *     is set on {@link ReactiveSecurityContextHolder}, else {@link ServerAuthenticationFailureHandler} is invoked
 *

 *

 *
 * @author Rob Winch
 * @since 5.0
 */
public class AuthenticationWebFilter implements WebFilter {

	private final ReactiveAuthenticationManager authenticationManager;

	private ServerAuthenticationSuccessHandler authenticationSuccessHandler = new WebFilterChainServerAuthenticationSuccessHandler();

	private ServerAuthenticationConverter authenticationConverter = new ServerHttpBasicAuthenticationConverter();

	private ServerAuthenticationFailureHandler authenticationFailureHandler = new ServerAuthenticationEntryPointFailureHandler(new HttpBasicServerAuthenticationEntryPoint());

	private ServerSecurityContextRepository securityContextRepository = NoOpServerSecurityContextRepository.getInstance();

	private ServerWebExchangeMatcher requiresAuthenticationMatcher = ServerWebExchangeMatchers.anyExchange();

	/**
	 * Creates an instance
	 * @param authenticationManager the authentication manager to use
	 */
	public AuthenticationWebFilter(ReactiveAuthenticationManager authenticationManager) {
		Assert.notNull(authenticationManager, "authenticationManager cannot be null");
		this.authenticationManager = authenticationManager;
	}

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
		return this.requiresAuthenticationMatcher.matches(exchange)
			.filter( matchResult -> matchResult.isMatch())
			.flatMap( matchResult -> this.authenticationConverter.convert(exchange))
			.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
			.flatMap( token -> authenticate(exchange, chain, token));
	}

	private Mono<Void> authenticate(ServerWebExchange exchange,
		WebFilterChain chain, Authentication token) {
		WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, chain);
		return this.authenticationManager.authenticate(token)
			.switchIfEmpty(Mono.defer(() -> Mono.error(new IllegalStateException("No provider found for " + token.getClass()))))
			.flatMap(authentication -> onAuthenticationSuccess(authentication, webFilterExchange))
			.onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler
				.onAuthenticationFailure(webFilterExchange, e));
	}

	protected Mono<Void> onAuthenticationSuccess(Authentication authentication, WebFilterExchange webFilterExchange) {
		ServerWebExchange exchange = webFilterExchange.getExchange();
		SecurityContextImpl securityContext = new SecurityContextImpl();
		securityContext.setAuthentication(authentication);
		return this.securityContextRepository.save(exchange, securityContext)
			.then(this.authenticationSuccessHandler
				.onAuthenticationSuccess(webFilterExchange, authentication))
			.subscriberContext(ReactiveSecurityContextHolder.withSecurityContext(Mono.just(securityContext)));
	}

	/**
	 * Sets the repository for persisting the SecurityContext. Default is {@link NoOpServerSecurityContextRepository}
	 * @param securityContextRepository the repository to use
	 */
	public void setSecurityContextRepository(
		ServerSecurityContextRepository securityContextRepository) {
		Assert.notNull(securityContextRepository, "securityContextRepository cannot be null");
		this.securityContextRepository = securityContextRepository;
	}

	/**
	 * Sets the authentication success handler. Default is {@link WebFilterChainServerAuthenticationSuccessHandler}
	 * @param authenticationSuccessHandler the success handler to use
	 */
	public void setAuthenticationSuccessHandler(ServerAuthenticationSuccessHandler authenticationSuccessHandler) {
		Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null");
		this.authenticationSuccessHandler = authenticationSuccessHandler;
	}

	/**
	 * Sets the strategy used for converting from a {@link ServerWebExchange} to an {@link Authentication} used for
	 * authenticating with the provided {@link ReactiveAuthenticationManager}. If the result is empty, then it signals
	 * that no authentication attempt should be made. The default converter is
	 * {@link ServerHttpBasicAuthenticationConverter}
	 * @param authenticationConverter the converter to use
	 * @deprecated As of 5.1 in favor of {@link #setServerAuthenticationConverter(ServerAuthenticationConverter)}
	 * @see #setServerAuthenticationConverter(ServerAuthenticationConverter)
	 */
	@Deprecated
	public void setAuthenticationConverter(Function<ServerWebExchange, Mono<Authentication>> authenticationConverter) {
		Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
		setServerAuthenticationConverter(authenticationConverter::apply);
	}

	/**
	 * Sets the strategy used for converting from a {@link ServerWebExchange} to an {@link Authentication} used for
	 * authenticating with the provided {@link ReactiveAuthenticationManager}. If the result is empty, then it signals
	 * that no authentication attempt should be made. The default converter is
	 * {@link ServerHttpBasicAuthenticationConverter}
	 * @param authenticationConverter the converter to use
	 * @since 5.1
	 */
	public void setServerAuthenticationConverter(
			ServerAuthenticationConverter authenticationConverter) {
		Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
		this.authenticationConverter = authenticationConverter;
	}

	/**
	 * Sets the failure handler used when authentication fails. The default is to prompt for basic authentication.
	 * @param authenticationFailureHandler the handler to use. Cannot be null.
	 */
	public void setAuthenticationFailureHandler(
		ServerAuthenticationFailureHandler authenticationFailureHandler) {
		Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null");
		this.authenticationFailureHandler = authenticationFailureHandler;
	}

	/**
	 * Sets the matcher used to determine when creating an {@link Authentication} from
	 * {@link #setServerAuthenticationConverter(ServerAuthenticationConverter)} to be authentication. If the converter returns an empty
	 * result, then no authentication is attempted. The default is any request
	 * @param requiresAuthenticationMatcher the matcher to use. Cannot be null.
	 */
	public void setRequiresAuthenticationMatcher(
		ServerWebExchangeMatcher requiresAuthenticationMatcher) {
		Assert.notNull(requiresAuthenticationMatcher, "requiresAuthenticationMatcher cannot be null");
		this.requiresAuthenticationMatcher = requiresAuthenticationMatcher;
	}
}


/*
 * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 *
 * 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 sample.contact;

import java.util.Random;

import javax.sql.DataSource;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.acls.domain.AclImpl;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;

/**
 * Populates the Contacts in-memory database with contact and ACL information.
 *
 * @author Ben Alex
 */
public class DataSourcePopulator implements InitializingBean {
	// ~ Instance fields
	// ================================================================================================

	JdbcTemplate template;
	private MutableAclService mutableAclService;
	final Random rnd = new Random();
	TransactionTemplate tt;
	final String[] firstNames = { "Bob", "Mary", "James", "Jane", "Kristy", "Kirsty",
			"Kate", "Jeni", "Angela", "Melanie", "Kent", "William", "Geoff", "Jeff",
			"Adrian", "Amanda", "Lisa", "Elizabeth", "Prue", "Richard", "Darin",
			"Phillip", "Michael", "Belinda", "Samantha", "Brian", "Greg", "Matthew" };
	final String[] lastNames = { "Smith", "Williams", "Jackson", "Rictor", "Nelson",
			"Fitzgerald", "McAlpine", "Sutherland", "Abbott", "Hall", "Edwards", "Gates",
			"Black", "Brown", "Gray", "Marwell", "Booch", "Johnson", "McTaggart",
			"Parklin", "Findlay", "Robinson", "Giugni", "Lang", "Chi", "Carmichael" };
	private int createEntities = 50;

	// ~ Methods
	// ========================================================================================================

	public void afterPropertiesSet() throws Exception {
		Assert.notNull(mutableAclService, "mutableAclService required");
		Assert.notNull(template, "dataSource required");
		Assert.notNull(tt, "platformTransactionManager required");

		// Set a user account that will initially own all the created data
		Authentication authRequest = new UsernamePasswordAuthenticationToken("rod",
				"koala", AuthorityUtils.createAuthorityList("ROLE_IGNORED"));
		SecurityContextHolder.getContext().setAuthentication(authRequest);

		try {
			template.execute("DROP TABLE CONTACTS");
			template.execute("DROP TABLE AUTHORITIES");
			template.execute("DROP TABLE USERS");
			template.execute("DROP TABLE ACL_ENTRY");
			template.execute("DROP TABLE ACL_OBJECT_IDENTITY");
			template.execute("DROP TABLE ACL_CLASS");
			template.execute("DROP TABLE ACL_SID");
		}
		catch (Exception e) {
			System.out.println("Failed to drop tables: " + e.getMessage());
		}

		template.execute("CREATE TABLE ACL_SID("
				+ "ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,"
				+ "PRINCIPAL BOOLEAN NOT NULL," + "SID VARCHAR_IGNORECASE(100) NOT NULL,"
				+ "CONSTRAINT UNIQUE_UK_1 UNIQUE(SID,PRINCIPAL));");
		template.execute("CREATE TABLE ACL_CLASS("
				+ "ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,"
				+ "CLASS VARCHAR_IGNORECASE(100) NOT NULL,"
				+ "CLASS_ID_TYPE VARCHAR_IGNORECASE(100),"
				+ "CONSTRAINT UNIQUE_UK_2 UNIQUE(CLASS));");
		template.execute("CREATE TABLE ACL_OBJECT_IDENTITY("
				+ "ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,"
				+ "OBJECT_ID_CLASS BIGINT NOT NULL,"
				+ "OBJECT_ID_IDENTITY VARCHAR_IGNORECASE(36) NOT NULL,"
				+ "PARENT_OBJECT BIGINT,"
				+ "OWNER_SID BIGINT,"
				+ "ENTRIES_INHERITING BOOLEAN NOT NULL,"
				+ "CONSTRAINT UNIQUE_UK_3 UNIQUE(OBJECT_ID_CLASS,OBJECT_ID_IDENTITY),"
				+ "CONSTRAINT FOREIGN_FK_1 FOREIGN KEY(PARENT_OBJECT)REFERENCES ACL_OBJECT_IDENTITY(ID),"
				+ "CONSTRAINT FOREIGN_FK_2 FOREIGN KEY(OBJECT_ID_CLASS)REFERENCES ACL_CLASS(ID),"
				+ "CONSTRAINT FOREIGN_FK_3 FOREIGN KEY(OWNER_SID)REFERENCES ACL_SID(ID));");
		template.execute("CREATE TABLE ACL_ENTRY("
				+ "ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,"
				+ "ACL_OBJECT_IDENTITY BIGINT NOT NULL,ACE_ORDER INT NOT NULL,SID BIGINT NOT NULL,"
				+ "MASK INTEGER NOT NULL,GRANTING BOOLEAN NOT NULL,AUDIT_SUCCESS BOOLEAN NOT NULL,"
				+ "AUDIT_FAILURE BOOLEAN NOT NULL,CONSTRAINT UNIQUE_UK_4 UNIQUE(ACL_OBJECT_IDENTITY,ACE_ORDER),"
				+ "CONSTRAINT FOREIGN_FK_4 FOREIGN KEY(ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY(ID),"
				+ "CONSTRAINT FOREIGN_FK_5 FOREIGN KEY(SID) REFERENCES ACL_SID(ID));");

		template.execute("CREATE TABLE USERS(USERNAME VARCHAR_IGNORECASE(50) NOT NULL PRIMARY KEY,PASSWORD VARCHAR_IGNORECASE(500) NOT NULL,ENABLED BOOLEAN NOT NULL);");
		template.execute("CREATE TABLE AUTHORITIES(USERNAME VARCHAR_IGNORECASE(50) NOT NULL,AUTHORITY VARCHAR_IGNORECASE(50) NOT NULL,CONSTRAINT FK_AUTHORITIES_USERS FOREIGN KEY(USERNAME) REFERENCES USERS(USERNAME));");
		template.execute("CREATE UNIQUE INDEX IX_AUTH_USERNAME ON AUTHORITIES(USERNAME,AUTHORITY);");

		template.execute("CREATE TABLE CONTACTS(ID BIGINT NOT NULL PRIMARY KEY, CONTACT_NAME VARCHAR_IGNORECASE(50) NOT NULL, EMAIL VARCHAR_IGNORECASE(50) NOT NULL)");

		/*
		 * Passwords encoded using MD5, NOT in Base64 format, with null as salt Encoded
		 * password for rod is "koala" Encoded password for dianne is "emu" Encoded
		 * password for scott is "wombat" Encoded password for peter is "opal" (but user
		 * is disabled) Encoded password for bill is "wombat" Encoded password for bob is
		 * "wombat" Encoded password for jane is "wombat"
		 */
		template.execute("INSERT INTO USERS VALUES('rod','$2a$10$75pBjapg4Nl8Pzd.3JRnUe7PDJmk9qBGwNEJDAlA3V.dEJxcDKn5O',TRUE);");
		template.execute("INSERT INTO USERS VALUES('dianne','$2a$04$bCMEyxrdF/7sgfUiUJ6Ose2vh9DAMaVBldS1Bw2fhi1jgutZrr9zm',TRUE);");
		template.execute("INSERT INTO USERS VALUES('scott','$2a$06$eChwvzAu3TSexnC3ynw4LOSw1qiEbtNItNeYv5uI40w1i3paoSfLu',TRUE);");
		template.execute("INSERT INTO USERS VALUES('peter','$2a$04$8.H8bCMROLF4CIgd7IpeQ.tcBXLP5w8iplO0n.kCIkISwrIgX28Ii',FALSE);");
		template.execute("INSERT INTO USERS VALUES('bill','$2a$04$8.H8bCMROLF4CIgd7IpeQ.3khQlPVNWbp8kzSQqidQHGFurim7P8O',TRUE);");
		template.execute("INSERT INTO USERS VALUES('bob','$2a$06$zMgxlMf01SfYNcdx7n4NpeFlAGU8apCETz/i2C7VlYWu6IcNyn4Ay',TRUE);");
		template.execute("INSERT INTO USERS VALUES('jane','$2a$05$ZrdS7yMhCZ1J.AAidXZhCOxdjD8LO/dhlv4FJzkXA6xh9gdEbBT/u',TRUE);");
		template.execute("INSERT INTO AUTHORITIES VALUES('rod','ROLE_USER');");
		template.execute("INSERT INTO AUTHORITIES VALUES('rod','ROLE_SUPERVISOR');");
		template.execute("INSERT INTO AUTHORITIES VALUES('dianne','ROLE_USER');");
		template.execute("INSERT INTO AUTHORITIES VALUES('scott','ROLE_USER');");
		template.execute("INSERT INTO AUTHORITIES VALUES('peter','ROLE_USER');");
		template.execute("INSERT INTO AUTHORITIES VALUES('bill','ROLE_USER');");
		template.execute("INSERT INTO AUTHORITIES VALUES('bob','ROLE_USER');");
		template.execute("INSERT INTO AUTHORITIES VALUES('jane','ROLE_USER');");

		template.execute("INSERT INTO contacts VALUES (1, 'John Smith', 'john@somewhere.com');");
		template.execute("INSERT INTO contacts VALUES (2, 'Michael Citizen', 'michael@xyz.com');");
		template.execute("INSERT INTO contacts VALUES (3, 'Joe Bloggs', 'joe@demo.com');");
		template.execute("INSERT INTO contacts VALUES (4, 'Karen Sutherland', 'karen@sutherland.com');");
		template.execute("INSERT INTO contacts VALUES (5, 'Mitchell Howard', 'mitchell@abcdef.com');");
		template.execute("INSERT INTO contacts VALUES (6, 'Rose Costas', 'rose@xyz.com');");
		template.execute("INSERT INTO contacts VALUES (7, 'Amanda Smith', 'amanda@abcdef.com');");
		template.execute("INSERT INTO contacts VALUES (8, 'Cindy Smith', 'cindy@smith.com');");
		template.execute("INSERT INTO contacts VALUES (9, 'Jonathan Citizen', 'jonathan@xyz.com');");

		for (int i = 10; i < createEntities; i++) {
			String[] person = selectPerson();
			template.execute("INSERT INTO contacts VALUES (" + i + ", '" + person[2]
					+ "', '" + person[0].toLowerCase() + "@" + person[1].toLowerCase()
					+ ".com');");
		}

		// Create acl_object_identity rows (and also acl_class rows as needed
		for (int i = 1; i < createEntities; i++) {
			final ObjectIdentity objectIdentity = new ObjectIdentityImpl(Contact.class,
					Long.valueOf(i));
			tt.execute(new TransactionCallback<Object>() {
				public Object doInTransaction(TransactionStatus arg0) {
					mutableAclService.createAcl(objectIdentity);

					return null;
				}
			});
		}

		// Now grant some permissions
		grantPermissions(1, "rod", BasePermission.ADMINISTRATION);
		grantPermissions(2, "rod", BasePermission.READ);
		grantPermissions(3, "rod", BasePermission.READ);
		grantPermissions(3, "rod", BasePermission.WRITE);
		grantPermissions(3, "rod", BasePermission.DELETE);
		grantPermissions(4, "rod", BasePermission.ADMINISTRATION);
		grantPermissions(4, "dianne", BasePermission.ADMINISTRATION);
		grantPermissions(4, "scott", BasePermission.READ);
		grantPermissions(5, "dianne", BasePermission.ADMINISTRATION);
		grantPermissions(5, "dianne", BasePermission.READ);
		grantPermissions(6, "dianne", BasePermission.READ);
		grantPermissions(6, "dianne", BasePermission.WRITE);
		grantPermissions(6, "dianne", BasePermission.DELETE);
		grantPermissions(6, "scott", BasePermission.READ);
		grantPermissions(7, "scott", BasePermission.ADMINISTRATION);
		grantPermissions(8, "dianne", BasePermission.ADMINISTRATION);
		grantPermissions(8, "dianne", BasePermission.READ);
		grantPermissions(8, "scott", BasePermission.READ);
		grantPermissions(9, "scott", BasePermission.ADMINISTRATION);
		grantPermissions(9, "scott", BasePermission.READ);
		grantPermissions(9, "scott", BasePermission.WRITE);
		grantPermissions(9, "scott", BasePermission.DELETE);

		// Now expressly change the owner of the first ten contacts
		// We have to do this last, because "rod" owns all of them (doing it sooner would
		// prevent ACL updates)
		// Note that ownership has no impact on permissions - they're separate (ownership
		// only allows ACl editing)
		changeOwner(5, "dianne");
		changeOwner(6, "dianne");
		changeOwner(7, "scott");
		changeOwner(8, "dianne");
		changeOwner(9, "scott");

		String[] users = { "bill", "bob", "jane" }; // don't want to mess around with
													// consistent sample data
		Permission[] permissions = { BasePermission.ADMINISTRATION, BasePermission.READ,
				BasePermission.DELETE };

		for (int i = 10; i < createEntities; i++) {
			String user = users[rnd.nextInt(users.length)];
			Permission permission = permissions[rnd.nextInt(permissions.length)];
			grantPermissions(i, user, permission);

			String user2 = users[rnd.nextInt(users.length)];
			Permission permission2 = permissions[rnd.nextInt(permissions.length)];
			grantPermissions(i, user2, permission2);
		}

		SecurityContextHolder.clearContext();
	}

	private void changeOwner(int contactNumber, String newOwnerUsername) {
		AclImpl acl = (AclImpl) mutableAclService.readAclById(new ObjectIdentityImpl(
				Contact.class, new Long(contactNumber)));
		acl.setOwner(new PrincipalSid(newOwnerUsername));
		updateAclInTransaction(acl);
	}

	public int getCreateEntities() {
		return createEntities;
	}

	private void grantPermissions(int contactNumber, String recipientUsername,
			Permission permission) {
		AclImpl acl = (AclImpl) mutableAclService.readAclById(new ObjectIdentityImpl(
				Contact.class, Long.valueOf(contactNumber)));
		acl.insertAce(acl.getEntries().size(), permission, new PrincipalSid(
				recipientUsername), true);
		updateAclInTransaction(acl);
	}

	private String[] selectPerson() {
		String firstName = firstNames[rnd.nextInt(firstNames.length)];
		String lastName = lastNames[rnd.nextInt(lastNames.length)];

		return new String[] { firstName, lastName, firstName + " " + lastName };
	}

	public void setCreateEntities(int createEntities) {
		this.createEntities = createEntities;
	}

	public void setDataSource(DataSource dataSource) {
		this.template = new JdbcTemplate(dataSource);
	}

	public void setMutableAclService(MutableAclService mutableAclService) {
		this.mutableAclService = mutableAclService;
	}

	public void setPlatformTransactionManager(
			PlatformTransactionManager platformTransactionManager) {
		this.tt = new TransactionTemplate(platformTransactionManager);
	}

	private void updateAclInTransaction(final MutableAcl acl) {
		tt.execute(new TransactionCallback<Object>() {
			public Object doInTransaction(TransactionStatus arg0) {
				mutableAclService.updateAcl(acl);

				return null;
			}
		});
	}
}


/*
 * Copyright 2002-2013 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.HashSet;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.authority.mapping.SimpleMappableAttributesRetriever;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesUserDetailsService;
import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter;

/**
 * Adds support for J2EE pre authentication.
 *
 *
Security Filters

 *
 * The following Filters are populated
 *
 *

 *

 * {@link J2eePreAuthenticatedProcessingFilter}

 *

 *
 *
Shared Objects Created

 *
 *

 *

 * {@link AuthenticationEntryPoint} is populated with an
 * {@link Http403ForbiddenEntryPoint}

 *
A {@link PreAuthenticatedAuthenticationProvider} is populated into
 * {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
 *

 *

 *
 *
Shared Objects Used

 *
 * The following shared objects are used:
 *
 *

 *
{@link AuthenticationManager}

 *

 *
 * @author Rob Winch
 * @since 3.2
 */
public final class JeeConfigurer<H extends HttpSecurityBuilder<H>> extends
		AbstractHttpConfigurer<JeeConfigurer<H>, H> {
	private J2eePreAuthenticatedProcessingFilter j2eePreAuthenticatedProcessingFilter;
	private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService;
	private Set<String> mappableRoles = new HashSet<>();

	/**
	 * Creates a new instance
	 * @see HttpSecurity#jee()
	 */
	public JeeConfigurer() {
	}

	/**
	 * Specifies roles to use map from the {@link HttpServletRequest} to the
	 * {@link UserDetails}. If {@link HttpServletRequest#isUserInRole(String)} returns
	 * true, the role is added to the {@link UserDetails}. This method is the equivalent
	 * of invoking {@link #mappableAuthorities(Set)}. Multiple invocations of
	 * {@link #mappableAuthorities(String...)} will override previous invocations.
	 *
	 *

	 * There are no default roles that are mapped.
	 *

	 *
	 * @param mappableRoles the roles to attempt to map to the {@link UserDetails} (i.e.
	 * "ROLE_USER", "ROLE_ADMIN", etc).
	 * @return the {@link JeeConfigurer} for further customizations
	 * @see SimpleMappableAttributesRetriever
	 * @see #mappableRoles(String...)
	 */
	public JeeConfigurer<H> mappableAuthorities(String... mappableRoles) {
		this.mappableRoles.clear();
		for (String role : mappableRoles) {
			this.mappableRoles.add(role);
		}
		return this;
	}

	/**
	 * Specifies roles to use map from the {@link HttpServletRequest} to the
	 * {@link UserDetails} and automatically prefixes it with "ROLE_". If
	 * {@link HttpServletRequest#isUserInRole(String)} returns true, the role is added to
	 * the {@link UserDetails}. This method is the equivalent of invoking
	 * {@link #mappableAuthorities(Set)}. Multiple invocations of
	 * {@link #mappableRoles(String...)} will override previous invocations.
	 *
	 *

	 * There are no default roles that are mapped.
	 *

	 *
	 * @param mappableRoles the roles to attempt to map to the {@link UserDetails} (i.e.
	 * "USER", "ADMIN", etc).
	 * @return the {@link JeeConfigurer} for further customizations
	 * @see SimpleMappableAttributesRetriever
	 * @see #mappableAuthorities(String...)
	 */
	public JeeConfigurer<H> mappableRoles(String... mappableRoles) {
		this.mappableRoles.clear();
		for (String role : mappableRoles) {
			this.mappableRoles.add("ROLE_" + role);
		}
		return this;
	}

	/**
	 * Specifies roles to use map from the {@link HttpServletRequest} to the
	 * {@link UserDetails}. If {@link HttpServletRequest#isUserInRole(String)} returns
	 * true, the role is added to the {@link UserDetails}. This is the equivalent of
	 * {@link #mappableRoles(String...)}. Multiple invocations of
	 * {@link #mappableAuthorities(Set)} will override previous invocations.
	 *
	 *

	 * There are no default roles that are mapped.
	 *

	 *
	 * @param mappableRoles the roles to attempt to map to the {@link UserDetails}.
	 * @return the {@link JeeConfigurer} for further customizations
	 * @see SimpleMappableAttributesRetriever
	 */
	public JeeConfigurer<H> mappableAuthorities(Set<String> mappableRoles) {
		this.mappableRoles = mappableRoles;
		return this;
	}

	/**
	 * Specifies the {@link AuthenticationUserDetailsService} that is used with the
	 * {@link PreAuthenticatedAuthenticationProvider}. The default is a
	 * {@link PreAuthenticatedGrantedAuthoritiesUserDetailsService}.
	 *
	 * @param authenticatedUserDetailsService the {@link AuthenticationUserDetailsService}
	 * to use.
	 * @return the {@link JeeConfigurer} for further configuration
	 */
	public JeeConfigurer<H> authenticatedUserDetailsService(
			AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticatedUserDetailsService) {
		this.authenticationUserDetailsService = authenticatedUserDetailsService;
		return this;
	}

	/**
	 * Allows specifying the {@link J2eePreAuthenticatedProcessingFilter} to use. If
	 * {@link J2eePreAuthenticatedProcessingFilter} is provided, all of its attributes
	 * must also be configured manually (i.e. all attributes populated in the
	 * {@link JeeConfigurer} are not used).
	 *
	 * @param j2eePreAuthenticatedProcessingFilter the
	 * {@link J2eePreAuthenticatedProcessingFilter} to use.
	 * @return the {@link JeeConfigurer} for further configuration
	 */
	public JeeConfigurer<H> j2eePreAuthenticatedProcessingFilter(
			J2eePreAuthenticatedProcessingFilter j2eePreAuthenticatedProcessingFilter) {
		this.j2eePreAuthenticatedProcessingFilter = j2eePreAuthenticatedProcessingFilter;
		return this;
	}

	/**
	 * Populates a {@link PreAuthenticatedAuthenticationProvider} into
	 * {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
	 * and a {@link Http403ForbiddenEntryPoint} into
	 * {@link HttpSecurityBuilder#setSharedObject(Class, Object)}
	 *
	 * @see org.springframework.security.config.annotation.SecurityConfigurerAdapter#init(org.springframework.security.config.annotation.SecurityBuilder)
	 */
	@Override
	public void init(H http) throws Exception {
		PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider();
		authenticationProvider
				.setPreAuthenticatedUserDetailsService(getUserDetailsService());
		authenticationProvider = postProcess(authenticationProvider);

		// @formatter:off
		http
			.authenticationProvider(authenticationProvider)
			.setSharedObject(AuthenticationEntryPoint.class, new Http403ForbiddenEntryPoint());
		// @formatter:on
	}

	@Override
	public void configure(H http) throws Exception {
		J2eePreAuthenticatedProcessingFilter filter = getFilter(http
				.getSharedObject(AuthenticationManager.class));
		http.addFilter(filter);
	}

	/**
	 * Gets the {@link J2eePreAuthenticatedProcessingFilter} or creates a default instance
	 * using the properties provided.
	 * @param authenticationManager the {@link AuthenticationManager} to use.
	 * @return the {@link J2eePreAuthenticatedProcessingFilter} to use.
	 */
	private J2eePreAuthenticatedProcessingFilter getFilter(
			AuthenticationManager authenticationManager) {
		if (j2eePreAuthenticatedProcessingFilter == null) {
			j2eePreAuthenticatedProcessingFilter = new J2eePreAuthenticatedProcessingFilter();
			j2eePreAuthenticatedProcessingFilter
					.setAuthenticationManager(authenticationManager);
			j2eePreAuthenticatedProcessingFilter
					.setAuthenticationDetailsSource(createWebAuthenticationDetailsSource());
			j2eePreAuthenticatedProcessingFilter = postProcess(j2eePreAuthenticatedProcessingFilter);
		}

		return j2eePreAuthenticatedProcessingFilter;
	}

	/**
	 * Gets the {@link AuthenticationUserDetailsService} that was specified or defaults to
	 * {@link PreAuthenticatedGrantedAuthoritiesUserDetailsService}.
	 *
	 * @return the {@link AuthenticationUserDetailsService} to use
	 */
	private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> getUserDetailsService() {
		return authenticationUserDetailsService == null ? new PreAuthenticatedGrantedAuthoritiesUserDetailsService()
				: authenticationUserDetailsService;
	}

	/**
	 * Creates the {@link J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource} to set
	 * on the {@link J2eePreAuthenticatedProcessingFilter}. It is populated with a
	 * {@link SimpleMappableAttributesRetriever}.
	 *
	 * @return the {@link J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource} to use.
	 */
	private J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource createWebAuthenticationDetailsSource() {
		J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource detailsSource = new J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource();
		SimpleMappableAttributesRetriever rolesRetriever = new SimpleMappableAttributesRetriever();
		rolesRetriever.setMappableAttributes(mappableRoles);
		detailsSource.setMappableRolesRetriever(rolesRetriever);

		detailsSource = postProcess(detailsSource);
		return detailsSource;
	}
}


/*
 * Copyright 2002-2013 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.web.header.writers;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.web.header.HeaderWriter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;

/**
 * Delegates to the provided {@link HeaderWriter} when
 * {@link RequestMatcher#matches(HttpServletRequest)} returns true.
 *
 * @author Rob Winch
 * @since 3.2
 */
public final class DelegatingRequestMatcherHeaderWriter implements HeaderWriter {
	private final RequestMatcher requestMatcher;

	private final HeaderWriter delegateHeaderWriter;

	/**
	 * Creates a new instance
	 *
	 * @param requestMatcher the {@link RequestMatcher} to use. If returns true, the
	 * delegateHeaderWriter will be invoked.
	 * @param delegateHeaderWriter the {@link HeaderWriter} to invoke if the
	 * {@link RequestMatcher} returns true.
	 */
	public DelegatingRequestMatcherHeaderWriter(RequestMatcher requestMatcher,
			HeaderWriter delegateHeaderWriter) {
		Assert.notNull(requestMatcher, "requestMatcher cannot be null");
		Assert.notNull(delegateHeaderWriter, "delegateHeaderWriter cannot be null");
		this.requestMatcher = requestMatcher;
		this.delegateHeaderWriter = delegateHeaderWriter;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.springframework.security.web.headers.HeaderWriter#writeHeaders(javax.servlet
	 * .http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
		if (this.requestMatcher.matches(request)) {
			this.delegateHeaderWriter.writeHeaders(request, response);
		}
	}

	@Override
	public String toString() {
		return getClass().getName() + " [requestMatcher=" + this.requestMatcher
				+ ", delegateHeaderWriter=" + this.delegateHeaderWriter + "]";
	}
}


/*
 * Copyright 2002-2018 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.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

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.web.header.HeaderWriter;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.header.writers.*;
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;

/**
 *

 * Adds the Security HTTP headers to the response. Security HTTP headers is activated by
 * default when using {@link WebSecurityConfigurerAdapter}'s default constructor.
 *

 *
 *

 * The default headers include are:
 *

 *
 *

 * Cache-Control: no-cache, no-store, max-age=0, must-revalidate
 * Pragma: no-cache
 * Expires: 0
 * X-Content-Type-Options: nosniff
 * Strict-Transport-Security: max-age=31536000 ; includeSubDomains
 * X-Frame-Options: DENY
 * X-XSS-Protection: 1; mode=block
 *

 *
 * @author Rob Winch
 * @author Tim Ysewyn
 * @author Joe Grandja
 * @author Eddú Meléndez
 * @author Vedran Pavic
 * @since 3.2
 */
public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
		AbstractHttpConfigurer<HeadersConfigurer<H>, H> {
	private List<HeaderWriter> headerWriters = new ArrayList<>();

	// --- default header writers ---

	private final ContentTypeOptionsConfig contentTypeOptions = new ContentTypeOptionsConfig();

	private final XXssConfig xssProtection = new XXssConfig();

	private final CacheControlConfig cacheControl = new CacheControlConfig();

	private final HstsConfig hsts = new HstsConfig();

	private final FrameOptionsConfig frameOptions = new FrameOptionsConfig();

	private final HpkpConfig hpkp = new HpkpConfig();

	private final ContentSecurityPolicyConfig contentSecurityPolicy = new ContentSecurityPolicyConfig();

	private final ReferrerPolicyConfig referrerPolicy = new ReferrerPolicyConfig();

	private final FeaturePolicyConfig featurePolicy = new FeaturePolicyConfig();

	/**
	 * Creates a new instance
	 *
	 * @see HttpSecurity#headers()
	 */
	public HeadersConfigurer() {
	}

	/**
	 * Adds a {@link HeaderWriter} instance
	 *
	 * @param headerWriter the {@link HeaderWriter} instance to add
	 * @return the {@link HeadersConfigurer} for additional customizations
	 */
	public HeadersConfigurer<H> addHeaderWriter(HeaderWriter headerWriter) {
		Assert.notNull(headerWriter, "headerWriter cannot be null");
		this.headerWriters.add(headerWriter);
		return this;
	}

	/**
	 * Configures the {@link XContentTypeOptionsHeaderWriter} which inserts the X-Content-Type-Options:
	 *
	 *

	 * X-Content-Type-Options: nosniff
	 *

	 *
	 * @return the ContentTypeOptionsConfig for additional customizations
	 */
	public ContentTypeOptionsConfig contentTypeOptions() {
		return contentTypeOptions.enable();
	}

	public final class ContentTypeOptionsConfig {
		private XContentTypeOptionsHeaderWriter writer;

		private ContentTypeOptionsConfig() {
			enable();
		}

		/**
		 * Removes the X-XSS-Protection header.
		 *
		 * @return {@link HeadersConfigurer} for additional customization.
		 */
		public HeadersConfigurer<H> disable() {
			writer = null;
			return and();
		}

		/**
		 * Allows customizing the {@link HeadersConfigurer}
		 * @return the {@link HeadersConfigurer} for additional customization
		 */
		public HeadersConfigurer<H> and() {
			return HeadersConfigurer.this;
		}

		/**
		 * Ensures that Content Type Options is enabled
		 *
		 * @return the {@link ContentTypeOptionsConfig} for additional customization
		 */
		private ContentTypeOptionsConfig enable() {
			if (writer == null) {
				writer = new XContentTypeOptionsHeaderWriter();
			}
			return this;
		}
	}

	/**
	 * Note this is not comprehensive XSS protection!
	 *
	 *

	 * Allows customizing the {@link XXssProtectionHeaderWriter} which adds the X-XSS-Protection header
	 *

	 *
	 * @return the {@link HeadersConfigurer} for additional customizations
	 */
	public XXssConfig xssProtection() {
		return xssProtection.enable();
	}

	public final class XXssConfig {
		private XXssProtectionHeaderWriter writer;

		private XXssConfig() {
			enable();
		}

		/**
		 * If false, will not specify the mode as blocked. In this instance, any content
		 * will be attempted to be fixed. If true, the content will be replaced with "#".
		 *
		 * @param enabled the new value
		 */
		public XXssConfig block(boolean enabled) {
			writer.setBlock(enabled);
			return this;
		}

		/**
		 * If true, the header value will contain a value of 1. For example:
		 *
		 *

		 * X-XSS-Protection: 1
		 *

		 *
		 * or if {@link XXssProtectionHeaderWriter#setBlock(boolean)} of the given {@link XXssProtectionHeaderWriter} is true
		 *
		 *
		 *

		 * X-XSS-Protection: 1; mode=block
		 *

		 *
		 * If false, will explicitly disable specify that X-XSS-Protection is disabled.
		 * For example:
		 *
		 *

		 * X-XSS-Protection: 0
		 *

		 *
		 * @param enabled the new value
		 */
		public XXssConfig xssProtectionEnabled(boolean enabled) {
			writer.setEnabled(enabled);
			return this;
		}

		/**
		 * Disables X-XSS-Protection header (does not include it)
		 *
		 * @return the {@link HeadersConfigurer} for additional configuration
		 */
		public HeadersConfigurer<H> disable() {
			writer = null;
			return and();
		}

		/**
		 * Allows completing configuration of X-XSS-Protection and continuing
		 * configuration of headers.
		 *
		 * @return the {@link HeadersConfigurer} for additional configuration
		 */
		public HeadersConfigurer<H> and() {
			return HeadersConfigurer.this;
		}

		/**
		 * Ensures the X-XSS-Protection header is enabled if it is not already.
		 *
		 * @return the {@link XXssConfig} for additional customization
		 */
		private XXssConfig enable() {
			if (writer == null) {
				writer = new XXssProtectionHeaderWriter();
			}
			return this;
		}
	}

	/**
	 * Allows customizing the {@link CacheControlHeadersWriter}. Specifically it adds the
	 * following headers:
	 *

	 *
Cache-Control: no-cache, no-store, max-age=0, must-revalidate

	 *
Pragma: no-cache

	 *
Expires: 0

	 *

	 *
	 * @return the {@link HeadersConfigurer} for additional customizations
	 */
	public CacheControlConfig cacheControl() {
		return cacheControl.enable();
	}

	public final class CacheControlConfig {
		private CacheControlHeadersWriter writer;

		private CacheControlConfig() {
			enable();
		}

		/**
		 * Disables Cache Control
		 *
		 * @return the {@link HeadersConfigurer} for additional configuration
		 */
		public HeadersConfigurer<H> disable() {
			writer = null;
			return HeadersConfigurer.this;
		}

		/**
		 * Allows completing configuration of Cache Control and continuing
		 * configuration of headers.
		 *
		 * @return the {@link HeadersConfigurer} for additional configuration
		 */
		public HeadersConfigurer<H> and() {
			return HeadersConfigurer.this;
		}

		/**
		 * Ensures the Cache Control headers are enabled if they are not already.
		 *
		 * @return the {@link CacheControlConfig} for additional customization
		 */
		private CacheControlConfig enable() {
			if (writer == null) {
				writer = new CacheControlHeadersWriter();
			}
			return this;
		}
	}

	/**
	 * Allows customizing the {@link HstsHeaderWriter} which provides support for HTTP Strict Transport Security
	 * (HSTS).
	 *
	 * @return the {@link HeadersConfigurer} for additional customizations
	 */
	public HstsConfig httpStrictTransportSecurity() {
		return hsts.enable();
	}

	public final class HstsConfig {
		private HstsHeaderWriter writer;

		private HstsConfig() {
			enable();
		}

		/**
		 *

		 * Sets the value (in seconds) for the max-age directive of the
		 * Strict-Transport-Security header. The default is one year.
		 *

		 *
		 *

		 * This instructs browsers how long to remember to keep this domain as a known
		 * HSTS Host. See Section 6.1.1 for
		 * additional details.
		 *

		 *
		 * @param maxAgeInSeconds the maximum amount of time (in seconds) to consider this
		 * domain as a known HSTS Host.
		 * @throws IllegalArgumentException if maxAgeInSeconds is negative
		 */
		public HstsConfig maxAgeInSeconds(long maxAgeInSeconds) {
			writer.setMaxAgeInSeconds(maxAgeInSeconds);
			return this;
		}

		/**
		 * Sets the {@link RequestMatcher} used to determine if the
		 * "Strict-Transport-Security" should be added. If true the header is added, else
		 * the header is not added. By default the header is added when
		 * {@link HttpServletRequest#isSecure()} returns true.
		 *
		 * @param requestMatcher the {@link RequestMatcher} to use.
		 * @throws IllegalArgumentException if {@link RequestMatcher} is null
		 */
		public HstsConfig requestMatcher(RequestMatcher requestMatcher) {
			writer.setRequestMatcher(requestMatcher);
			return this;
		}

		/**
		 *

		 * If true, subdomains should be considered HSTS Hosts too. The default is true.
		 *

		 *
		 *

		 * See Section
		 * 6.1.2 for additional details.
		 *

		 *
		 * @param includeSubDomains true to include subdomains, else false
		 */
		public HstsConfig includeSubDomains(boolean includeSubDomains) {
			writer.setIncludeSubDomains(includeSubDomains);
			return this;
		}

		/**
		 *

		 * If true, preload will be included in HSTS Header. The default is false.
		 *

		 *
		 *

		 * See Website hstspreload.org
		 * for additional details.
		 *

		 *
		 * @param preload true to include preload, else false
		 * @since 5.2.0
		 * @author Ankur Pathak
		 */
		public HstsConfig preload(boolean preload) {
			writer.setPreload(preload);
			return this;
		}

		/**
		 * Disables Strict Transport Security
		 *
		 * @return the {@link HeadersConfigurer} for additional configuration
		 */
		public HeadersConfigurer<H> disable() {
			writer = null;
			return HeadersConfigurer.this;
		}

		/**
		 * Allows completing configuration of Strict Transport Security and continuing
		 * configuration of headers.
		 *
		 * @return the {@link HeadersConfigurer} for additional configuration
		 */
		public HeadersConfigurer<H> and() {
			return HeadersConfigurer.this;
		}

		/**
		 * Ensures that Strict-Transport-Security is enabled if it is not already
		 *
		 * @return the {@link HstsConfig} for additional customization
		 */
		private HstsConfig enable() {
			if (writer == null) {
				writer = new HstsHeaderWriter();
			}
			return this;
		}
	}

	/**
	 * Allows customizing the {@link XFrameOptionsHeaderWriter}.
	 *
	 * @return the {@link HeadersConfigurer} for additional customizations
	 */
	public FrameOptionsConfig frameOptions() {
		return frameOptions.enable();
	}

	public final class FrameOptionsConfig {
		private XFrameOptionsHeaderWriter writer;

		private FrameOptionsConfig() {
			enable();
		}

		/**
		 * Specify to DENY framing any content from this application.
		 *
		 * @return the {@link HeadersConfigurer} for additional customization.
		 */
		public HeadersConfigurer<H> deny() {
			writer = new XFrameOptionsHeaderWriter(XFrameOptionsMode.DENY);
			return and();
		}

		/**
		 *

		 * Specify to allow any request that comes from the same origin to frame this
		 * application. For example, if the application was hosted on example.com, then
		 * example.com could frame the application, but evil.com could not frame the
		 * application.
		 *

		 *
		 * @return the {@link HeadersConfigurer} for additional customization.
		 */
		public HeadersConfigurer<H> sameOrigin() {
			writer = new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN);
			return and();
		}

		/**
		 * Prevents the header from being added to the response.
		 *
		 * @return the {@link HeadersConfigurer} for additional configuration.
		 */
		public HeadersConfigurer<H> disable() {
			writer = null;
			return and();
		}

		/**
		 * Allows continuing customizing the headers configuration.
		 *
		 * @return the {@link HeadersConfigurer} for additional configuration
		 */
		public HeadersConfigurer<H> and() {
			return HeadersConfigurer.this;
		}

		/**
		 * Enables FrameOptionsConfig if it is not already enabled.
		 *
		 * @return the FrameOptionsConfig for additional customization.
		 */
		private FrameOptionsConfig enable() {
			if (writer == null) {
				writer = new XFrameOptionsHeaderWriter(XFrameOptionsMode.DENY);
			}
			return this;
		}
	}

	/**
	 * Allows customizing the {@link HpkpHeaderWriter} which provides support for HTTP Public Key Pinning (HPKP).
	 *
	 * @return the {@link HeadersConfigurer} for additional customizations
	 *
	 * @since 4.1
	 */
	public HpkpConfig httpPublicKeyPinning() {
		return hpkp.enable();
	}

	public final class HpkpConfig {
		private HpkpHeaderWriter writer;

		private HpkpConfig() {}

		/**
		 *

		 * Sets the value for the pin- directive of the Public-Key-Pins header.
		 *

		 *
		 *

		 * The pin directive specifies a way for web host operators to indicate
		 * a cryptographic identity that should be bound to a given web host.
		 * See Section 2.1.1 for additional details.
		 *

		 *
		 * @param pins the map of base64-encoded SPKI fingerprint & cryptographic hash algorithm pairs.
		 * @throws IllegalArgumentException if pins is null
		 */
		public HpkpConfig withPins(Map<String, String> pins) {
			writer.setPins(pins);
			return this;
		}

		/**
		 *

		 * Adds a list of SHA256 hashed pins for the pin- directive of the Public-Key-Pins header.
		 *

		 *
		 *

		 * The pin directive specifies a way for web host operators to indicate
		 * a cryptographic identity that should be bound to a given web host.
		 * See Section 2.1.1 for additional details.
		 *

		 *
		 * @param pins a list of base64-encoded SPKI fingerprints.
		 * @throws IllegalArgumentException if a pin is null
		 */
		public HpkpConfig addSha256Pins(String ... pins) {
			writer.addSha256Pins(pins);
			return this;
		}

		/**
		 *

		 * Sets the value (in seconds) for the max-age directive of the Public-Key-Pins header.
		 * The default is 60 days.
		 *

		 *
		 *

		 * This instructs browsers how long they should regard the host (from whom the message was received)
		 * as a known pinned host. See Section
		 * 2.1.2 for additional details.
		 *

		 *
		 * @param maxAgeInSeconds the maximum amount of time (in seconds) to regard the host
		 * as a known pinned host.
		 * @throws IllegalArgumentException if maxAgeInSeconds is negative
		 */
		public HpkpConfig maxAgeInSeconds(long maxAgeInSeconds) {
			writer.setMaxAgeInSeconds(maxAgeInSeconds);
			return this;
		}

		/**
		 *

		 * If true, the pinning policy applies to this pinned host as well as any subdomains
		 * of the host's domain name. The default is false.
		 *

		 *
		 *

		 * See Section 2.1.3
		 * for additional details.
		 *

		 *
		 * @param includeSubDomains true to include subdomains, else false
		 */
		public HpkpConfig includeSubDomains(boolean includeSubDomains) {
			writer.setIncludeSubDomains(includeSubDomains);
			return this;
		}

		/**
		 *

		 * If true, the browser should not terminate the connection with the server. The default is true.
		 *

		 *
		 *

		 * See Section 2.1
		 * for additional details.
		 *

		 *
		 * @param reportOnly true to report only, else false
		 */
		public HpkpConfig reportOnly(boolean reportOnly) {
			writer.setReportOnly(reportOnly);
			return this;
		}

		/**
		 *

		 * Sets the URI to which the browser should report pin validation failures.
		 *

		 *
		 *

		 * See Section 2.1.4
		 * for additional details.
		 *

		 *
		 * @param reportUri the URI where the browser should send the report to.
		 */
		public HpkpConfig reportUri(URI reportUri) {
			writer.setReportUri(reportUri);
			return this;
		}

		/**
		 *

		 * Sets the URI to which the browser should report pin validation failures.
		 *

		 *
		 *

		 * See Section 2.1.4
		 * for additional details.
		 *

		 *
		 * @param reportUri the URI where the browser should send the report to.
		 * @throws IllegalArgumentException if the reportUri is not a valid URI
		 */
		public HpkpConfig reportUri(String reportUri) {
			writer.setReportUri(reportUri);
			return this;
		}

		/**
		 * Prevents the header from being added to the response.
		 *
		 * @return the {@link HeadersConfigurer} for additional configuration.
		 */
		public HeadersConfigurer<H> disable() {
			writer = null;
			return and();
		}

		/**
		 * Allows completing configuration of Public Key Pinning and continuing
		 * configuration of headers.
		 *
		 * @return the {@link HeadersConfigurer} for additional configuration
		 */
		public HeadersConfigurer<H> and() {
			return HeadersConfigurer.this;
		}

		/**
		 * Ensures that Public-Key-Pins or Public-Key-Pins-Report-Only is enabled if it is not already
		 *
		 * @return the {@link HstsConfig} for additional customization
		 */
		private HpkpConfig enable() {
			if (writer == null) {
				writer = new HpkpHeaderWriter();
			}
			return this;
		}
	}

	/**
	 *

	 * Allows configuration for Content Security Policy (CSP) Level 2.
	 *

	 *
	 *

	 * Calling this method automatically enables (includes) the Content-Security-Policy header in the response
	 * using the supplied security policy directive(s).
	 *

	 *
	 *

	 * Configuration is provided to the {@link ContentSecurityPolicyHeaderWriter} which supports the writing
	 * of the two headers as detailed in the W3C Candidate Recommendation:
	 *

	 *

	 *
Content-Security-Policy

	 *
Content-Security-Policy-Report-Only

	 *

	 *
	 * @see ContentSecurityPolicyHeaderWriter
	 * @since 4.1
	 * @return the ContentSecurityPolicyConfig for additional configuration
	 * @throws IllegalArgumentException if policyDirectives is null or empty
	 */
	public ContentSecurityPolicyConfig contentSecurityPolicy(String policyDirectives) {
		this.contentSecurityPolicy.writer =
				new ContentSecurityPolicyHeaderWriter(policyDirectives);
		return contentSecurityPolicy;
	}

	public final class ContentSecurityPolicyConfig {
		private ContentSecurityPolicyHeaderWriter writer;

		private ContentSecurityPolicyConfig() {
		}

		/**
		 * Enables (includes) the Content-Security-Policy-Report-Only header in the response.
		 *
		 * @return the {@link ContentSecurityPolicyConfig} for additional configuration
		 */
		public ContentSecurityPolicyConfig reportOnly() {
			this.writer.setReportOnly(true);
			return this;
		}

		/**
		 * Allows completing configuration of Content Security Policy and continuing
		 * configuration of headers.
		 *
		 * @return the {@link HeadersConfigurer} for additional configuration
		 */
		public HeadersConfigurer<H> and() {
			return HeadersConfigurer.this;
		}

	}

	/**
	 * Clears all of the default headers from the response. After doing so, one can add
	 * headers back. For example, if you only want to use Spring Security's cache control
	 * you can use the following:
	 *
	 *

	 * http.headers().defaultsDisabled().cacheControl();
	 *

	 *
	 * @return the {@link HeadersConfigurer} for additional customization
	 */
	public HeadersConfigurer<H> defaultsDisabled() {
		contentTypeOptions.disable();
		xssProtection.disable();
		cacheControl.disable();
		hsts.disable();
		frameOptions.disable();
		return this;
	}

	@Override
	public void configure(H http) throws Exception {
		HeaderWriterFilter headersFilter = createHeaderWriterFilter();
		http.addFilter(headersFilter);
	}

	/**
	 * Creates the {@link HeaderWriter}
	 *
	 * @return the {@link HeaderWriter}
	 */
	private HeaderWriterFilter createHeaderWriterFilter() {
		List<HeaderWriter> writers = getHeaderWriters();
		if (writers.isEmpty()) {
			throw new IllegalStateException(
					"Headers security is enabled, but no headers will be added. Either add headers or disable headers security");
		}
		HeaderWriterFilter headersFilter = new HeaderWriterFilter(writers);
		headersFilter = postProcess(headersFilter);
		return headersFilter;
	}

	/**
	 * Gets the {@link HeaderWriter} instances and possibly initializes with the defaults.
	 *
	 * @return
	 */
	private List<HeaderWriter> getHeaderWriters() {
		List<HeaderWriter> writers = new ArrayList<>();
		addIfNotNull(writers, contentTypeOptions.writer);
		addIfNotNull(writers, xssProtection.writer);
		addIfNotNull(writers, cacheControl.writer);
		addIfNotNull(writers, hsts.writer);
		addIfNotNull(writers, frameOptions.writer);
		addIfNotNull(writers, hpkp.writer);
		addIfNotNull(writers, contentSecurityPolicy.writer);
		addIfNotNull(writers, referrerPolicy.writer);
		addIfNotNull(writers, featurePolicy.writer);
		writers.addAll(headerWriters);
		return writers;
	}

	private <T> void addIfNotNull(List<T> values, T value) {
		if (value != null) {
			values.add(value);
		}
	}

	/**
	 *

	 * Allows configuration for Referrer Policy.
	 *

	 *
	 *

	 * Configuration is provided to the {@link ReferrerPolicyHeaderWriter} which support the writing
	 * of the header as detailed in the W3C Technical Report:
	 *

	 *

	 *
Referrer-Policy

	 *

	 *
	 *
Default value is:

	 *
	 *

	 * Referrer-Policy: no-referrer
	 *

	 *
	 * @see ReferrerPolicyHeaderWriter
	 * @since 4.2
	 * @return the ReferrerPolicyConfig for additional configuration
	 */
	public ReferrerPolicyConfig referrerPolicy() {
		this.referrerPolicy.writer = new ReferrerPolicyHeaderWriter();
		return this.referrerPolicy;
	}

	/**
	 *

	 * Allows configuration for Referrer Policy.
	 *

	 *
	 *

	 * Configuration is provided to the {@link ReferrerPolicyHeaderWriter} which support the writing
	 * of the header as detailed in the W3C Technical Report:
	 *

	 *

	 *
Referrer-Policy

	 *

	 *
	 * @see ReferrerPolicyHeaderWriter
	 * @since 4.2
	 * @return the ReferrerPolicyConfig for additional configuration
	 * @throws IllegalArgumentException if policy is null or empty
	 */
	public ReferrerPolicyConfig referrerPolicy(ReferrerPolicy policy) {
		this.referrerPolicy.writer = new ReferrerPolicyHeaderWriter(policy);
		return this.referrerPolicy;
	}

	public final class ReferrerPolicyConfig {

		private ReferrerPolicyHeaderWriter writer;

		private ReferrerPolicyConfig() {
		}

		public HeadersConfigurer<H> and() {
			return HeadersConfigurer.this;
		}

	}

	/**
	 * Allows configuration for Feature
	 * Policy.
	 *

	 * Calling this method automatically enables (includes) the {@code Feature-Policy}
	 * header in the response using the supplied policy directive(s).
	 *

	 * Configuration is provided to the {@link FeaturePolicyHeaderWriter} which is
	 * responsible for writing the header.
	 *
	 * @see FeaturePolicyHeaderWriter
	 * @since 5.1
	 * @return the {@link FeaturePolicyHeaderWriter} for additional configuration
	 * @throws IllegalArgumentException if policyDirectives is {@code null} or empty
	 */
	public FeaturePolicyConfig featurePolicy(String policyDirectives) {
		this.featurePolicy.writer = new FeaturePolicyHeaderWriter(policyDirectives);
		return featurePolicy;
	}

	public final class FeaturePolicyConfig {

		private FeaturePolicyHeaderWriter writer;

		private FeaturePolicyConfig() {
		}

		/**
		 * Allows completing configuration of Feature Policy and continuing configuration
		 * of headers.
		 *
		 * @return the {@link HeadersConfigurer} for additional configuration
		 */
		public HeadersConfigurer<H> and() {
			return HeadersConfigurer.this;
		}

	}

}


/*
 * Copyright 2002-2013 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.builders;

import java.io.Serializable;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;

import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.web.filter.CorsFilter;

/**
 * An internal use only {@link Comparator} that sorts the Security {@link Filter}
 * instances to ensure they are in the correct order.
 *
 * @author Rob Winch
 * @since 3.2
 */

@SuppressWarnings("serial")
final class FilterComparator implements Comparator<Filter>, Serializable {
	private static final int INITIAL_ORDER = 100;
	private static final int ORDER_STEP = 100;
	private final Map<String, Integer> filterToOrder = new HashMap<>();

	FilterComparator() {
		Step order = new Step(INITIAL_ORDER, ORDER_STEP);
		put(ChannelProcessingFilter.class, order.next());
		put(ConcurrentSessionFilter.class, order.next());
		put(WebAsyncManagerIntegrationFilter.class, order.next());
		put(SecurityContextPersistenceFilter.class, order.next());
		put(HeaderWriterFilter.class, order.next());
		put(CorsFilter.class, order.next());
		put(CsrfFilter.class, order.next());
		put(LogoutFilter.class, order.next());
		filterToOrder.put(
			"org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter",
				order.next());
		put(X509AuthenticationFilter.class, order.next());
		put(AbstractPreAuthenticatedProcessingFilter.class, order.next());
		filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter",
				order.next());
		filterToOrder.put(
			"org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter",
				order.next());
		put(UsernamePasswordAuthenticationFilter.class, order.next());
		put(ConcurrentSessionFilter.class, order.next());
		filterToOrder.put(
				"org.springframework.security.openid.OpenIDAuthenticationFilter", order.next());
		put(DefaultLoginPageGeneratingFilter.class, order.next());
		put(DefaultLogoutPageGeneratingFilter.class, order.next());
		put(ConcurrentSessionFilter.class, order.next());
		put(DigestAuthenticationFilter.class, order.next());
		filterToOrder.put(
				"org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter", order.next());
		put(BasicAuthenticationFilter.class, order.next());
		put(RequestCacheAwareFilter.class, order.next());
		put(SecurityContextHolderAwareRequestFilter.class, order.next());
		put(JaasApiIntegrationFilter.class, order.next());
		put(RememberMeAuthenticationFilter.class, order.next());
		put(AnonymousAuthenticationFilter.class, order.next());
		filterToOrder.put(
			"org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter",
				order.next());
		put(SessionManagementFilter.class, order.next());
		put(ExceptionTranslationFilter.class, order.next());
		put(FilterSecurityInterceptor.class, order.next());
		put(SwitchUserFilter.class, order.next());
	}

	public int compare(Filter lhs, Filter rhs) {
		Integer left = getOrder(lhs.getClass());
		Integer right = getOrder(rhs.getClass());
		return left - right;
	}

	/**
	 * Determines if a particular {@link Filter} is registered to be sorted
	 *
	 * @param filter
	 * @return
	 */
	public boolean isRegistered(Class<? extends Filter> filter) {
		return getOrder(filter) != null;
	}

	/**
	 * Registers a {@link Filter} to exist after a particular {@link Filter} that is
	 * already registered.
	 * @param filter the {@link Filter} to register
	 * @param afterFilter the {@link Filter} that is already registered and that
	 * {@code filter} should be placed after.
	 */
	public void registerAfter(Class<? extends Filter> filter,
			Class<? extends Filter> afterFilter) {
		Integer position = getOrder(afterFilter);
		if (position == null) {
			throw new IllegalArgumentException(
					"Cannot register after unregistered Filter " + afterFilter);
		}

		put(filter, position + 1);
	}

	/**
	 * Registers a {@link Filter} to exist at a particular {@link Filter} position
	 * @param filter the {@link Filter} to register
	 * @param atFilter the {@link Filter} that is already registered and that
	 * {@code filter} should be placed at.
	 */
	public void registerAt(Class<? extends Filter> filter,
			Class<? extends Filter> atFilter) {
		Integer position = getOrder(atFilter);
		if (position == null) {
			throw new IllegalArgumentException(
					"Cannot register after unregistered Filter " + atFilter);
		}

		put(filter, position);
	}

	/**
	 * Registers a {@link Filter} to exist before a particular {@link Filter} that is
	 * already registered.
	 * @param filter the {@link Filter} to register
	 * @param beforeFilter the {@link Filter} that is already registered and that
	 * {@code filter} should be placed before.
	 */
	public void registerBefore(Class<? extends Filter> filter,
			Class<? extends Filter> beforeFilter) {
		Integer position = getOrder(beforeFilter);
		if (position == null) {
			throw new IllegalArgumentException(
					"Cannot register after unregistered Filter " + beforeFilter);
		}

		put(filter, position - 1);
	}

	private void put(Class<? extends Filter> filter, int position) {
		String className = filter.getName();
		filterToOrder.put(className, position);
	}

	/**
	 * Gets the order of a particular {@link Filter} class taking into consideration
	 * superclasses.
	 *
	 * @param clazz the {@link Filter} class to determine the sort order
	 * @return the sort order or null if not defined
	 */
	private Integer getOrder(Class<?> clazz) {
		while (clazz != null) {
			Integer result = filterToOrder.get(clazz.getName());
			if (result != null) {
				return result;
			}
			clazz = clazz.getSuperclass();
		}
		return null;
	}

	private static class Step {

		private int value;
		private final int stepSize;

		Step(int initialValue, int stepSize) {
			this.value = initialValue;
			this.stepSize = stepSize;
		}

		int next() {
			int value = this.value;
			this.value += this.stepSize;
			return value;
		}

	}

}


/*
 * Copyright 2002-2018 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.oauth2.server.resource.authentication;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.stream.Collectors;

import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.util.StringUtils;

/**
 * Extracts the {@link GrantedAuthority}s from scope attributes typically found in a
 * {@link Jwt}.
 *
 * @author Eric Deandrea
 * @since 5.2
 */
public final class JwtGrantedAuthoritiesConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
	private static final String SCOPE_AUTHORITY_PREFIX = "SCOPE_";

	private static final Collection<String> WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES =
			Arrays.asList("scope", "scp");

	/**
	 * Extracts the authorities
	 * @param jwt The {@link Jwt} token
	 * @return The {@link GrantedAuthority authorities} read from the token scopes
	 */
	@Override
	public Collection<GrantedAuthority> convert(Jwt jwt) {
		return getScopes(jwt)
				.stream()
				.map(authority -> SCOPE_AUTHORITY_PREFIX + authority)
				.map(SimpleGrantedAuthority::new)
				.collect(Collectors.toList());
	}

	/**
	 * Gets the scopes from a {@link Jwt} token
	 * @param jwt The {@link Jwt} token
	 * @return The scopes from the token
	 */
	private Collection<String> getScopes(Jwt jwt) {
		for ( String attributeName : WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES ) {
			Object scopes = jwt.getClaims().get(attributeName);
			if (scopes instanceof String) {
				if (StringUtils.hasText((String) scopes)) {
					return Arrays.asList(((String) scopes).split(" "));
				} else {
					return Collections.emptyList();
				}
			} else if (scopes instanceof Collection) {
				return (Collection<String>) scopes;
			}
		}

		return Collections.emptyList();
	}
}