diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java index e5e588a40a3..69bc3ab70ce 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java @@ -62,6 +62,7 @@ import org.springframework.security.web.PortMapperImpl; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; +import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; import org.springframework.security.web.session.HttpSessionEventPublisher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -238,6 +239,128 @@ public OpenIDLoginConfigurer openidLogin() throws Exception { return getOrApply(new OpenIDLoginConfigurer<>()); } + /** + * Allows configuring OpenID based authentication. + * + *

Example Configurations

+ * + * A basic example accepting the defaults and not using attribute exchange: + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class OpenIDLoginConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) {
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.openidLogin(openidLogin ->
+	 * 				openidLogin
+	 * 					.permitAll()
+	 * 			);
+	 * 	}
+	 *
+	 * 	@Override
+	 * 	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+	 * 		auth.inMemoryAuthentication()
+	 * 				// the username must match the OpenID of the user you are
+	 * 				// logging in with
+	 * 				.withUser(
+	 * 						"https://www.google.com/accounts/o8/id?id=lmkCn9xzPdsxVwG7pjYMuDgNNdASFmobNkcRPaWU")
+	 * 				.password("password").roles("USER");
+	 * 	}
+	 * }
+	 * 
+ * + * A more advanced example demonstrating using attribute exchange and providing a + * custom AuthenticationUserDetailsService that will make any user that authenticates + * a valid user. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class OpenIDLoginConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.openidLogin(openidLogin ->
+	 * 				openidLogin
+	 * 					.loginPage("/login")
+	 * 					.permitAll()
+	 * 					.authenticationUserDetailsService(
+	 * 						new AutoProvisioningUserDetailsService())
+	 * 					.attributeExchange(googleExchange ->
+	 * 						googleExchange
+	 * 							.identifierPattern("https://www.google.com/.*")
+	 * 							.attribute(emailAttribute ->
+	 * 								emailAttribute
+	 * 									.name("email")
+	 * 									.type("https://axschema.org/contact/email")
+	 * 									.required(true)
+	 * 							)
+	 * 							.attribute(firstnameAttribute ->
+	 * 								firstnameAttribute
+	 * 									.name("firstname")
+	 * 									.type("https://axschema.org/namePerson/first")
+	 * 									.required(true)
+	 * 							)
+	 * 							.attribute(lastnameAttribute ->
+	 * 								lastnameAttribute
+	 * 									.name("lastname")
+	 * 									.type("https://axschema.org/namePerson/last")
+	 * 									.required(true)
+	 * 							)
+	 * 					)
+	 * 					.attributeExchange(yahooExchange ->
+	 * 						yahooExchange
+	 * 							.identifierPattern(".*yahoo.com.*")
+	 * 							.attribute(emailAttribute ->
+	 * 								emailAttribute
+	 * 									.name("email")
+	 * 									.type("https://schema.openid.net/contact/email")
+	 * 									.required(true)
+	 * 							)
+	 * 							.attribute(fullnameAttribute ->
+	 * 								fullnameAttribute
+	 * 									.name("fullname")
+	 * 									.type("https://axschema.org/namePerson")
+	 * 									.required(true)
+	 * 							)
+	 * 					)
+	 * 			);
+	 * 	}
+	 * }
+	 *
+	 * public class AutoProvisioningUserDetailsService implements
+	 * 		AuthenticationUserDetailsService<OpenIDAuthenticationToken> {
+	 * 	public UserDetails loadUserDetails(OpenIDAuthenticationToken token)
+	 * 			throws UsernameNotFoundException {
+	 * 		return new User(token.getName(), "NOTUSED",
+	 * 				AuthorityUtils.createAuthorityList("ROLE_USER"));
+	 * 	}
+	 * }
+	 * 
+ * + * @see OpenIDLoginConfigurer + * + * @param openidLoginCustomizer the {@link Customizer} to provide more options for + * the {@link OpenIDLoginConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity openidLogin(Customizer> openidLoginCustomizer) throws Exception { + openidLoginCustomizer.customize(getOrApply(new OpenIDLoginConfigurer<>())); + return HttpSecurity.this; + } + /** * Adds the Security headers to the response. This is activated by default when using * {@link WebSecurityConfigurerAdapter}'s default constructor. Accepting the @@ -338,6 +461,103 @@ public HeadersConfigurer headers() throws Exception { return getOrApply(new HeadersConfigurer<>()); } + /** + * Adds the Security headers to the response. This is activated by default when using + * {@link WebSecurityConfigurerAdapter}'s default constructor. + * + *

Example Configurations

+ * + * Accepting the default provided by {@link WebSecurityConfigurerAdapter} or only invoking + * {@link #headers()} without invoking additional methods on it, is the equivalent of: + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 *	@Override
+	 *	protected void configure(HttpSecurity http) throws Exception {
+	 *		http
+	 *			.headers(headers ->
+	 *				headers
+	 *					.contentTypeOptions(withDefaults())
+	 *					.xssProtection(withDefaults())
+	 *					.cacheControl(withDefaults())
+	 *					.httpStrictTransportSecurity(withDefaults())
+	 *					.frameOptions(withDefaults()
+	 *			);
+	 *	}
+	 * }
+	 * 
+ * + * You can disable the headers using the following: + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 *	@Override
+	 *	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.headers(headers -> headers.disable());
+	 *	}
+	 * }
+	 * 
+ * + * You can enable only a few of the headers by first invoking + * {@link HeadersConfigurer#defaultsDisabled()} + * and then invoking the appropriate methods on the {@link #headers()} result. + * For example, the following will enable {@link HeadersConfigurer#cacheControl()} and + * {@link HeadersConfigurer#frameOptions()} only. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 *	@Override
+	 *	protected void configure(HttpSecurity http) throws Exception {
+	 *		http
+	 *			.headers(headers ->
+	 *				headers
+	 *			 		.defaultsDisabled()
+	 *			 		.cacheControl(withDefaults())
+	 *			 		.frameOptions(withDefaults())
+	 *			);
+	 * 	}
+	 * }
+	 * 
+ * + * You can also choose to keep the defaults but explicitly disable a subset of headers. + * For example, the following will enable all the default headers except + * {@link HeadersConfigurer#frameOptions()}. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 *  protected void configure(HttpSecurity http) throws Exception {
+	 *  	http
+	 *  		.headers(headers ->
+	 *  			headers
+	 *  				.frameOptions(frameOptions -> frameOptions.disable())
+	 *  		);
+	 * }
+	 * 
+ * + * @param headersCustomizer the {@link Customizer} to provide more options for + * the {@link HeadersConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity headers(Customizer> headersCustomizer) throws Exception { + headersCustomizer.customize(getOrApply(new HeadersConfigurer<>())); + return HttpSecurity.this; + } + /** * Adds a {@link CorsFilter} to be used. If a bean by the name of corsFilter is * provided, that {@link CorsFilter} is used. Else if corsConfigurationSource is @@ -351,6 +571,36 @@ public CorsConfigurer cors() throws Exception { return getOrApply(new CorsConfigurer<>()); } + /** + * Adds a {@link CorsFilter} to be used. If a bean by the name of corsFilter is + * provided, that {@link CorsFilter} is used. Else if corsConfigurationSource is + * defined, then that {@link CorsConfiguration} is used. Otherwise, if Spring MVC is + * on the classpath a {@link HandlerMappingIntrospector} is used. + * You can enable CORS using: + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class CorsSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 *     protected void configure(HttpSecurity http) throws Exception {
+	 *         http
+	 *             .cors(withDefaults());
+	 *     }
+	 * }
+	 * 
+ * + * @param corsCustomizer the {@link Customizer} to provide more options for + * the {@link CorsConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity cors(Customizer> corsCustomizer) throws Exception { + corsCustomizer.customize(getOrApply(new CorsConfigurer<>())); + return HttpSecurity.this; + } + /** * Allows configuring of Session Management. * @@ -403,6 +653,70 @@ public SessionManagementConfigurer sessionManagement() throws Exce return getOrApply(new SessionManagementConfigurer<>()); } + /** + * Allows configuring of Session Management. + * + *

Example Configuration

+ * + * The following configuration demonstrates how to enforce that only a single instance + * of a user is authenticated at a time. If a user authenticates with the username + * "user" without logging out and an attempt to authenticate with "user" is made the + * first session will be forcibly terminated and sent to the "/login?expired" URL. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class SessionManagementSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.anyRequest().hasRole("USER")
+	 * 			)
+	 * 			.formLogin(formLogin ->
+	 * 				formLogin
+	 * 					.permitAll()
+	 * 			)
+	 * 			.sessionManagement(sessionManagement ->
+	 * 				sessionManagement
+	 * 					.sessionConcurrency(sessionConcurrency ->
+	 * 						sessionConcurrency
+	 * 							.maximumSessions(1)
+	 * 							.expiredUrl("/login?expired")
+	 * 					)
+	 * 			);
+	 * 	}
+	 * }
+	 * 
+ * + * When using {@link SessionManagementConfigurer#maximumSessions(int)}, do not forget + * to configure {@link HttpSessionEventPublisher} for the application to ensure that + * expired sessions are cleaned up. + * + * In a web.xml this can be configured using the following: + * + *
+	 * <listener>
+	 *      <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
+	 * </listener>
+	 * 
+ * + * Alternatively, + * {@link AbstractSecurityWebApplicationInitializer#enableHttpSessionEventPublisher()} + * could return true. + * + * @param sessionManagementCustomizer the {@link Customizer} to provide more options for + * the {@link SessionManagementConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity sessionManagement(Customizer> sessionManagementCustomizer) throws Exception { + sessionManagementCustomizer.customize(getOrApply(new SessionManagementConfigurer<>())); + return HttpSecurity.this; + } + /** * Allows configuring a {@link PortMapper} that is available from * {@link HttpSecurity#getSharedObject(Class)}. Other provided @@ -446,6 +760,53 @@ public PortMapperConfigurer portMapper() throws Exception { return getOrApply(new PortMapperConfigurer<>()); } + /** + * Allows configuring a {@link PortMapper} that is available from + * {@link HttpSecurity#getSharedObject(Class)}. Other provided + * {@link SecurityConfigurer} objects use this configured {@link PortMapper} as a + * default {@link PortMapper} when redirecting from HTTP to HTTPS or from HTTPS to + * HTTP (for example when used in combination with {@link #requiresChannel()}. By + * default Spring Security uses a {@link PortMapperImpl} which maps the HTTP port 8080 + * to the HTTPS port 8443 and the HTTP port of 80 to the HTTPS port of 443. + * + *

Example Configuration

+ * + * The following configuration will ensure that redirects within Spring Security from + * HTTP of a port of 9090 will redirect to HTTPS port of 9443 and the HTTP port of 80 + * to the HTTPS port of 443. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class PortMapperSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.requiresChannel(requiresChannel ->
+	 * 				requiresChannel
+	 * 					.anyRequest().requiresSecure()
+	 * 			)
+	 * 			.portMapper(portMapper ->
+	 * 				portMapper
+	 * 					.http(9090).mapsTo(9443)
+	 * 					.http(80).mapsTo(443)
+	 * 			);
+	 * 	}
+	 * }
+	 * 
+ * + * @see #requiresChannel() + * @param portMapperCustomizer the {@link Customizer} to provide more options for + * the {@link PortMapperConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity portMapper(Customizer> portMapperCustomizer) throws Exception { + portMapperCustomizer.customize(getOrApply(new PortMapperConfigurer<>())); + return HttpSecurity.this; + } + /** * Configures container based pre authentication. In this case, authentication * is managed by the Servlet Container. @@ -519,41 +880,158 @@ public JeeConfigurer jee() throws Exception { } /** - * Configures X509 based pre authentication. + * Configures container based pre authentication. In this case, authentication + * is managed by the Servlet Container. * *

Example Configuration

* - * The following configuration will attempt to extract the username from the X509 - * certificate. Remember that the Servlet Container will need to be configured to - * request client certificates in order for this to work. + * The following configuration will use the principal found on the + * {@link HttpServletRequest} and if the user is in the role "ROLE_USER" or + * "ROLE_ADMIN" will add that to the resulting {@link Authentication}. * *
 	 * @Configuration
 	 * @EnableWebSecurity
-	 * public class X509SecurityConfig extends WebSecurityConfigurerAdapter {
+	 * public class JeeSecurityConfig extends WebSecurityConfigurerAdapter {
 	 *
 	 * 	@Override
 	 * 	protected void configure(HttpSecurity http) throws Exception {
-	 * 		http.authorizeRequests().antMatchers("/**").hasRole("USER").and()
-	 * 		// Example x509() configuration
-	 * 				.x509();
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.jee(jee ->
+	 * 				jee
+	 * 					.mappableRoles("USER", "ADMIN")
+	 * 			);
 	 * 	}
 	 * }
 	 * 
* - * @return the {@link X509Configurer} for further customizations - * @throws Exception - */ - public X509Configurer x509() throws Exception { - return getOrApply(new X509Configurer<>()); - } - - /** - * Allows configuring of Remember Me authentication. - * - *

Example Configuration

+ * Developers wishing to use pre authentication with the container will need to ensure + * their web.xml configures the security constraints. For example, the web.xml (there + * is no equivalent Java based configuration supported by the Servlet specification) + * might look like: * - * The following configuration demonstrates how to allow token based remember me + *
+	 * <login-config>
+	 *     <auth-method>FORM</auth-method>
+	 *     <form-login-config>
+	 *         <form-login-page>/login</form-login-page>
+	 *         <form-error-page>/login?error</form-error-page>
+	 *     </form-login-config>
+	 * </login-config>
+	 *
+	 * <security-role>
+	 *     <role-name>ROLE_USER</role-name>
+	 * </security-role>
+	 * <security-constraint>
+	 *     <web-resource-collection>
+	 *     <web-resource-name>Public</web-resource-name>
+	 *         <description>Matches unconstrained pages</description>
+	 *         <url-pattern>/login</url-pattern>
+	 *         <url-pattern>/logout</url-pattern>
+	 *         <url-pattern>/resources/*</url-pattern>
+	 *     </web-resource-collection>
+	 * </security-constraint>
+	 * <security-constraint>
+	 *     <web-resource-collection>
+	 *         <web-resource-name>Secured Areas</web-resource-name>
+	 *         <url-pattern>/*</url-pattern>
+	 *     </web-resource-collection>
+	 *     <auth-constraint>
+	 *         <role-name>ROLE_USER</role-name>
+	 *     </auth-constraint>
+	 * </security-constraint>
+	 * 
+ * + * Last you will need to configure your container to contain the user with the correct + * roles. This configuration is specific to the Servlet Container, so consult your + * Servlet Container's documentation. + * + * @param jeeCustomizer the {@link Customizer} to provide more options for + * the {@link JeeConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity jee(Customizer> jeeCustomizer) throws Exception { + jeeCustomizer.customize(getOrApply(new JeeConfigurer<>())); + return HttpSecurity.this; + } + + /** + * Configures X509 based pre authentication. + * + *

Example Configuration

+ * + * The following configuration will attempt to extract the username from the X509 + * certificate. Remember that the Servlet Container will need to be configured to + * request client certificates in order for this to work. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class X509SecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http.authorizeRequests().antMatchers("/**").hasRole("USER").and()
+	 * 		// Example x509() configuration
+	 * 				.x509();
+	 * 	}
+	 * }
+	 * 
+ * + * @return the {@link X509Configurer} for further customizations + * @throws Exception + */ + public X509Configurer x509() throws Exception { + return getOrApply(new X509Configurer<>()); + } + + /** + * Configures X509 based pre authentication. + * + *

Example Configuration

+ * + * The following configuration will attempt to extract the username from the X509 + * certificate. Remember that the Servlet Container will need to be configured to + * request client certificates in order for this to work. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class X509SecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.x509(withDefaults());
+	 * 	}
+	 * }
+	 * 
+ * + * @param x509Customizer the {@link Customizer} to provide more options for + * the {@link X509Configurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity x509(Customizer> x509Customizer) throws Exception { + x509Customizer.customize(getOrApply(new X509Configurer<>())); + return HttpSecurity.this; + } + + /** + * Allows configuring of Remember Me authentication. + * + *

Example Configuration

+ * + * The following configuration demonstrates how to allow token based remember me * authentication. Upon authenticating if the HTTP parameter named "remember-me" * exists, then the user will be remembered even after their * {@link javax.servlet.http.HttpSession} expires. @@ -585,6 +1063,44 @@ public RememberMeConfigurer rememberMe() throws Exception { return getOrApply(new RememberMeConfigurer<>()); } + /** + * Allows configuring of Remember Me authentication. + * + *

Example Configuration

+ * + * The following configuration demonstrates how to allow token based remember me + * authentication. Upon authenticating if the HTTP parameter named "remember-me" + * exists, then the user will be remembered even after their + * {@link javax.servlet.http.HttpSession} expires. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class RememberMeSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.formLogin(withDefaults())
+	 * 			.rememberMe(withDefaults());
+	 * 	}
+	 * }
+	 * 
+ * + * @param rememberMeCustomizer the {@link Customizer} to provide more options for + * the {@link RememberMeConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity rememberMe(Customizer> rememberMeCustomizer) throws Exception { + rememberMeCustomizer.customize(getOrApply(new RememberMeConfigurer<>())); + return HttpSecurity.this; + } + /** * Allows restricting access based upon the {@link HttpServletRequest} using * @@ -656,6 +1172,91 @@ public ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrl .getRegistry(); } + /** + * Allows restricting access based upon the {@link HttpServletRequest} using + * {@link RequestMatcher} implementations (i.e. via URL patterns). + * + *

Example Configurations

+ * + * The most basic example is to configure all URLs to require the role "ROLE_USER". + * The configuration below requires authentication to every URL and will grant access + * to both the user "admin" and "user". + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class AuthorizeUrlsSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.formLogin(withDefaults());
+	 * 	}
+	 * }
+	 * 
+ * + * We can also configure multiple URLs. The configuration below requires + * authentication to every URL and will grant access to URLs starting with /admin/ to + * only the "admin" user. All other URLs either user can access. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class AuthorizeUrlsSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/admin/**").hasRole("ADMIN")
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.formLogin(withDefaults());
+	 * 	}
+	 * }
+	 * 
+ * + * Note that the matchers are considered in order. Therefore, the following is invalid + * because the first matcher matches every request and will never get to the second + * mapping: + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class AuthorizeUrlsSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		 http
+	 * 		 	.authorizeRequests(authorizeRequests ->
+	 * 		 		authorizeRequests
+	 * 			 		.antMatchers("/**").hasRole("USER")
+	 * 			 		.antMatchers("/admin/**").hasRole("ADMIN")
+	 * 		 	);
+	 * 	}
+	 * }
+	 * 
+ * + * @see #requestMatcher(RequestMatcher) + * + * @param authorizeRequestsCustomizer the {@link Customizer} to provide more options for + * the {@link ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity authorizeRequests(Customizer.ExpressionInterceptUrlRegistry> authorizeRequestsCustomizer) + throws Exception { + ApplicationContext context = getContext(); + authorizeRequestsCustomizer.customize(getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context)) + .getRegistry()); + return HttpSecurity.this; + } + /** * Allows configuring the Request Cache. For example, a protected page (/protected) * may be requested prior to authentication. The application will redirect the user to @@ -670,6 +1271,47 @@ public RequestCacheConfigurer requestCache() throws Exception { return getOrApply(new RequestCacheConfigurer<>()); } + /** + * Allows configuring the Request Cache. For example, a protected page (/protected) + * may be requested prior to authentication. The application will redirect the user to + * a login page. After authentication, Spring Security will redirect the user to the + * originally requested protected page (/protected). This is automatically applied + * when using {@link WebSecurityConfigurerAdapter}. + * + *

Example Custom Configuration

+ * + * The following example demonstrates how to disable request caching. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class RequestCacheDisabledSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.requestCache(requestCache ->
+	 * 				requestCache.disable()
+	 * 			);
+	 * 	}
+	 * }
+	 * 
+ * + * @param requestCacheCustomizer the {@link Customizer} to provide more options for + * the {@link RequestCacheConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity requestCache(Customizer> requestCacheCustomizer) + throws Exception { + requestCacheCustomizer.customize(getOrApply(new RequestCacheConfigurer<>())); + return HttpSecurity.this; + } + /** * Allows configuring exception handling. This is automatically applied when using * {@link WebSecurityConfigurerAdapter}. @@ -681,6 +1323,46 @@ public ExceptionHandlingConfigurer exceptionHandling() throws Exce return getOrApply(new ExceptionHandlingConfigurer<>()); } + /** + * Allows configuring exception handling. This is automatically applied when using + * {@link WebSecurityConfigurerAdapter}. + * + *

Example Custom Configuration

+ * + * The following customization will ensure that users who are denied access are forwarded + * to the page "/errors/access-denied". + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class ExceptionHandlingSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			// sample exception handling customization
+	 * 			.exceptionHandling(exceptionHandling ->
+	 * 				exceptionHandling
+	 * 					.accessDeniedPage("/errors/access-denied")
+	 * 			);
+	 * 	}
+	 * }
+	 * 
+ * + * @param exceptionHandlingCustomizer the {@link Customizer} to provide more options for + * the {@link ExceptionHandlingConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity exceptionHandling(Customizer> exceptionHandlingCustomizer) throws Exception { + exceptionHandlingCustomizer.customize(getOrApply(new ExceptionHandlingConfigurer<>())); + return HttpSecurity.this; + } + /** * Sets up management of the {@link SecurityContext} on the * {@link SecurityContextHolder} between {@link HttpServletRequest}'s. This is @@ -693,6 +1375,39 @@ public SecurityContextConfigurer securityContext() throws Exceptio return getOrApply(new SecurityContextConfigurer<>()); } + /** + * Sets up management of the {@link SecurityContext} on the + * {@link SecurityContextHolder} between {@link HttpServletRequest}'s. This is + * automatically applied when using {@link WebSecurityConfigurerAdapter}. + * + * The following customization specifies the shared {@link SecurityContextRepository} + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class SecurityContextSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.securityContext(securityContext ->
+	 * 				securityContext
+	 * 					.securityContextRepository(SCR)
+	 * 			);
+	 * 	}
+	 * }
+	 * 
+ * + * @param securityContextCustomizer the {@link Customizer} to provide more options for + * the {@link SecurityContextConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity securityContext(Customizer> securityContextCustomizer) throws Exception { + securityContextCustomizer.customize(getOrApply(new SecurityContextConfigurer<>())); + return HttpSecurity.this; + } + /** * Integrates the {@link HttpServletRequest} methods with the values found on the * {@link SecurityContext}. This is automatically applied when using @@ -705,6 +1420,36 @@ public ServletApiConfigurer servletApi() throws Exception { return getOrApply(new ServletApiConfigurer<>()); } + /** + * Integrates the {@link HttpServletRequest} methods with the values found on the + * {@link SecurityContext}. This is automatically applied when using + * {@link WebSecurityConfigurerAdapter}. You can disable it using: + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class ServletApiSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.servletApi(servletApi ->
+	 * 				servletApi.disable()
+	 * 			);
+	 * 	}
+	 * }
+	 * 
+ * + * @param servletApiCustomizer the {@link Customizer} to provide more options for + * the {@link ServletApiConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity servletApi(Customizer> servletApiCustomizer) throws Exception { + servletApiCustomizer.customize(getOrApply(new ServletApiConfigurer<>())); + return HttpSecurity.this; + } + /** * Adds CSRF support. This is activated by default when using * {@link WebSecurityConfigurerAdapter}'s default constructor. You can disable it @@ -732,6 +1477,35 @@ public CsrfConfigurer csrf() throws Exception { return getOrApply(new CsrfConfigurer<>(context)); } + /** + * Adds CSRF support. This is activated by default when using + * {@link WebSecurityConfigurerAdapter}'s default constructor. You can disable it + * using: + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 *     protected void configure(HttpSecurity http) throws Exception {
+	 *         http
+	 *             .csrf(csrf -> csrf.disable());
+	 *     }
+	 * }
+	 * 
+ * + * @param csrfCustomizer the {@link Customizer} to provide more options for + * the {@link CsrfConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity csrf(Customizer> csrfCustomizer) throws Exception { + ApplicationContext context = getContext(); + csrfCustomizer.customize(getOrApply(new CsrfConfigurer<>(context))); + return HttpSecurity.this; + } + /** * Provides logout support. This is automatically applied when using * {@link WebSecurityConfigurerAdapter}. The default is that accessing the URL @@ -773,6 +1547,53 @@ public LogoutConfigurer logout() throws Exception { return getOrApply(new LogoutConfigurer<>()); } + /** + * Provides logout support. This is automatically applied when using + * {@link WebSecurityConfigurerAdapter}. The default is that accessing the URL + * "/logout" will log the user out by invalidating the HTTP Session, cleaning up any + * {@link #rememberMe()} authentication that was configured, clearing the + * {@link SecurityContextHolder}, and then redirect to "/login?success". + * + *

Example Custom Configuration

+ * + * The following customization to log out when the URL "/custom-logout" is invoked. + * Log out will remove the cookie named "remove", not invalidate the HttpSession, + * clear the SecurityContextHolder, and upon completion redirect to "/logout-success". + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class LogoutSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.formLogin(withDefaults())
+	 * 			// sample logout customization
+	 * 			.logout(logout ->
+	 * 				logout.deleteCookies("remove")
+	 * 					.invalidateHttpSession(false)
+	 * 					.logoutUrl("/custom-logout")
+	 * 					.logoutSuccessUrl("/logout-success")
+	 * 			);
+	 * 	}
+	 * }
+	 * 
+ * + * @param logoutCustomizer the {@link Customizer} to provide more options for + * the {@link LogoutConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity logout(Customizer> logoutCustomizer) throws Exception { + logoutCustomizer.customize(getOrApply(new LogoutConfigurer<>())); + return HttpSecurity.this; + } + /** * Allows configuring how an anonymous user is represented. This is automatically * applied when used in conjunction with {@link WebSecurityConfigurerAdapter}. By @@ -836,6 +1657,76 @@ public AnonymousConfigurer anonymous() throws Exception { return getOrApply(new AnonymousConfigurer<>()); } + /** + * Allows configuring how an anonymous user is represented. This is automatically + * applied when used in conjunction with {@link WebSecurityConfigurerAdapter}. By + * default anonymous users will be represented with an + * {@link org.springframework.security.authentication.AnonymousAuthenticationToken} + * and contain the role "ROLE_ANONYMOUS". + * + *

Example Configuration

+ * + * The following configuration demonstrates how to specify that anonymous users should + * contain the role "ROLE_ANON" instead. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class AnononymousSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.formLogin(withDefaults())
+	 * 			// sample anonymous customization
+	 * 			.anonymous(anonymous ->
+	 * 				anonymous
+	 * 					.authorities("ROLE_ANON")
+	 * 			)
+	 * 	}
+	 * }
+	 * 
+ * + * The following demonstrates how to represent anonymous users as null. Note that this + * can cause {@link NullPointerException} in code that assumes anonymous + * authentication is enabled. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class AnonymousSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.formLogin(withDefaults())
+	 * 			// sample anonymous customization
+	 * 			.anonymous(anonymous ->
+	 * 				anonymous.disabled()
+	 * 			);
+	 * 	}
+	 * }
+	 * 
+ * + * @param anonymousCustomizer the {@link Customizer} to provide more options for + * the {@link AnonymousConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity anonymous(Customizer> anonymousCustomizer) throws Exception { + anonymousCustomizer.customize(getOrApply(new AnonymousConfigurer<>())); + return HttpSecurity.this; + } + + /** * Specifies to support form based authentication. If * {@link FormLoginConfigurer#loginPage(String)} is not specified a default login page @@ -885,19 +1776,86 @@ public AnonymousConfigurer anonymous() throws Exception { * } * * @Override - * protected void configure(AuthenticationManagerBuilder auth) throws Exception { - * auth.inMemoryAuthentication().withUser("user").password("password").roles("USER"); + * protected void configure(AuthenticationManagerBuilder auth) throws Exception { + * auth.inMemoryAuthentication().withUser("user").password("password").roles("USER"); + * } + * } + * + * + * @see FormLoginConfigurer#loginPage(String) + * + * @return the {@link FormLoginConfigurer} for further customizations + * @throws Exception + */ + public FormLoginConfigurer formLogin() throws Exception { + return getOrApply(new FormLoginConfigurer<>()); + } + + /** + * Specifies to support form based authentication. If + * {@link FormLoginConfigurer#loginPage(String)} is not specified a default login page + * will be generated. + * + *

Example Configurations

+ * + * The most basic configuration defaults to automatically generating a login page at + * the URL "/login", redirecting to "/login?error" for authentication failure. The + * details of the login page can be found on + * {@link FormLoginConfigurer#loginPage(String)} + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.formLogin(withDefaults());
+	 * 	}
+	 * }
+	 * 
+ * + * The configuration below demonstrates customizing the defaults. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.formLogin(formLogin ->
+	 * 				formLogin
+	 * 					.usernameParameter("username")
+	 * 					.passwordParameter("password")
+	 * 					.loginPage("/authentication/login")
+	 * 					.failureUrl("/authentication/login?failed")
+	 * 					.loginProcessingUrl("/authentication/login/process")
+	 * 			);
 	 * 	}
 	 * }
 	 * 
* * @see FormLoginConfigurer#loginPage(String) * - * @return the {@link FormLoginConfigurer} for further customizations + * @param formLoginCustomizer the {@link Customizer} to provide more options for + * the {@link FormLoginConfigurer} + * @return the {@link HttpSecurity} for further customizations * @throws Exception */ - public FormLoginConfigurer formLogin() throws Exception { - return getOrApply(new FormLoginConfigurer<>()); + public HttpSecurity formLogin(Customizer> formLoginCustomizer) throws Exception { + formLoginCustomizer.customize(getOrApply(new FormLoginConfigurer<>())); + return HttpSecurity.this; } /** @@ -993,6 +1951,103 @@ public OAuth2LoginConfigurer oauth2Login() throws Exception { return getOrApply(new OAuth2LoginConfigurer<>()); } + /** + * Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0 Provider. + *
+ *
+ * + * The "authentication flow" is implemented using the Authorization Code Grant, as specified in the + * OAuth 2.0 Authorization Framework + * and OpenID Connect Core 1.0 + * specification. + *
+ *
+ * + * As a prerequisite to using this feature, you must register a client with a provider. + * The client registration information may than be used for configuring + * a {@link org.springframework.security.oauth2.client.registration.ClientRegistration} using a + * {@link org.springframework.security.oauth2.client.registration.ClientRegistration.Builder}. + *
+ *
+ * + * {@link org.springframework.security.oauth2.client.registration.ClientRegistration}(s) are composed within a + * {@link org.springframework.security.oauth2.client.registration.ClientRegistrationRepository}, + * which is required and must be registered with the {@link ApplicationContext} or + * configured via oauth2Login().clientRegistrationRepository(..). + *
+ *
+ * + * The default configuration provides an auto-generated login page at "/login" and + * redirects to "/login?error" when an authentication error occurs. + * The login page will display each of the clients with a link + * that is capable of initiating the "authentication flow". + *
+ *
+ * + *

+ *

Example Configuration

+ * + * The following example shows the minimal configuration required, using Google as the Authentication Provider. + * + *
+	 * @Configuration
+	 * public class OAuth2LoginConfig {
+	 *
+	 * 	@EnableWebSecurity
+	 * 	public static class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
+	 * 		@Override
+	 * 		protected void configure(HttpSecurity http) throws Exception {
+	 * 			http
+	 * 				.authorizeRequests(authorizeRequests ->
+	 * 					authorizeRequests
+	 * 						.anyRequest().authenticated()
+	 * 				)
+	 * 				.oauth2Login(withDefaults());
+	 *		}
+	 *	}
+	 *
+	 *	@Bean
+	 *	public ClientRegistrationRepository clientRegistrationRepository() {
+	 *		return new InMemoryClientRegistrationRepository(this.googleClientRegistration());
+	 *	}
+	 *
+	 * 	private ClientRegistration googleClientRegistration() {
+	 * 		return ClientRegistration.withRegistrationId("google")
+	 * 			.clientId("google-client-id")
+	 * 			.clientSecret("google-client-secret")
+	 * 			.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
+	 * 			.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+	 * 			.redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}")
+	 * 			.scope("openid", "profile", "email", "address", "phone")
+	 * 			.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
+	 * 			.tokenUri("https://www.googleapis.com/oauth2/v4/token")
+	 * 			.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
+	 * 			.userNameAttributeName(IdTokenClaimNames.SUB)
+	 * 			.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
+	 * 			.clientName("Google")
+	 * 			.build();
+	 *	}
+	 * }
+	 * 
+ * + *

+ * For more advanced configuration, see {@link OAuth2LoginConfigurer} for available options to customize the defaults. + * + * @see Section 4.1 Authorization Code Grant + * @see Section 3.1 Authorization Code Flow + * @see org.springframework.security.oauth2.client.registration.ClientRegistration + * @see org.springframework.security.oauth2.client.registration.ClientRegistrationRepository + * + * @param oauth2LoginCustomizer the {@link Customizer} to provide more options for + * the {@link OAuth2LoginConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity oauth2Login(Customizer> oauth2LoginCustomizer) throws Exception { + oauth2LoginCustomizer.customize(getOrApply(new OAuth2LoginConfigurer<>())); + return HttpSecurity.this; + } + /** * Configures OAuth 2.0 Client support. * @@ -1007,6 +2062,41 @@ public OAuth2ClientConfigurer oauth2Client() throws Exception { return configurer; } + /** + * Configures OAuth 2.0 Client support. + * + *

Example Configuration

+ * + * The following example demonstrates how to enable OAuth 2.0 Client support for all endpoints. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter {
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.anyRequest().authenticated()
+	 * 			)
+	 * 			.oauth2Client(withDefaults());
+	 *	}
+	 * }
+	 * 
+ * + * @see OAuth 2.0 Authorization Framework + * + * @param oauth2ClientCustomizer the {@link Customizer} to provide more options for + * the {@link OAuth2ClientConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity oauth2Client(Customizer> oauth2ClientCustomizer) throws Exception { + oauth2ClientCustomizer.customize(getOrApply(new OAuth2ClientConfigurer<>())); + return HttpSecurity.this; + } + /** * Configures OAuth 2.0 Resource Server support. * @@ -1021,6 +2111,55 @@ public OAuth2ResourceServerConfigurer oauth2ResourceServer() throw return configurer; } + /** + * Configures OAuth 2.0 Resource Server support. + * + *

Example Configuration

+ * + * The following example demonstrates how to configure a custom JWT authentication converter. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter {
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.anyRequest().authenticated()
+	 * 			)
+	 * 			.oauth2ResourceServer(oauth2ResourceServer ->
+	 * 				oauth2ResourceServer
+	 * 					.jwt(jwt ->
+	 * 						jwt
+	 * 							.jwtAuthenticationConverter(jwtDecoder())
+	 * 					)
+	 * 			);
+	 *	}
+	 *
+	 * 	@Bean
+	 * 	public JwtDecoder jwtDecoder() {
+	 * 		return JwtDecoders.fromOidcIssuerLocation(issuerUri);
+	 * 	}
+	 * }
+	 * 
+ * + * @see OAuth 2.0 Authorization Framework + * + * @param oauth2ResourceServerCustomizer the {@link Customizer} to provide more options for + * the {@link OAuth2ResourceServerConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity oauth2ResourceServer(Customizer> oauth2ResourceServerCustomizer) + throws Exception { + OAuth2ResourceServerConfigurer configurer = getOrApply(new OAuth2ResourceServerConfigurer<>(getContext())); + this.postProcess(configurer); + oauth2ResourceServerCustomizer.customize(configurer); + return HttpSecurity.this; + } + /** * Configures channel security. In order for this configuration to be useful at least * one mapping to a required channel must be provided. @@ -1062,6 +2201,52 @@ public ChannelSecurityConfigurer.ChannelRequestMatcherRegistry req .getRegistry(); } + /** + * Configures channel security. In order for this configuration to be useful at least + * one mapping to a required channel must be provided. + * + *

Example Configuration

+ * + * The example below demonstrates how to require HTTPs for every request. Only + * requiring HTTPS for some requests is supported, but not recommended since an + * application that allows for HTTP introduces many security vulnerabilities. For one + * such example, read about Firesheep. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class ChannelSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.formLogin(withDefaults())
+	 * 			.requiresChannel(requiresChannel ->
+	 * 				requiresChannel
+	 * 					.anyRequest().requiresSecure()
+	 * 			);
+	 * 	}
+	 * }
+	 * 
+ * + * @param requiresChannelCustomizer the {@link Customizer} to provide more options for + * the {@link ChannelSecurityConfigurer.ChannelRequestMatcherRegistry} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity requiresChannel(Customizer.ChannelRequestMatcherRegistry> requiresChannelCustomizer) + throws Exception { + ApplicationContext context = getContext(); + requiresChannelCustomizer.customize(getOrApply(new ChannelSecurityConfigurer<>(context)) + .getRegistry()); + return HttpSecurity.this; + } + /** * Configures HTTP Basic authentication. * @@ -1112,9 +2297,10 @@ public HttpBasicConfigurer httpBasic() throws Exception { * @Override * protected void configure(HttpSecurity http) throws Exception { * http - * .authorizeRequests() - * .antMatchers("/**").hasRole("USER") - * .and() + * .authorizeRequests(authorizeRequests -> + * authorizeRequests + * .antMatchers("/**").hasRole("USER") + * ) * .httpBasic(withDefaults()); * } * } @@ -1354,6 +2540,107 @@ public RequestMatcherConfigurer requestMatchers() { return requestMatcherConfigurer; } + /** + * Allows specifying which {@link HttpServletRequest} instances this + * {@link HttpSecurity} will be invoked on. This method allows for easily invoking the + * {@link HttpSecurity} for multiple different {@link RequestMatcher} instances. If + * only a single {@link RequestMatcher} is necessary consider using {@link #mvcMatcher(String)}, + * {@link #antMatcher(String)}, {@link #regexMatcher(String)}, or + * {@link #requestMatcher(RequestMatcher)}. + * + *

+ * Invoking {@link #requestMatchers()} will not override previous invocations of {@link #mvcMatcher(String)}}, + * {@link #requestMatchers()}, {@link #antMatcher(String)}, + * {@link #regexMatcher(String)}, and {@link #requestMatcher(RequestMatcher)}. + *

+ * + *

Example Configurations

+ * + * The following configuration enables the {@link HttpSecurity} for URLs that begin + * with "/api/" or "/oauth/". + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class RequestMatchersSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.requestMatchers(requestMatchers ->
+	 * 				requestMatchers
+	 * 					.antMatchers("/api/**", "/oauth/**")
+	 * 			)
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.httpBasic(withDefaults());
+	 * 	}
+	 * }
+	 * 
+ * + * The configuration below is the same as the previous configuration. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class RequestMatchersSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.requestMatchers(requestMatchers ->
+	 * 				requestMatchers
+	 * 					.antMatchers("/api/**")
+	 * 					.antMatchers("/oauth/**")
+	 * 			)
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.httpBasic(withDefaults());
+	 * 	}
+	 * }
+	 * 
+ * + * The configuration below is also the same as the above configuration. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class RequestMatchersSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.requestMatchers(requestMatchers ->
+	 * 				requestMatchers
+	 * 					.antMatchers("/api/**")
+	 * 			)
+	 *			.requestMatchers(requestMatchers ->
+	 *			requestMatchers
+	 * 				.antMatchers("/oauth/**")
+	 * 			)
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.httpBasic(withDefaults());
+	 * 	}
+	 * }
+	 * 
+ * + * @param requestMatcherCustomizer the {@link Customizer} to provide more options for + * the {@link RequestMatcherConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity requestMatchers(Customizer requestMatcherCustomizer) throws Exception { + requestMatcherCustomizer.customize(requestMatcherConfigurer); + return HttpSecurity.this; + } + /** * Allows configuring the {@link HttpSecurity} to only be invoked when matching the * provided {@link RequestMatcher}. If more advanced configuration is necessary, diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java index 1f38253d407..77c840d5a04 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -23,6 +23,7 @@ import javax.servlet.http.HttpServletRequest; +import org.springframework.security.config.Customizer; 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; @@ -30,6 +31,8 @@ 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.XContentTypeOptionsHeaderWriter; +import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter; 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; @@ -121,6 +124,26 @@ public ContentTypeOptionsConfig contentTypeOptions() { return contentTypeOptions.enable(); } + /** + * Configures the {@link XContentTypeOptionsHeaderWriter} which inserts the X-Content-Type-Options: + * + *
+	 * X-Content-Type-Options: nosniff
+	 * 
+ * + * @param contentTypeOptionsCustomizer the {@link Customizer} to provide more options for + * the {@link ContentTypeOptionsConfig} + * @return the {@link HeadersConfigurer} for additional customizations + * @throws Exception + */ + public HeadersConfigurer contentTypeOptions(Customizer contentTypeOptionsCustomizer) + throws Exception { + contentTypeOptionsCustomizer.customize(contentTypeOptions.enable()); + return HeadersConfigurer.this; + } + public final class ContentTypeOptionsConfig { private XContentTypeOptionsHeaderWriter writer; @@ -174,6 +197,25 @@ public XXssConfig xssProtection() { return xssProtection.enable(); } + /** + * Note this is not comprehensive XSS protection! + * + *

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

+ * + * @param xssCustomizer the {@link Customizer} to provide more options for + * the {@link XXssConfig} + * @return the {@link HeadersConfigurer} for additional customizations + * @throws Exception + */ + public HeadersConfigurer xssProtection(Customizer xssCustomizer) throws Exception { + xssCustomizer.customize(xssProtection.enable()); + return HeadersConfigurer.this; + } + public final class XXssConfig { private XXssProtectionHeaderWriter writer; @@ -268,6 +310,26 @@ public CacheControlConfig cacheControl() { return cacheControl.enable(); } + /** + * 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
  • + *
+ * + * @param cacheControlCustomizer the {@link Customizer} to provide more options for + * the {@link CacheControlConfig} + * @return the {@link HeadersConfigurer} for additional customizations + * @throws Exception + */ + public HeadersConfigurer cacheControl(Customizer cacheControlCustomizer) throws Exception { + cacheControlCustomizer.customize(cacheControl.enable()); + return HeadersConfigurer.this; + } + + public final class CacheControlConfig { private CacheControlHeadersWriter writer; @@ -319,6 +381,21 @@ public HstsConfig httpStrictTransportSecurity() { return hsts.enable(); } + /** + * Allows customizing the {@link HstsHeaderWriter} which provides support for HTTP Strict Transport Security + * (HSTS). + * + * @param hstsCustomizer the {@link Customizer} to provide more options for + * the {@link HstsConfig} + * @return the {@link HeadersConfigurer} for additional customizations + * @throws Exception + */ + public HeadersConfigurer httpStrictTransportSecurity(Customizer hstsCustomizer) throws Exception { + hstsCustomizer.customize(hsts.enable()); + return HeadersConfigurer.this; + } + public final class HstsConfig { private HstsHeaderWriter writer; @@ -440,6 +517,19 @@ public FrameOptionsConfig frameOptions() { return frameOptions.enable(); } + /** + * Allows customizing the {@link XFrameOptionsHeaderWriter}. + * + * @param frameOptionsCustomizer the {@link Customizer} to provide more options for + * the {@link FrameOptionsConfig} + * @return the {@link HeadersConfigurer} for additional customizations + * @throws Exception + */ + public HeadersConfigurer frameOptions(Customizer frameOptionsCustomizer) throws Exception { + frameOptionsCustomizer.customize(frameOptions.enable()); + return HeadersConfigurer.this; + } + public final class FrameOptionsConfig { private XFrameOptionsHeaderWriter writer; @@ -516,6 +606,20 @@ public HpkpConfig httpPublicKeyPinning() { return hpkp.enable(); } + /** + * Allows customizing the {@link HpkpHeaderWriter} which provides support for HTTP Public Key Pinning (HPKP). + * + * @param hpkpCustomizer the {@link Customizer} to provide more options for + * the {@link HpkpConfig} + * @return the {@link HeadersConfigurer} for additional customizations + * @throws Exception + */ + public HeadersConfigurer httpPublicKeyPinning(Customizer hpkpCustomizer) throws Exception { + hpkpCustomizer.customize(hpkp.enable()); + return HeadersConfigurer.this; + } + public final class HpkpConfig { private HpkpHeaderWriter writer; @@ -713,12 +817,57 @@ public ContentSecurityPolicyConfig contentSecurityPolicy(String policyDirectives return contentSecurityPolicy; } + /** + *

+ * 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 + * @param contentSecurityCustomizer the {@link Customizer} to provide more options for + * the {@link ContentSecurityPolicyConfig} + * @return the {@link HeadersConfigurer} for additional customizations + * @throws Exception + */ + public HeadersConfigurer contentSecurityPolicy(Customizer contentSecurityCustomizer) + throws Exception { + this.contentSecurityPolicy.writer = new ContentSecurityPolicyHeaderWriter(); + contentSecurityCustomizer.customize(this.contentSecurityPolicy); + + return HeadersConfigurer.this; + } + public final class ContentSecurityPolicyConfig { private ContentSecurityPolicyHeaderWriter writer; private ContentSecurityPolicyConfig() { } + /** + * Sets the security policy directive(s) to be used in the response header. + * + * @param policyDirectives the security policy directive(s) + * @return the {@link ContentSecurityPolicyConfig} for additional configuration + * @throws IllegalArgumentException if policyDirectives is null or empty + */ + public ContentSecurityPolicyConfig policyDirectives(String policyDirectives) { + this.writer.setPolicyDirectives(policyDirectives); + return this; + } + /** * Enables (includes) the Content-Security-Policy-Report-Only header in the response. * @@ -860,6 +1009,31 @@ public ReferrerPolicyConfig referrerPolicy(ReferrerPolicy policy) { 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 + * @param referrerPolicyCustomizer the {@link Customizer} to provide more options for + * the {@link ReferrerPolicyConfig} + * @return the {@link HeadersConfigurer} for additional customizations + * @throws Exception + */ + public HeadersConfigurer referrerPolicy(Customizer referrerPolicyCustomizer) throws Exception { + this.referrerPolicy.writer = new ReferrerPolicyHeaderWriter(); + referrerPolicyCustomizer.customize(this.referrerPolicy); + return HeadersConfigurer.this; + } + public final class ReferrerPolicyConfig { private ReferrerPolicyHeaderWriter writer; @@ -867,6 +1041,18 @@ public final class ReferrerPolicyConfig { private ReferrerPolicyConfig() { } + /** + * Sets the policy to be used in the response header. + * + * @param policy a referrer policy + * @return the {@link ReferrerPolicyConfig} for additional configuration + * @throws IllegalArgumentException if policy is null + */ + public ReferrerPolicyConfig policy(ReferrerPolicy policy) { + this.writer.setPolicy(policy); + return this; + } + public HeadersConfigurer and() { return HeadersConfigurer.this; } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java index cb7c2205589..ad8f4e033ac 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java @@ -26,6 +26,7 @@ import org.springframework.context.event.GenericApplicationListenerAdapter; import org.springframework.context.event.SmartApplicationListener; import org.springframework.security.authentication.AuthenticationTrustResolver; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; @@ -249,6 +250,19 @@ public SessionFixationConfigurer sessionFixation() { return new SessionFixationConfigurer(); } + /** + * Allows configuring session fixation protection. + * + * @param sessionFixationCustomizer the {@link Customizer} to provide more options for + * the {@link SessionFixationConfigurer} + * @return the {@link SessionManagementConfigurer} for further customizations + */ + public SessionManagementConfigurer sessionFixation(Customizer sessionFixationCustomizer) + throws Exception { + sessionFixationCustomizer.customize(new SessionFixationConfigurer()); + return this; + } + /** * Controls the maximum number of sessions for a user. The default is to allow any * number of users. @@ -260,6 +274,20 @@ public ConcurrencyControlConfigurer maximumSessions(int maximumSessions) { return new ConcurrencyControlConfigurer(); } + /** + * Controls the maximum number of sessions for a user. The default is to allow any + * number of users. + * + * @param sessionConcurrencyCustomizer the {@link Customizer} to provide more options for + * the {@link ConcurrencyControlConfigurer} + * @return the {@link SessionManagementConfigurer} for further customizations + */ + public SessionManagementConfigurer sessionConcurrency(Customizer sessionConcurrencyCustomizer) + throws Exception { + sessionConcurrencyCustomizer.customize(new ConcurrencyControlConfigurer()); + return this; + } + /** * Invokes {@link #postProcess(Object)} and sets the * {@link SessionAuthenticationStrategy} for session fixation. @@ -338,6 +366,18 @@ public SessionManagementConfigurer none() { */ public final class ConcurrencyControlConfigurer { + /** + * Controls the maximum number of sessions for a user. The default is to allow any + * number of users. + * + * @param maximumSessions the maximum number of sessions for a user + * @return the {@link ConcurrencyControlConfigurer} for further customizations + */ + public ConcurrencyControlConfigurer maximumSessions(int maximumSessions) { + SessionManagementConfigurer.this.maximumSessions = maximumSessions; + return this; + } + /** * The URL to redirect to if a user tries to access a resource and their session * has been expired due to too many sessions for the current user. The default is diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurer.java index f4a5c2c366b..066ca0c692e 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -16,6 +16,7 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.client; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; @@ -135,6 +136,20 @@ public AuthorizationCodeGrantConfigurer authorizationCodeGrant() { return this.authorizationCodeGrantConfigurer; } + /** + * Configures the OAuth 2.0 Authorization Code Grant. + * + * @param authorizationCodeGrantCustomizer the {@link Customizer} to provide more options for + * the {@link AuthorizationCodeGrantConfigurer} + * @return the {@link OAuth2ClientConfigurer} for further customizations + * @throws Exception + */ + public OAuth2ClientConfigurer authorizationCodeGrant(Customizer authorizationCodeGrantCustomizer) + throws Exception { + authorizationCodeGrantCustomizer.customize(this.authorizationCodeGrantConfigurer); + return this; + } + /** * Configuration options for the OAuth 2.0 Authorization Code Grant. */ diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java index 5e0bb87a2f8..b54b5225912 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java @@ -20,6 +20,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.core.ResolvableType; import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; @@ -201,6 +202,20 @@ public AuthorizationEndpointConfig authorizationEndpoint() { return this.authorizationEndpointConfig; } + /** + * Configures the Authorization Server's Authorization Endpoint. + * + * @param authorizationEndpointCustomizer the {@link Customizer} to provide more options for + * the {@link AuthorizationEndpointConfig} + * @return the {@link OAuth2LoginConfigurer} for further customizations + * @throws Exception + */ + public OAuth2LoginConfigurer authorizationEndpoint(Customizer authorizationEndpointCustomizer) + throws Exception { + authorizationEndpointCustomizer.customize(this.authorizationEndpointConfig); + return this; + } + /** * Configuration options for the Authorization Server's Authorization Endpoint. */ @@ -268,6 +283,20 @@ public TokenEndpointConfig tokenEndpoint() { return this.tokenEndpointConfig; } + /** + * Configures the Authorization Server's Token Endpoint. + * + * @param tokenEndpointCustomizer the {@link Customizer} to provide more options for + * the {@link TokenEndpointConfig} + * @return the {@link OAuth2LoginConfigurer} for further customizations + * @throws Exception + */ + public OAuth2LoginConfigurer tokenEndpoint(Customizer tokenEndpointCustomizer) + throws Exception { + tokenEndpointCustomizer.customize(this.tokenEndpointConfig); + return this; + } + /** * Configuration options for the Authorization Server's Token Endpoint. */ @@ -310,6 +339,20 @@ public RedirectionEndpointConfig redirectionEndpoint() { return this.redirectionEndpointConfig; } + /** + * Configures the Client's Redirection Endpoint. + * + * @param redirectionEndpointCustomizer the {@link Customizer} to provide more options for + * the {@link RedirectionEndpointConfig} + * @return the {@link OAuth2LoginConfigurer} for further customizations + * @throws Exception + */ + public OAuth2LoginConfigurer redirectionEndpoint(Customizer redirectionEndpointCustomizer) + throws Exception { + redirectionEndpointCustomizer.customize(this.redirectionEndpointConfig); + return this; + } + /** * Configuration options for the Client's Redirection Endpoint. */ @@ -350,6 +393,20 @@ public UserInfoEndpointConfig userInfoEndpoint() { return this.userInfoEndpointConfig; } + /** + * Configures the Authorization Server's UserInfo Endpoint. + * + * @param userInfoEndpointCustomizer the {@link Customizer} to provide more options for + * the {@link UserInfoEndpointConfig} + * @return the {@link OAuth2LoginConfigurer} for further customizations + * @throws Exception + */ + public OAuth2LoginConfigurer userInfoEndpoint(Customizer userInfoEndpointCustomizer) + throws Exception { + userInfoEndpointCustomizer.customize(this.userInfoEndpointConfig); + return this; + } + /** * Configuration options for the Authorization Server's UserInfo Endpoint. */ diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java index 2d4ffb0cbd6..589dd391e56 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java @@ -25,6 +25,7 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManagerResolver; import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; @@ -65,11 +66,12 @@ *
  • {@link #accessDeniedHandler(AccessDeniedHandler)}
  • - customizes how access denied errors are handled *
  • {@link #authenticationEntryPoint(AuthenticationEntryPoint)}
  • - customizes how authentication failures are handled *
  • {@link #bearerTokenResolver(BearerTokenResolver)} - customizes how to resolve a bearer token from the request
  • - *
  • {@link #jwt()} - enables Jwt-encoded bearer token support
  • + *
  • {@link #jwt(Customizer)} - enables Jwt-encoded bearer token support
  • + *
  • {@link #opaqueToken(Customizer)} - enables opaque bearer token support
  • * * *

    - * When using {@link #jwt()}, either + * When using {@link #jwt(Customizer)}, either * *

      *
    • @@ -83,7 +85,7 @@ *
    • *
    * - * Also with {@link #jwt()} consider + * Also with {@link #jwt(Customizer)} consider * *
      *
    • @@ -93,12 +95,12 @@ *
    * *

    - * When using {@link #opaque()}, supply an introspection endpoint and its authentication configuration + * When using {@link #opaqueToken(Customizer)}, supply an introspection endpoint and its authentication configuration *

    * *

    Security Filters

    * - * The following {@code Filter}s are populated when {@link #jwt()} is configured: + * The following {@code Filter}s are populated when {@link #jwt(Customizer)} is configured: * *
      *
    • {@link BearerTokenAuthenticationFilter}
    • @@ -180,6 +182,22 @@ public JwtConfigurer jwt() { return this.jwtConfigurer; } + /** + * Enables Jwt-encoded bearer token support. + * + * @param jwtCustomizer the {@link Customizer} to provide more options for + * the {@link JwtConfigurer} + * @return the {@link OAuth2ResourceServerConfigurer} for further customizations + * @throws Exception + */ + public OAuth2ResourceServerConfigurer jwt(Customizer jwtCustomizer) throws Exception { + if ( this.jwtConfigurer == null ) { + this.jwtConfigurer = new JwtConfigurer(this.context); + } + jwtCustomizer.customize(this.jwtConfigurer); + return this; + } + public OpaqueTokenConfigurer opaqueToken() { if (this.opaqueTokenConfigurer == null) { this.opaqueTokenConfigurer = new OpaqueTokenConfigurer(this.context); @@ -188,6 +206,23 @@ public OpaqueTokenConfigurer opaqueToken() { return this.opaqueTokenConfigurer; } + /** + * Enables opaque bearer token support. + * + * @param opaqueTokenCustomizer the {@link Customizer} to provide more options for + * the {@link OpaqueTokenConfigurer} + * @return the {@link OAuth2ResourceServerConfigurer} for further customizations + * @throws Exception + */ + public OAuth2ResourceServerConfigurer opaqueToken(Customizer opaqueTokenCustomizer) + throws Exception { + if (this.opaqueTokenConfigurer == null) { + this.opaqueTokenConfigurer = new OpaqueTokenConfigurer(this.context); + } + opaqueTokenCustomizer.customize(this.opaqueTokenConfigurer); + return this; + } + @Override public void init(H http) throws Exception { registerDefaultAccessDeniedHandler(http); diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurer.java index 71d7c2e2151..01e1b3198a9 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2019 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. @@ -27,6 +27,7 @@ import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.Customizer; 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; @@ -148,6 +149,24 @@ public AttributeExchangeConfigurer attributeExchange(String identifierPattern) { return attributeExchangeConfigurer; } + /** + * Sets up OpenID attribute exchange for OpenIDs matching the specified pattern. + * The default pattern is ".*", it can be specified using + * {@link AttributeExchangeConfigurer#identifierPattern(String)} + * + * @param attributeExchangeCustomizer the {@link Customizer} to provide more options for + * the {@link AttributeExchangeConfigurer} + * @return a {@link OpenIDLoginConfigurer} for further customizations + * @throws Exception + */ + public OpenIDLoginConfigurer attributeExchange(Customizer attributeExchangeCustomizer) + throws Exception { + AttributeExchangeConfigurer attributeExchangeConfigurer = new AttributeExchangeConfigurer(".*"); + attributeExchangeCustomizer.customize(attributeExchangeConfigurer); + this.attributeExchangeConfigurers.add(attributeExchangeConfigurer); + return this; + } + /** * Allows specifying the {@link OpenIDConsumer} to be used. The default is using an * {@link OpenID4JavaConsumer}. @@ -373,7 +392,7 @@ private void initDefaultLoginFilter(H http) { * @author Rob Winch */ public final class AttributeExchangeConfigurer { - private final String identifier; + private String identifier; private List attributes = new ArrayList<>(); private List attributeConfigurers = new ArrayList<>(); @@ -395,6 +414,19 @@ public OpenIDLoginConfigurer and() { return OpenIDLoginConfigurer.this; } + /** + * Sets the regular expression for matching on OpenID's (i.e. + * "https://www.google.com/.*", ".*yahoo.com.*", etc) + * + * @param identifierPattern the regular expression for matching on OpenID's + * @return the {@link AttributeExchangeConfigurer} for further customization of + * attribute exchange + */ + public AttributeExchangeConfigurer identifierPattern(String identifierPattern) { + this.identifier = identifierPattern; + return this; + } + /** * Adds an {@link OpenIDAttribute} to be obtained for the configured OpenID * pattern. @@ -419,6 +451,22 @@ public AttributeConfigurer attribute(String name) { return attributeConfigurer; } + /** + * Adds an {@link OpenIDAttribute} named "default-attribute". + * The name can by updated using {@link AttributeConfigurer#name(String)}. + * + * @param attributeCustomizer the {@link Customizer} to provide more options for + * the {@link AttributeConfigurer} + * @return a {@link AttributeExchangeConfigurer} for further customizations + * @throws Exception + */ + public AttributeExchangeConfigurer attribute(Customizer attributeCustomizer) throws Exception { + AttributeConfigurer attributeConfigurer = new AttributeConfigurer(); + attributeCustomizer.customize(attributeConfigurer); + this.attributeConfigurers.add(attributeConfigurer); + return this; + } + /** * Gets the {@link OpenIDAttribute}'s for the configured OpenID pattern * @return @@ -443,6 +491,16 @@ public final class AttributeConfigurer { private boolean required = false; private String type; + /** + * Creates a new instance named "default-attribute". + * The name can by updated using {@link #name(String)}. + * + * @see AttributeExchangeConfigurer#attribute(String) + */ + private AttributeConfigurer() { + this.name = "default-attribute"; + } + /** * Creates a new instance * @param name the name of the attribute @@ -486,6 +544,16 @@ public AttributeConfigurer type(String type) { return this; } + /** + * The OpenID attribute name. + * @param name + * @return the {@link AttributeConfigurer} for further customizations + */ + public AttributeConfigurer name(String name) { + this.name = name; + return this; + } + /** * Gets the {@link AttributeExchangeConfigurer} for further customization of * the attributes diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurerTests.java index b10dcdbe59f..11d80e02d82 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -18,18 +18,22 @@ import org.junit.Rule; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.test.SpringTestRule; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Rob Winch @@ -44,7 +48,7 @@ public class AnonymousConfigurerTests { @Test public void requestWhenAnonymousTwiceInvokedThenDoesNotOverride() throws Exception { - this.spring.register(InvokeTwiceDoesNotOverride.class).autowire(); + this.spring.register(InvokeTwiceDoesNotOverride.class, PrincipalController.class).autowire(); this.mockMvc.perform(get("/")) .andExpect(content().string("principal")); @@ -63,13 +67,99 @@ protected void configure(HttpSecurity http) throws Exception { .and() .anonymous(); } + } + + @Test + public void requestWhenAnonymousPrincipalInLambdaThenPrincipalUsed() throws Exception { + this.spring.register(AnonymousPrincipalInLambdaConfig.class, PrincipalController.class).autowire(); + + this.mockMvc.perform(get("/")) + .andExpect(content().string("principal")); + } + + @EnableWebSecurity + @EnableWebMvc + static class AnonymousPrincipalInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .anonymous(anonymous -> + anonymous + .principal("principal") + ); + // @formatter:on + } + } + + @Test + public void requestWhenAnonymousDisabledInLambdaThenRespondsWithForbidden() throws Exception { + this.spring.register(AnonymousDisabledInLambdaConfig.class, PrincipalController.class).autowire(); + + this.mockMvc.perform(get("/")) + .andExpect(status().isForbidden()); + } + + @EnableWebSecurity + static class AnonymousDisabledInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().permitAll() + ) + .anonymous(AbstractHttpConfigurer::disable); + // @formatter:on + } + + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + // @formatter:on + } + } + + @Test + public void requestWhenAnonymousWithDefaultsInLambdaThenRespondsWithOk() throws Exception { + this.spring.register(AnonymousWithDefaultsInLambdaConfig.class, PrincipalController.class).autowire(); + + this.mockMvc.perform(get("/")) + .andExpect(status().isOk()); + } + + @EnableWebSecurity + static class AnonymousWithDefaultsInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().permitAll() + ) + .anonymous(withDefaults()); + // @formatter:on + } + + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + // @formatter:on + } + } - @RestController - static class PrincipalController { - @GetMapping("/") - String principal(@AuthenticationPrincipal String principal) { - return principal; - } + @RestController + static class PrincipalController { + @GetMapping("/") + String principal(@AuthenticationPrincipal String principal) { + return principal; } } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java index 526f3b86579..b9fb86db21b 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2019 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. @@ -49,6 +49,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.spy; +import static org.springframework.security.config.Customizer.withDefaults; /** * @author Rob Winch @@ -113,6 +114,39 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception { } } + @Test + public void postWhenPostDenyAllInLambdaThenRespondsWithForbidden() throws Exception { + loadConfig(AntMatchersNoPatternsInLambdaConfig.class); + this.request.setMethod("POST"); + + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN); + } + + @EnableWebSecurity + @Configuration + static class AntMatchersNoPatternsInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers(HttpMethod.POST).denyAll() + ); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication(); + // @formatter:on + } + } + // SEC-2256 @Test public void antMatchersPathVariables() throws Exception { @@ -314,6 +348,66 @@ public String path() { } } + @Test + public void requestWhenMvcMatcherDenyAllThenRespondsWithUnauthorized() throws Exception { + loadConfig(MvcMatcherInLambdaConfig.class); + + this.request.setRequestURI("/path"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()) + .isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + + setup(); + + this.request.setRequestURI("/path.html"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()) + .isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + + setup(); + + this.request.setServletPath("/path/"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()) + .isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + } + + @EnableWebSecurity + @Configuration + @EnableWebMvc + static class MvcMatcherInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .httpBasic(withDefaults()) + .authorizeRequests(authorizeRequests -> + authorizeRequests + .mvcMatchers("/path").denyAll() + ); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication(); + // @formatter:on + } + + @RestController + static class PathController { + @RequestMapping("/path") + public String path() { + return "path"; + } + } + } + @Test public void mvcMatcherServletPath() throws Exception { loadConfig(MvcMatcherServletPathConfig.class); @@ -391,6 +485,85 @@ public String path() { } } + @Test + public void requestWhenMvcMatcherServletPathDenyAllThenMatchesOnServletPath() throws Exception { + loadConfig(MvcMatcherServletPathInLambdaConfig.class); + + this.request.setServletPath("/spring"); + this.request.setRequestURI("/spring/path"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()) + .isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + + setup(); + + this.request.setServletPath("/spring"); + this.request.setRequestURI("/spring/path.html"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()) + .isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + + setup(); + + this.request.setServletPath("/spring"); + this.request.setRequestURI("/spring/path/"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()) + .isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + + setup(); + + this.request.setServletPath("/foo"); + this.request.setRequestURI("/foo/path"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + + setup(); + + this.request.setServletPath("/"); + this.request.setRequestURI("/path"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + } + + @EnableWebSecurity + @Configuration + @EnableWebMvc + static class MvcMatcherServletPathInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .httpBasic(withDefaults()) + .authorizeRequests(authorizeRequests -> + authorizeRequests + .mvcMatchers("/path").servletPath("/spring").denyAll() + ); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication(); + // @formatter:on + } + + @RestController + static class PathController { + @RequestMapping("/path") + public String path() { + return "path"; + } + } + } + @Test public void mvcMatcherPathVariables() throws Exception { loadConfig(MvcMatcherPathVariablesConfig.class); @@ -441,6 +614,58 @@ public String path() { } } + @Test + public void requestWhenMvcMatcherPathVariablesThenMatchesOnPathVariables() throws Exception { + loadConfig(MvcMatcherPathVariablesInLambdaConfig.class); + + this.request.setRequestURI("/user/user"); + + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + + this.setup(); + this.request.setRequestURI("/user/deny"); + + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()) + .isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + } + + @EnableWebSecurity + @Configuration + @EnableWebMvc + static class MvcMatcherPathVariablesInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .httpBasic(withDefaults()) + .authorizeRequests(authorizeRequests -> + authorizeRequests + .mvcMatchers("/user/{userName}").access("#userName == 'user'") + ); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication(); + // @formatter:on + } + + @RestController + static class PathController { + @RequestMapping("/path") + public String path() { + return "path"; + } + } + } + @EnableWebSecurity @Configuration @EnableWebMvc diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurerTests.java index c755c5f3a37..c33682e7169 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurerTests.java @@ -135,4 +135,27 @@ protected void configure(HttpSecurity http) throws Exception { // @formatter:on } } + + @Test + public void requestWhenRequiresChannelConfiguredInLambdaThenRedirectsToHttps() throws Exception { + this.spring.register(RequiresChannelInLambdaConfig.class).autowire(); + + mvc.perform(get("/")) + .andExpect(redirectedUrl("https://localhost/")); + } + + @EnableWebSecurity + static class RequiresChannelInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .requiresChannel(requiresChannel -> + requiresChannel + .anyRequest().requiresSecure() + ); + // @formatter:on + } + } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CorsConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CorsConfigurerTests.java index 8122817caf7..b880b3ef5b1 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CorsConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CorsConfigurerTests.java @@ -42,6 +42,7 @@ import java.util.Collections; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; @@ -131,6 +132,56 @@ String hello() { } } + @Test + public void getWhenDefaultsInLambdaAndCrossOriginAnnotationThenRespondsWithCorsHeaders() throws Exception { + this.spring.register(MvcCorsInLambdaConfig.class).autowire(); + + this.mvc.perform(get("/") + .header(HttpHeaders.ORIGIN, "https://example.com")) + .andExpect(header().exists("Access-Control-Allow-Origin")) + .andExpect(header().exists("X-Content-Type-Options")); + } + + @Test + public void optionsWhenDefaultsInLambdaAndCrossOriginAnnotationThenRespondsWithCorsHeaders() throws Exception { + this.spring.register(MvcCorsInLambdaConfig.class).autowire(); + + this.mvc.perform(options("/") + .header(org.springframework.http.HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.name()) + .header(HttpHeaders.ORIGIN, "https://example.com")) + .andExpect(status().isOk()) + .andExpect(header().exists("Access-Control-Allow-Origin")) + .andExpect(header().exists("X-Content-Type-Options")); + } + + @EnableWebMvc + @EnableWebSecurity + static class MvcCorsInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .cors(withDefaults()); + // @formatter:on + } + + @RestController + @CrossOrigin(methods = { + RequestMethod.GET, RequestMethod.POST + }) + static class CorsController { + @RequestMapping("/") + String hello() { + return "Hello"; + } + } + } + @Test public void getWhenCorsConfigurationSourceBeanThenRespondsWithCorsHeaders() throws Exception { this.spring.register(ConfigSourceConfig.class).autowire(); @@ -180,6 +231,58 @@ CorsConfigurationSource corsConfigurationSource() { } } + @Test + public void getWhenMvcCorsInLambdaConfigAndCorsConfigurationSourceBeanThenRespondsWithCorsHeaders() + throws Exception { + this.spring.register(ConfigSourceInLambdaConfig.class).autowire(); + + this.mvc.perform(get("/") + .header(HttpHeaders.ORIGIN, "https://example.com")) + .andExpect(header().exists("Access-Control-Allow-Origin")) + .andExpect(header().exists("X-Content-Type-Options")); + } + + @Test + public void optionsWhenMvcCorsInLambdaConfigAndCorsConfigurationSourceBeanThenRespondsWithCorsHeaders() + throws Exception { + this.spring.register(ConfigSourceInLambdaConfig.class).autowire(); + + this.mvc.perform(options("/") + .header(org.springframework.http.HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.name()) + .header(HttpHeaders.ORIGIN, "https://example.com")) + .andExpect(status().isOk()) + .andExpect(header().exists("Access-Control-Allow-Origin")) + .andExpect(header().exists("X-Content-Type-Options")); + } + + @EnableWebSecurity + static class ConfigSourceInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .cors(withDefaults()); + // @formatter:on + } + + @Bean + CorsConfigurationSource corsConfigurationSource() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration corsConfiguration = new CorsConfiguration(); + corsConfiguration.setAllowedOrigins(Collections.singletonList("*")); + corsConfiguration.setAllowedMethods(Arrays.asList( + RequestMethod.GET.name(), + RequestMethod.POST.name())); + source.registerCorsConfiguration("/**", corsConfiguration); + return source; + } + } + @Test public void getWhenCorsFilterBeanThenRespondsWithCorsHeaders() throws Exception { this.spring.register(CorsFilterConfig.class).autowire(); @@ -228,4 +331,54 @@ CorsFilter corsFilter() { return new CorsFilter(source); } } + + @Test + public void getWhenConfigSourceInLambdaConfigAndCorsFilterBeanThenRespondsWithCorsHeaders() throws Exception { + this.spring.register(CorsFilterInLambdaConfig.class).autowire(); + + this.mvc.perform(get("/") + .header(HttpHeaders.ORIGIN, "https://example.com")) + .andExpect(header().exists("Access-Control-Allow-Origin")) + .andExpect(header().exists("X-Content-Type-Options")); + } + + @Test + public void optionsWhenConfigSourceInLambdaConfigAndCorsFilterBeanThenRespondsWithCorsHeaders() throws Exception { + this.spring.register(CorsFilterInLambdaConfig.class).autowire(); + + this.mvc.perform(options("/") + .header(org.springframework.http.HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.name()) + .header(HttpHeaders.ORIGIN, "https://example.com")) + .andExpect(status().isOk()) + .andExpect(header().exists("Access-Control-Allow-Origin")) + .andExpect(header().exists("X-Content-Type-Options")); + } + + @EnableWebSecurity + static class CorsFilterInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .cors(withDefaults()); + // @formatter:on + } + + @Bean + CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration corsConfiguration = new CorsConfiguration(); + corsConfiguration.setAllowedOrigins(Collections.singletonList("*")); + corsConfiguration.setAllowedMethods(Arrays.asList( + RequestMethod.GET.name(), + RequestMethod.POST.name())); + source.registerCorsConfiguration("/**", corsConfiguration); + return new CorsFilter(source); + } + } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerIgnoringRequestMatchersTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerIgnoringRequestMatchersTests.java index 39af9745cb3..56d99f0610b 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerIgnoringRequestMatchersTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerIgnoringRequestMatchersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -75,6 +75,36 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void requestWhenIgnoringRequestMatchersInLambdaThenAugmentedByConfiguredRequestMatcher() + throws Exception { + this.spring.register(IgnoringRequestInLambdaMatchers.class, BasicController.class).autowire(); + + this.mvc.perform(get("/path")) + .andExpect(status().isForbidden()); + + this.mvc.perform(post("/path")) + .andExpect(status().isOk()); + } + + @EnableWebSecurity + static class IgnoringRequestInLambdaMatchers extends WebSecurityConfigurerAdapter { + RequestMatcher requestMatcher = + request -> HttpMethod.POST.name().equals(request.getMethod()); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .csrf(csrf -> + csrf + .requireCsrfProtectionMatcher(new AntPathRequestMatcher("/path")) + .ignoringRequestMatchers(this.requestMatcher) + ); + // @formatter:on + } + } + @Test public void requestWhenIgnoringRequestMatcherThenUnionsWithConfiguredIgnoringAntMatchers() throws Exception { @@ -107,6 +137,40 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void requestWhenIgnoringRequestMatcherInLambdaThenUnionsWithConfiguredIgnoringAntMatchers() + throws Exception { + + this.spring.register(IgnoringPathsAndMatchersInLambdaConfig.class, BasicController.class).autowire(); + + this.mvc.perform(put("/csrf")) + .andExpect(status().isForbidden()); + + this.mvc.perform(post("/csrf")) + .andExpect(status().isOk()); + + this.mvc.perform(put("/no-csrf")) + .andExpect(status().isOk()); + } + + @EnableWebSecurity + static class IgnoringPathsAndMatchersInLambdaConfig extends WebSecurityConfigurerAdapter { + RequestMatcher requestMatcher = + request -> HttpMethod.POST.name().equals(request.getMethod()); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .csrf(csrf -> + csrf + .ignoringAntMatchers("/no-csrf") + .ignoringRequestMatchers(this.requestMatcher) + ); + // @formatter:on + } + } + @RestController public static class BasicController { @RequestMapping("/path") diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java index e66fd1a075d..22e88597ce1 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java @@ -55,6 +55,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; @@ -210,6 +211,26 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void postWhenCsrfDisabledInLambdaThenRespondsWithOk() throws Exception { + this.spring.register(DisableCsrfInLambdaConfig.class, BasicController.class).autowire(); + + this.mvc.perform(post("/")) + .andExpect(status().isOk()); + } + + @EnableWebSecurity + static class DisableCsrfInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .csrf(AbstractHttpConfigurer::disable); + // @formatter:on + } + } + // SEC-2498 @Test public void loginWhenCsrfDisabledThenRedirectsToPreviousPostRequest() throws Exception { @@ -386,6 +407,40 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void requireCsrfProtectionMatcherInLambdaWhenRequestDoesNotMatchThenRespondsWithOk() throws Exception { + RequireCsrfProtectionMatcherInLambdaConfig.MATCHER = mock(RequestMatcher.class); + this.spring.register(RequireCsrfProtectionMatcherInLambdaConfig.class, BasicController.class).autowire(); + when(RequireCsrfProtectionMatcherInLambdaConfig.MATCHER.matches(any())) + .thenReturn(false); + + this.mvc.perform(get("/")) + .andExpect(status().isOk()); + } + + @Test + public void requireCsrfProtectionMatcherInLambdaWhenRequestMatchesThenRespondsWithForbidden() throws Exception { + RequireCsrfProtectionMatcherInLambdaConfig.MATCHER = mock(RequestMatcher.class); + when(RequireCsrfProtectionMatcherInLambdaConfig.MATCHER.matches(any())).thenReturn(true); + this.spring.register(RequireCsrfProtectionMatcherInLambdaConfig.class, BasicController.class).autowire(); + + this.mvc.perform(get("/")) + .andExpect(status().isForbidden()); + } + + @EnableWebSecurity + static class RequireCsrfProtectionMatcherInLambdaConfig extends WebSecurityConfigurerAdapter { + static RequestMatcher MATCHER; + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .csrf(csrf -> csrf.requireCsrfProtectionMatcher(MATCHER)); + // @formatter:on + } + } + @Test public void getWhenCustomCsrfTokenRepositoryThenRepositoryIsUsed() throws Exception { CsrfTokenRepositoryConfig.REPO = mock(CsrfTokenRepository.class); @@ -454,6 +509,32 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception { } } + @Test + public void getWhenCustomCsrfTokenRepositoryInLambdaThenRepositoryIsUsed() throws Exception { + CsrfTokenRepositoryInLambdaConfig.REPO = mock(CsrfTokenRepository.class); + when(CsrfTokenRepositoryInLambdaConfig.REPO.loadToken(any())) + .thenReturn(new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "token")); + this.spring.register(CsrfTokenRepositoryInLambdaConfig.class, BasicController.class).autowire(); + + this.mvc.perform(get("/")) + .andExpect(status().isOk()); + verify(CsrfTokenRepositoryInLambdaConfig.REPO).loadToken(any(HttpServletRequest.class)); + } + + @EnableWebSecurity + static class CsrfTokenRepositoryInLambdaConfig extends WebSecurityConfigurerAdapter { + static CsrfTokenRepository REPO; + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .formLogin(withDefaults()) + .csrf(csrf -> csrf.csrfTokenRepository(REPO)); + // @formatter:on + } + } + @Test public void getWhenCustomAccessDeniedHandlerThenHandlerIsUsed() throws Exception { AccessDeniedHandlerConfig.DENIED_HANDLER = mock(AccessDeniedHandler.class); diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerAccessDeniedHandlerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerAccessDeniedHandlerTests.java index aad85a825be..ef1084cdbda 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerAccessDeniedHandlerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerAccessDeniedHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -86,6 +86,48 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + @WithMockUser(roles = "ANYTHING") + public void getWhenAccessDeniedOverriddenInLambdaThenCustomizesResponseByRequest() + throws Exception { + this.spring.register(RequestMatcherBasedAccessDeniedHandlerInLambdaConfig.class).autowire(); + + this.mvc.perform(get("/hello")) + .andExpect(status().isIAmATeapot()); + + this.mvc.perform(get("/goodbye")) + .andExpect(status().isForbidden()); + } + + @EnableWebSecurity + static class RequestMatcherBasedAccessDeniedHandlerInLambdaConfig extends WebSecurityConfigurerAdapter { + AccessDeniedHandler teapotDeniedHandler = + (request, response, exception) -> + response.setStatus(HttpStatus.I_AM_A_TEAPOT.value()); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().denyAll() + ) + .exceptionHandling(exceptionHandling -> + exceptionHandling + .defaultAccessDeniedHandlerFor( + this.teapotDeniedHandler, + new AntPathRequestMatcher("/hello/**") + ) + .defaultAccessDeniedHandlerFor( + new AccessDeniedHandlerImpl(), + AnyRequestMatcher.INSTANCE + ) + ); + // @formatter:on + } + } + @Test @WithMockUser(roles = "ANYTHING") public void getWhenAccessDeniedOverriddenByOnlyOneHandlerThenAllRequestsUseThatHandler() diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java index 42118060943..f7b9c1635e0 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java @@ -42,6 +42,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.logout; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; @@ -195,6 +196,82 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception { } } + @Test + public void loginWhenFormLoginDefaultsInLambdaThenHasDefaultUsernameAndPasswordParameterNames() throws Exception { + this.spring.register(FormLoginInLambdaConfig.class).autowire(); + + this.mockMvc.perform(formLogin().user("username", "user").password("password", "password")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/")); + } + + @Test + public void loginWhenFormLoginDefaultsInLambdaThenHasDefaultFailureUrl() throws Exception { + this.spring.register(FormLoginInLambdaConfig.class).autowire(); + + this.mockMvc.perform(formLogin().user("invalid")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?error")); + } + + @Test + public void loginWhenFormLoginDefaultsInLambdaThenHasDefaultSuccessUrl() throws Exception { + this.spring.register(FormLoginInLambdaConfig.class).autowire(); + + this.mockMvc.perform(formLogin()) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/")); + } + + @Test + public void getLoginPageWhenFormLoginDefaultsInLambdaThenNotSecured() throws Exception { + this.spring.register(FormLoginInLambdaConfig.class).autowire(); + + this.mockMvc.perform(get("/login")) + .andExpect(status().isOk()); + } + + @Test + public void loginWhenFormLoginDefaultsInLambdaThenSecured() throws Exception { + this.spring.register(FormLoginInLambdaConfig.class).autowire(); + + this.mockMvc.perform(post("/login")) + .andExpect(status().isForbidden()); + } + + @Test + public void requestProtectedWhenFormLoginDefaultsInLambdaThenRedirectsToLogin() throws Exception { + this.spring.register(FormLoginInLambdaConfig.class).autowire(); + + this.mockMvc.perform(get("/private")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("http://localhost/login")); + } + + @EnableWebSecurity + static class FormLoginInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().hasRole("USER") + ) + .formLogin(withDefaults()); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + // @formatter:on + } + } + @Test public void getLoginPageWhenFormLoginPermitAllThenPermittedAndNoRedirect() throws Exception { this.spring.register(FormLoginConfigPermitAll.class).autowire(); @@ -297,6 +374,34 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void getLoginPageWhenCustomLoginPageInLambdaThenPermittedAndNoRedirect() throws Exception { + this.spring.register(FormLoginDefaultsInLambdaConfig.class).autowire(); + + this.mockMvc.perform(get("/authenticate")) + .andExpect(redirectedUrl(null)); + } + + @EnableWebSecurity + static class FormLoginDefaultsInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().hasRole("USER") + ) + .formLogin(formLogin -> + formLogin + .loginPage("/authenticate") + .permitAll() + ) + .logout(LogoutConfigurer::permitAll); + // @formatter:on + } + } + @Test public void loginWhenCustomLoginProcessingUrlThenRedirectsToHome() throws Exception { this.spring.register(FormLoginLoginProcessingUrlConfig.class).autowire(); @@ -340,6 +445,51 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception { } } + @Test + public void loginWhenCustomLoginProcessingUrlInLambdaThenRedirectsToHome() throws Exception { + this.spring.register(FormLoginLoginProcessingUrlInLambdaConfig.class).autowire(); + + this.mockMvc.perform(formLogin("/loginCheck")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/")); + } + + @EnableWebSecurity + static class FormLoginLoginProcessingUrlInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .formLogin(formLogin -> + formLogin + .loginProcessingUrl("/loginCheck") + .loginPage("/login") + .defaultSuccessUrl("/", true) + .permitAll() + ) + .logout(logout -> + logout + .logoutSuccessUrl("/login") + .logoutUrl("/logout") + .deleteCookies("JSESSIONID") + ); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + // @formatter:on + } + } + @Test public void requestWhenCustomPortMapperThenPortMapperUsed() throws Exception { FormLoginUsesPortMapperConfig.PORT_MAPPER = mock(PortMapper.class); diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java index f8ed9adcf20..0ca822a6fdc 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java @@ -36,6 +36,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; @@ -87,6 +88,36 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void getWhenHeadersConfiguredInLambdaThenDefaultHeadersInResponse() throws Exception { + this.spring.register(HeadersInLambdaConfig.class).autowire(); + + MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) + .andExpect(header().string(HttpHeaders.X_CONTENT_TYPE_OPTIONS, "nosniff")) + .andExpect(header().string(HttpHeaders.X_FRAME_OPTIONS, XFrameOptionsMode.DENY.name())) + .andExpect(header().string(HttpHeaders.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains")) + .andExpect(header().string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")) + .andExpect(header().string(HttpHeaders.EXPIRES, "0")) + .andExpect(header().string(HttpHeaders.PRAGMA, "no-cache")) + .andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "1; mode=block")) + .andReturn(); + assertThat(mvcResult.getResponse().getHeaderNames()).containsExactlyInAnyOrder( + HttpHeaders.X_CONTENT_TYPE_OPTIONS, HttpHeaders.X_FRAME_OPTIONS, HttpHeaders.STRICT_TRANSPORT_SECURITY, + HttpHeaders.CACHE_CONTROL, HttpHeaders.EXPIRES, HttpHeaders.PRAGMA, HttpHeaders.X_XSS_PROTECTION); + } + + @EnableWebSecurity + static class HeadersInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .headers(withDefaults()); + // @formatter:on + } + } + @Test public void getWhenHeaderDefaultsDisabledAndContentTypeConfiguredThenOnlyContentTypeHeaderInResponse() throws Exception { @@ -112,6 +143,33 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void getWhenOnlyContentTypeConfiguredInLambdaThenOnlyContentTypeHeaderInResponse() + throws Exception { + this.spring.register(ContentTypeOptionsInLambdaConfig.class).autowire(); + + MvcResult mvcResult = this.mvc.perform(get("/")) + .andExpect(header().string(HttpHeaders.X_CONTENT_TYPE_OPTIONS, "nosniff")) + .andReturn(); + assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_CONTENT_TYPE_OPTIONS); + } + + @EnableWebSecurity + static class ContentTypeOptionsInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .headers(headers -> + headers + .defaultsDisabled() + .contentTypeOptions(withDefaults()) + ); + // @formatter:on + } + } + @Test public void getWhenHeaderDefaultsDisabledAndFrameOptionsConfiguredThenOnlyFrameOptionsHeaderInResponse() throws Exception { @@ -190,6 +248,36 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void getWhenOnlyCacheControlConfiguredInLambdaThenCacheControlAndExpiresAndPragmaHeadersInResponse() + throws Exception { + this.spring.register(CacheControlInLambdaConfig.class).autowire(); + + MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) + .andExpect(header().string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")) + .andExpect(header().string(HttpHeaders.EXPIRES, "0")) + .andExpect(header().string(HttpHeaders.PRAGMA, "no-cache")) + .andReturn(); + assertThat(mvcResult.getResponse().getHeaderNames()).containsExactlyInAnyOrder(HttpHeaders.CACHE_CONTROL, + HttpHeaders.EXPIRES, HttpHeaders.PRAGMA); + } + + @EnableWebSecurity + static class CacheControlInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .headers(headers -> + headers + .defaultsDisabled() + .cacheControl(withDefaults()) + ); + // @formatter:on + } + } + @Test public void getWhenHeaderDefaultsDisabledAndXssProtectionConfiguredThenOnlyXssProtectionHeaderInResponse() throws Exception { @@ -215,6 +303,33 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void getWhenOnlyXssProtectionConfiguredInLambdaThenOnlyXssProtectionHeaderInResponse() + throws Exception { + this.spring.register(XssProtectionInLambdaConfig.class).autowire(); + + MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) + .andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "1; mode=block")) + .andReturn(); + assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION); + } + + @EnableWebSecurity + static class XssProtectionInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .headers(headers -> + headers + .defaultsDisabled() + .xssProtection(withDefaults()) + ); + // @formatter:on + } + } + @Test public void getWhenFrameOptionsSameOriginConfiguredThenFrameOptionsHeaderHasValueSameOrigin() throws Exception { this.spring.register(HeadersCustomSameOriginConfig.class).autowire(); @@ -237,6 +352,31 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void getWhenFrameOptionsSameOriginConfiguredInLambdaThenFrameOptionsHeaderHasValueSameOrigin() + throws Exception { + this.spring.register(HeadersCustomSameOriginInLambdaConfig.class).autowire(); + + this.mvc.perform(get("/").secure(true)) + .andExpect(header().string(HttpHeaders.X_FRAME_OPTIONS, XFrameOptionsMode.SAMEORIGIN.name())) + .andReturn(); + } + + @EnableWebSecurity + static class HeadersCustomSameOriginInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .headers(headers -> + headers + .frameOptions(frameOptionsConfig -> frameOptionsConfig.sameOrigin()) + ); + // @formatter:on + } + } + @Test public void getWhenHeaderDefaultsDisabledAndPublicHpkpWithNoPinThenNoHeadersInResponse() throws Exception { this.spring.register(HpkpConfigNoPins.class).autowire(); @@ -465,6 +605,38 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void getWhenHpkpWithReportUriInLambdaThenPublicKeyPinsReportOnlyHeaderWithReportUriInResponse() + throws Exception { + this.spring.register(HpkpWithReportUriInLambdaConfig.class).autowire(); + + MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) + .andExpect(header().string(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY, + "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; report-uri=\"https://example.net/pkp-report\"")) + .andReturn(); + assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY); + } + + @EnableWebSecurity + static class HpkpWithReportUriInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .headers(headers -> + headers + .defaultsDisabled() + .httpPublicKeyPinning(hpkp -> + hpkp + .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=") + .reportUri("https://example.net/pkp-report") + ) + ); + // @formatter:on + } + } + @Test public void getWhenContentSecurityPolicyConfiguredThenContentSecurityPolicyHeaderInResponse() throws Exception { this.spring.register(ContentSecurityPolicyDefaultConfig.class).autowire(); @@ -515,6 +687,38 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void getWhenContentSecurityPolicyWithReportOnlyInLambdaThenContentSecurityPolicyReportOnlyHeaderInResponse() + throws Exception { + this.spring.register(ContentSecurityPolicyReportOnlyInLambdaConfig.class).autowire(); + + MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) + .andExpect(header().string(HttpHeaders.CONTENT_SECURITY_POLICY_REPORT_ONLY, + "default-src 'self'; script-src trustedscripts.example.com")) + .andReturn(); + assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.CONTENT_SECURITY_POLICY_REPORT_ONLY); + } + + @EnableWebSecurity + static class ContentSecurityPolicyReportOnlyInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .headers(headers -> + headers + .defaultsDisabled() + .contentSecurityPolicy(csp -> + csp + .policyDirectives("default-src 'self'; script-src trustedscripts.example.com") + .reportOnly() + ) + ); + // @formatter:on + } + } + @Test public void configureWhenContentSecurityPolicyEmptyThenException() { assertThatThrownBy(() -> this.spring.register(ContentSecurityPolicyInvalidConfig.class).autowire()) @@ -536,6 +740,58 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void configureWhenContentSecurityPolicyEmptyInLambdaThenException() { + assertThatThrownBy(() -> this.spring.register(ContentSecurityPolicyInvalidInLambdaConfig.class).autowire()) + .isInstanceOf(BeanCreationException.class) + .hasRootCauseInstanceOf(IllegalArgumentException.class); + } + + @EnableWebSecurity + static class ContentSecurityPolicyInvalidInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .headers(headers -> + headers + .defaultsDisabled() + .contentSecurityPolicy(csp -> + csp.policyDirectives("") + ) + ); + // @formatter:on + } + } + + @Test + public void configureWhenContentSecurityPolicyNoPolicyDirectivesInLambdaThenDefaultHeaderValue() throws Exception { + this.spring.register(ContentSecurityPolicyNoDirectivesInLambdaConfig.class).autowire(); + + MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) + .andExpect(header().string(HttpHeaders.CONTENT_SECURITY_POLICY, + "default-src 'self'")) + .andReturn(); + assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.CONTENT_SECURITY_POLICY); + } + + @EnableWebSecurity + static class ContentSecurityPolicyNoDirectivesInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .headers(headers -> + headers + .defaultsDisabled() + .contentSecurityPolicy(withDefaults()) + ); + // @formatter:on + } + } + @Test public void getWhenReferrerPolicyConfiguredThenReferrerPolicyHeaderInResponse() throws Exception { this.spring.register(ReferrerPolicyDefaultConfig.class).autowire(); @@ -560,6 +816,32 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void getWhenReferrerPolicyInLambdaThenReferrerPolicyHeaderInResponse() throws Exception { + this.spring.register(ReferrerPolicyDefaultInLambdaConfig.class).autowire(); + + MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) + .andExpect(header().string("Referrer-Policy", ReferrerPolicy.NO_REFERRER.getPolicy())) + .andReturn(); + assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Referrer-Policy"); + } + + @EnableWebSecurity + static class ReferrerPolicyDefaultInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .headers(headers -> + headers + .defaultsDisabled() + .referrerPolicy() + ); + // @formatter:on + } + } + @Test public void getWhenReferrerPolicyConfiguredWithCustomValueThenReferrerPolicyHeaderWithCustomValueInResponse() throws Exception { @@ -585,6 +867,34 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void getWhenReferrerPolicyConfiguredWithCustomValueInLambdaThenCustomValueInResponse() throws Exception { + this.spring.register(ReferrerPolicyCustomInLambdaConfig.class).autowire(); + + MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) + .andExpect(header().string("Referrer-Policy", ReferrerPolicy.SAME_ORIGIN.getPolicy())) + .andReturn(); + assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Referrer-Policy"); + } + + @EnableWebSecurity + static class ReferrerPolicyCustomInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .headers(headers -> + headers + .defaultsDisabled() + .referrerPolicy(referrerPolicy -> + referrerPolicy.policy(ReferrerPolicy.SAME_ORIGIN) + ) + ); + // @formatter:on + } + } + @Test public void getWhenFeaturePolicyConfiguredThenFeaturePolicyHeaderInResponse() throws Exception { this.spring.register(FeaturePolicyConfig.class).autowire(); @@ -656,4 +966,32 @@ protected void configure(HttpSecurity http) throws Exception { // @formatter:on } } + + @Test + public void getWhenHstsConfiguredWithPreloadInLambdaThenStrictTransportSecurityHeaderWithPreloadInResponse() + throws Exception { + this.spring.register(HstsWithPreloadInLambdaConfig.class).autowire(); + + MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) + .andExpect(header().string(HttpHeaders.STRICT_TRANSPORT_SECURITY, + "max-age=31536000 ; includeSubDomains ; preload")) + .andReturn(); + assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.STRICT_TRANSPORT_SECURITY); + } + + @EnableWebSecurity + static class HstsWithPreloadInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .headers(headers -> + headers + .defaultsDisabled() + .httpStrictTransportSecurity(hstsConfig -> hstsConfig.preload(true)) + ); + // @formatter:on + } + } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurerTests.java index a6445aa35b8..43d956b3e51 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurerTests.java @@ -107,9 +107,10 @@ static class DefaultsLambdaEntryPointConfig extends WebSecurityConfigurerAdapter protected void configure(HttpSecurity http) throws Exception { // @formatter:off http - .authorizeRequests() - .anyRequest().authenticated() - .and() + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) .httpBasic(withDefaults()); // @formatter:on } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java index c05f8064a63..ef4d4a2e425 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2019 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. @@ -38,6 +38,7 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.security.config.Customizer.withDefaults; /** * @author Rob Winch @@ -195,6 +196,62 @@ public String path() { } } + @Test + public void requestMatchersWhenMvcMatcherInLambdaThenPathIsSecured() throws Exception { + loadConfig(RequestMatchersMvcMatcherInLambdaConfig.class); + + this.request.setServletPath("/path"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()) + .isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + + setup(); + + this.request.setServletPath("/path.html"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()) + .isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + + setup(); + + this.request.setServletPath("/path/"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()) + .isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + } + + @EnableWebSecurity + @Configuration + @EnableWebMvc + static class RequestMatchersMvcMatcherInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .requestMatchers(requestMatchers -> + requestMatchers + .mvcMatchers("/path") + ) + .httpBasic(withDefaults()) + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().denyAll() + ); + // @formatter:on + } + + @RestController + static class PathController { + @RequestMapping("/path") + public String path() { + return "path"; + } + } + } + @Test public void requestMatchersMvcMatcherServletPath() throws Exception { loadConfig(RequestMatchersMvcMatcherServeltPathConfig.class); @@ -260,6 +317,66 @@ public String path() { } } + @Test + public void requestMatcherWhensMvcMatcherServletPathInLambdaThenPathIsSecured() throws Exception { + loadConfig(RequestMatchersMvcMatcherServletPathInLambdaConfig.class); + + this.request.setServletPath("/spring"); + this.request.setRequestURI("/spring/path"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()) + .isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + + setup(); + + this.request.setServletPath(""); + this.request.setRequestURI("/path"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + + setup(); + + this.request.setServletPath("/other"); + this.request.setRequestURI("/other/path"); + + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + } + + @EnableWebSecurity + @Configuration + @EnableWebMvc + static class RequestMatchersMvcMatcherServletPathInLambdaConfig + extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .requestMatchers(requestMatchers -> + requestMatchers + .mvcMatchers("/path").servletPath("/spring") + .mvcMatchers("/never-match") + ) + .httpBasic(withDefaults()) + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().denyAll() + ); + // @formatter:on + } + + @RestController + static class PathController { + @RequestMapping("/path") + public String path() { + return "path"; + } + } + } + public void loadConfig(Class... configs) { this.context = new AnnotationConfigWebApplicationContext(); this.context.register(configs); diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/JeeConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/JeeConfigurerTests.java index fac4baf354b..57de13f0877 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/JeeConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/JeeConfigurerTests.java @@ -25,6 +25,9 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.test.SpringTestRule; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; +import org.springframework.security.core.userdetails.User; import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource; import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter; import org.springframework.test.web.servlet.MockMvc; @@ -125,4 +128,115 @@ protected void configure(HttpSecurity http) throws Exception { // @formatter:on } } + + @Test + public void requestWhenJeeMappableRolesInLambdaThenAuthenticatedWithMappableRoles() throws Exception { + this.spring.register(JeeMappableRolesConfig.class).autowire(); + Principal user = mock(Principal.class); + when(user.getName()).thenReturn("user"); + + this.mvc.perform(get("/") + .principal(user) + .with(request -> { + request.addUserRole("ROLE_ADMIN"); + request.addUserRole("ROLE_USER"); + return request; + })) + .andExpect(authenticated().withRoles("USER")); + } + + @EnableWebSecurity + public static class JeeMappableRolesConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().hasRole("USER") + ) + .jee(jee -> + jee + .mappableRoles("USER") + ); + // @formatter:on + } + } + + @Test + public void requestWhenJeeMappableAuthoritiesInLambdaThenAuthenticatedWithMappableAuthorities() throws Exception { + this.spring.register(JeeMappableAuthoritiesConfig.class).autowire(); + Principal user = mock(Principal.class); + when(user.getName()).thenReturn("user"); + + this.mvc.perform(get("/") + .principal(user) + .with(request -> { + request.addUserRole("ROLE_ADMIN"); + request.addUserRole("ROLE_USER"); + return request; + })) + .andExpect(authenticated().withAuthorities(AuthorityUtils.createAuthorityList("ROLE_USER"))); + } + + @EnableWebSecurity + public static class JeeMappableAuthoritiesConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().hasRole("USER") + ) + .jee(jee -> + jee + .mappableAuthorities("ROLE_USER") + ); + // @formatter:on + } + } + + @Test + public void requestWhenCustomAuthenticatedUserDetailsServiceInLambdaThenCustomAuthenticatedUserDetailsServiceUsed() + throws Exception { + this.spring.register(JeeCustomAuthenticatedUserDetailsServiceConfig.class).autowire(); + Principal user = mock(Principal.class); + User userDetails = new User("user", "N/A", true, true, true, true, + AuthorityUtils.createAuthorityList("ROLE_USER")); + when(user.getName()).thenReturn("user"); + when(JeeCustomAuthenticatedUserDetailsServiceConfig.authenticationUserDetailsService.loadUserDetails(any())) + .thenReturn(userDetails); + + this.mvc.perform(get("/") + .principal(user) + .with(request -> { + request.addUserRole("ROLE_ADMIN"); + request.addUserRole("ROLE_USER"); + return request; + })) + .andExpect(authenticated().withRoles("USER")); + } + + @EnableWebSecurity + public static class JeeCustomAuthenticatedUserDetailsServiceConfig extends WebSecurityConfigurerAdapter { + static AuthenticationUserDetailsService authenticationUserDetailsService = + mock(AuthenticationUserDetailsService.class); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().hasRole("USER") + ) + .jee(jee -> + jee + .authenticatedUserDetailsService(authenticationUserDetailsService) + ); + // @formatter:on + } + } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.java index 3ef62bf4a8f..86bf87a6bed 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.java @@ -37,10 +37,15 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -77,6 +82,26 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void configureWhenDefaultLogoutSuccessHandlerForHasNullLogoutHandlerInLambdaThenException() { + assertThatThrownBy(() -> this.spring.register(NullLogoutSuccessHandlerInLambdaConfig.class).autowire()) + .isInstanceOf(BeanCreationException.class) + .hasRootCauseInstanceOf(IllegalArgumentException.class); + } + + @EnableWebSecurity + static class NullLogoutSuccessHandlerInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .logout(logout -> + logout.defaultLogoutSuccessHandlerFor(null, mock(RequestMatcher.class)) + ); + // @formatter:on + } + } + @Test public void configureWhenDefaultLogoutSuccessHandlerForHasNullMatcherThenException() { assertThatThrownBy(() -> this.spring.register(NullMatcherConfig.class).autowire()) @@ -96,6 +121,26 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void configureWhenDefaultLogoutSuccessHandlerForHasNullMatcherInLambdaThenException() { + assertThatThrownBy(() -> this.spring.register(NullMatcherInLambdaConfig.class).autowire()) + .isInstanceOf(BeanCreationException.class) + .hasRootCauseInstanceOf(IllegalArgumentException.class); + } + + @EnableWebSecurity + static class NullMatcherInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .logout(logout -> + logout.defaultLogoutSuccessHandlerFor(mock(LogoutSuccessHandler.class), null) + ); + // @formatter:on + } + } + @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnLogoutFilter() { this.spring.register(ObjectPostProcessorConfig.class).autowire(); @@ -263,6 +308,29 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void logoutWhenCustomLogoutUrlInLambdaThenRedirectsToLogin() throws Exception { + this.spring.register(CsrfDisabledAndCustomLogoutInLambdaConfig.class).autowire(); + + this.mvc.perform(get("/custom/logout")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?logout")); + } + + @EnableWebSecurity + static class CsrfDisabledAndCustomLogoutInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .csrf() + .disable() + .logout(logout -> logout.logoutUrl("/custom/logout")); + // @formatter:on + } + } + // SEC-3170 @Test public void configureWhenLogoutHandlerNullThenException() { @@ -283,6 +351,24 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void configureWhenLogoutHandlerNullInLambdaThenException() { + assertThatThrownBy(() -> this.spring.register(NullLogoutHandlerInLambdaConfig.class).autowire()) + .isInstanceOf(BeanCreationException.class) + .hasRootCauseInstanceOf(IllegalArgumentException.class); + } + + @EnableWebSecurity + static class NullLogoutHandlerInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .logout(logout -> logout.addLogoutHandler(null)); + // @formatter:on + } + } + // SEC-3170 @Test public void rememberMeWhenRememberMeServicesNotLogoutHandlerThenRedirectsToLogin() throws Exception { diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpBasicTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpBasicTests.java index 83d05080573..9a66e356fd7 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpBasicTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpBasicTests.java @@ -125,9 +125,10 @@ static class HttpBasicLambdaConfig extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) throws Exception { // @formatter:off http - .authorizeRequests() - .anyRequest().hasRole("USER") - .and() + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().hasRole("USER") + ) .httpBasic(withDefaults()); // @formatter:on } @@ -174,9 +175,10 @@ static class CustomHttpBasicLambdaConfig extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) throws Exception { // @formatter:off http - .authorizeRequests() - .anyRequest().hasRole("USER") - .and() + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().hasRole("USER") + ) .httpBasic(httpBasicConfig -> httpBasicConfig.realmName("Custom Realm")); // @formatter:on } @@ -310,9 +312,10 @@ static class EntryPointRefHttpBasicLambdaConfig extends WebSecurityConfigurerAda protected void configure(HttpSecurity http) throws Exception { // @formatter:off http - .authorizeRequests() - .anyRequest().hasRole("USER") - .and() + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().hasRole("USER") + ) .httpBasic(httpBasicConfig -> httpBasicConfig.authenticationEntryPoint(this.authenticationEntryPoint)); // @formatter:on diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpLogoutTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpLogoutTests.java index cd0bf72decd..a1ad03748d5 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpLogoutTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpLogoutTests.java @@ -41,9 +41,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests to verify that all the functionality of attributes is present @@ -83,6 +85,23 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + @WithMockUser + public void logoutWhenDisabledInLambdaThenRespondsWithNotFound() throws Exception { + this.spring.register(HttpLogoutDisabledInLambdaConfig.class).autowire(); + + this.mvc.perform(post("/logout").with(csrf()).with(user("user"))) + .andExpect(status().isNotFound()); + } + + @EnableWebSecurity + static class HttpLogoutDisabledInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http.logout(AbstractHttpConfigurer::disable); + } + } + /** * http/logout custom */ @@ -112,6 +131,35 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + @WithMockUser + public void logoutWhenUsingVariousCustomizationsInLambdaThenMatchesNamespace() throws Exception { + this.spring.register(CustomHttpLogoutInLambdaConfig.class).autowire(); + + this.mvc.perform(post("/custom-logout").with(csrf())) + .andExpect(authenticated(false)) + .andExpect(redirectedUrl("/logout-success")) + .andExpect(result -> assertThat(result.getResponse().getCookies()).hasSize(1)) + .andExpect(cookie().maxAge("remove", 0)) + .andExpect(session(Objects::nonNull)); + } + + @EnableWebSecurity + static class CustomHttpLogoutInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .logout(logout -> + logout.deleteCookies("remove") + .invalidateHttpSession(false) + .logoutUrl("/custom-logout") + .logoutSuccessUrl("/logout-success") + ); + // @formatter:on + } + } + /** * http/logout@success-handler-ref */ @@ -141,6 +189,32 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + @WithMockUser + public void logoutWhenUsingSuccessHandlerRefInLambdaThenMatchesNamespace() throws Exception { + this.spring.register(SuccessHandlerRefHttpLogoutInLambdaConfig.class).autowire(); + + this.mvc.perform(post("/logout").with(csrf())) + .andExpect(authenticated(false)) + .andExpect(redirectedUrl("/SuccessHandlerRefHttpLogoutConfig")) + .andExpect(noCookies()) + .andExpect(session(Objects::isNull)); + } + + @EnableWebSecurity + static class SuccessHandlerRefHttpLogoutInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + SimpleUrlLogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler(); + logoutSuccessHandler.setDefaultTargetUrl("/SuccessHandlerRefHttpLogoutConfig"); + + // @formatter:off + http + .logout(logout -> logout.logoutSuccessHandler(logoutSuccessHandler)); + // @formatter:on + } + } + ResultMatcher authenticated(boolean authenticated) { return result -> assertThat( Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpServerAccessDeniedHandlerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpServerAccessDeniedHandlerTests.java index eb4763bd551..0aea788b3d8 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpServerAccessDeniedHandlerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpServerAccessDeniedHandlerTests.java @@ -83,6 +83,32 @@ private static Authentication user() { return new UsernamePasswordAuthenticationToken("user", null, AuthorityUtils.NO_AUTHORITIES); } + @Test + public void requestWhenCustomAccessDeniedPageInLambdaThenForwardedToCustomPage() throws Exception { + this.spring.register(AccessDeniedPageInLambdaConfig.class).autowire(); + + this.mvc.perform(get("/") + .with(authentication(user()))) + .andExpect(status().isForbidden()) + .andExpect(forwardedUrl("/AccessDeniedPageConfig")); + } + + @EnableWebSecurity + static class AccessDeniedPageInLambdaConfig extends WebSecurityConfigurerAdapter { + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().denyAll() + ) + .exceptionHandling(exceptionHandling -> + exceptionHandling.accessDeniedPage("/AccessDeniedPageConfig") + ); + // @formatter:on + } + } + @Test public void requestWhenCustomAccessDeniedHandlerThenBehaviorMatchesNamespace() throws Exception { this.spring.register(AccessDeniedHandlerRefConfig.class).autowire(); @@ -109,6 +135,40 @@ AccessDeniedHandler accessDeniedHandler() { } } + @Test + public void requestWhenCustomAccessDeniedHandlerInLambdaThenBehaviorMatchesNamespace() throws Exception { + this.spring.register(AccessDeniedHandlerRefInLambdaConfig.class).autowire(); + + this.mvc.perform(get("/") + .with(authentication(user()))); + + verify(AccessDeniedHandlerRefInLambdaConfig.accessDeniedHandler) + .handle(any(HttpServletRequest.class), any(HttpServletResponse.class), any(AccessDeniedException.class)); + } + + @EnableWebSecurity + static class AccessDeniedHandlerRefInLambdaConfig extends WebSecurityConfigurerAdapter { + static AccessDeniedHandler accessDeniedHandler = mock(AccessDeniedHandler.class); + + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().denyAll() + ) + .exceptionHandling(exceptionHandling -> + exceptionHandling.accessDeniedHandler(accessDeniedHandler()) + ); + // @formatter:on + } + + @Bean + AccessDeniedHandler accessDeniedHandler() { + return accessDeniedHandler; + } + } + private T verifyBean(Class beanClass) { return verify(this.spring.getContext().getBean(beanClass)); } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/PortMapperConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/PortMapperConfigurerTests.java index bc451f4c95f..bd68d2ecec9 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/PortMapperConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/PortMapperConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -22,8 +22,11 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.test.SpringTestRule; +import org.springframework.security.web.PortMapperImpl; import org.springframework.test.web.servlet.MockMvc; +import java.util.Collections; + import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; @@ -61,4 +64,58 @@ protected void configure(HttpSecurity http) throws Exception { .portMapper(); } } + + @Test + public void requestWhenPortMapperHttpMapsToInLambdaThenRedirectsToHttpsPort() throws Exception { + this.spring.register(HttpMapsToInLambdaConfig.class).autowire(); + + this.mockMvc.perform(get("http://localhost:543")) + .andExpect(redirectedUrl("https://localhost:123")); + } + + @EnableWebSecurity + static class HttpMapsToInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .requiresChannel(requiresChannel -> + requiresChannel + .anyRequest().requiresSecure() + ) + .portMapper(portMapper -> + portMapper + .http(543).mapsTo(123) + ); + // @formatter:on + } + } + + @Test + public void requestWhenCustomPortMapperInLambdaThenRedirectsToHttpsPort() throws Exception { + this.spring.register(CustomPortMapperInLambdaConfig.class).autowire(); + + this.mockMvc.perform(get("http://localhost:543")) + .andExpect(redirectedUrl("https://localhost:123")); + } + + @EnableWebSecurity + static class CustomPortMapperInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + PortMapperImpl customPortMapper = new PortMapperImpl(); + customPortMapper.setPortMappings(Collections.singletonMap("543", "123")); + // @formatter:off + http + .requiresChannel(requiresChannel -> + requiresChannel + .anyRequest().requiresSecure() + ) + .portMapper(portMapper -> + portMapper + .portMapper(customPortMapper) + ); + // @formatter:on + } + } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java index 4c217ee2d94..1e3ee6018db 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java @@ -51,6 +51,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; @@ -299,6 +300,45 @@ public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception } } + + @Test + public void loginWhenRememberMeConfiguredInLambdaThenRespondsWithRememberMeCookie() throws Exception { + this.spring.register(RememberMeInLambdaConfig.class).autowire(); + + this.mvc.perform(post("/login") + .with(csrf()) + .param("username", "user") + .param("password", "password") + .param("remember-me", "true")) + .andExpect(cookie().exists("remember-me")); + } + + @EnableWebSecurity + static class RememberMeInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().hasRole("USER") + ) + .formLogin(withDefaults()) + .rememberMe(withDefaults()); + // @formatter:on + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + // @formatter:on + } + } + @Test public void loginWhenRememberMeTrueAndCookieDomainThenRememberMeCookieHasDomain() throws Exception { this.spring.register(RememberMeCookieDomainConfig.class).autowire(); @@ -337,6 +377,46 @@ public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception } } + @Test + public void loginWhenRememberMeTrueAndCookieDomainInLambdaThenRememberMeCookieHasDomain() throws Exception { + this.spring.register(RememberMeCookieDomainInLambdaConfig.class).autowire(); + + this.mvc.perform(post("/login") + .with(csrf()) + .param("username", "user") + .param("password", "password") + .param("remember-me", "true")) + .andExpect(cookie().exists("remember-me")) + .andExpect(cookie().domain("remember-me", "spring.io")); + } + + @EnableWebSecurity + static class RememberMeCookieDomainInLambdaConfig extends WebSecurityConfigurerAdapter { + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().hasRole("USER") + ) + .formLogin(withDefaults()) + .rememberMe(rememberMe -> + rememberMe + .rememberMeCookieDomain("spring.io") + ); + // @formatter:on + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + // @formatter:on + } + } + @Test public void configureWhenRememberMeCookieNameAndRememberMeServicesThenException() { assertThatThrownBy(() -> this.spring.register(RememberMeCookieNameAndRememberMeServicesConfig.class).autowire()) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java index 683a4951c2c..7b161231657 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -33,6 +33,7 @@ import org.springframework.security.config.test.SpringTestRule; import org.springframework.security.core.userdetails.User; import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.savedrequest.NullRequestCache; import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.savedrequest.RequestCacheAwareFilter; import org.springframework.test.web.servlet.MockMvc; @@ -42,6 +43,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -271,6 +273,93 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void getWhenRequestCacheIsDisabledInLambdaThenExceptionTranslationFilterDoesNotStoreRequest() throws Exception { + this.spring.register(RequestCacheDisabledInLambdaConfig.class, DefaultSecurityConfig.class).autowire(); + + MockHttpSession session = (MockHttpSession) + this.mvc.perform(get("/bob")) + .andReturn().getRequest().getSession(); + + this.mvc.perform(formLogin(session)) + .andExpect(redirectedUrl("/")); + } + + @EnableWebSecurity + static class RequestCacheDisabledInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .formLogin(withDefaults()) + .requestCache(RequestCacheConfigurer::disable); + // @formatter:on + } + } + + @Test + public void getWhenRequestCacheInLambdaThenRedirectedToCachedPage() throws Exception { + this.spring.register(RequestCacheInLambdaConfig.class, DefaultSecurityConfig.class).autowire(); + + MockHttpSession session = (MockHttpSession) + this.mvc.perform(get("/bob")) + .andReturn().getRequest().getSession(); + + this.mvc.perform(formLogin(session)) + .andExpect(redirectedUrl("http://localhost/bob")); + } + + @EnableWebSecurity + static class RequestCacheInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .formLogin(withDefaults()) + .requestCache(withDefaults()); + // @formatter:on + } + } + + @Test + public void getWhenCustomRequestCacheInLambdaThenCustomRequestCacheUsed() throws Exception { + this.spring.register(CustomRequestCacheInLambdaConfig.class, DefaultSecurityConfig.class).autowire(); + + MockHttpSession session = (MockHttpSession) + this.mvc.perform(get("/bob")) + .andReturn().getRequest().getSession(); + + this.mvc.perform(formLogin(session)) + .andExpect(redirectedUrl("/")); + } + + @EnableWebSecurity + static class CustomRequestCacheInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .formLogin(withDefaults()) + .requestCache(requestCache -> + requestCache + .requestCache(new NullRequestCache()) + ); + // @formatter:on + } + } + @EnableWebSecurity static class DefaultSecurityConfig { diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherConfigurerTests.java index c47b8f970fe..4bc131761bf 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherConfigurerTests.java @@ -71,4 +71,37 @@ protected void configure(HttpSecurity http) throws Exception { // @formatter:on } } + + @Test + public void authorizeRequestsWhenInvokedMultipleTimesInLambdaThenChainsPaths() throws Exception { + this.spring.register(AuthorizeRequestInLambdaConfig.class).autowire(); + + this.mvc.perform(get("/oauth/abc")) + .andExpect(status().isForbidden()); + this.mvc.perform(get("/api/abc")) + .andExpect(status().isForbidden()); + } + + @EnableWebSecurity + static class AuthorizeRequestInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .requestMatchers(requestMatchers -> + requestMatchers + .antMatchers("/api/**") + ) + .requestMatchers(requestMatchers -> + requestMatchers + .antMatchers("/oauth/**") + ) + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().denyAll() + ); + // @formatter:on + } + } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SecurityContextConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SecurityContextConfigurerTests.java index b9b09393447..5d0419d9595 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SecurityContextConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SecurityContextConfigurerTests.java @@ -28,14 +28,22 @@ import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.test.SpringTestRule; import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.web.context.HttpRequestResponseHolder; +import org.springframework.security.web.context.NullSecurityContextRepository; import org.springframework.security.web.context.SecurityContextPersistenceFilter; import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import javax.servlet.http.HttpSession; + +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +import static org.springframework.security.config.Customizer.withDefaults; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; /** @@ -151,4 +159,97 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception { // @formatter:on } } + + @Test + public void requestWhenSecurityContextWithDefaultsInLambdaThenSessionIsCreated() throws Exception { + this.spring.register(SecurityContextWithDefaultsInLambdaConfig.class).autowire(); + + MvcResult mvcResult = this.mvc.perform(formLogin()).andReturn(); + HttpSession session = mvcResult.getRequest().getSession(false); + assertThat(session).isNotNull(); + } + + @EnableWebSecurity + static class SecurityContextWithDefaultsInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .formLogin(withDefaults()) + .securityContext(withDefaults()); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + // @formatter:on + } + } + + @Test + public void requestWhenSecurityContextDisabledInLambdaThenContextNotSavedInSession() throws Exception { + this.spring.register(SecurityContextDisabledInLambdaConfig.class).autowire(); + + MvcResult mvcResult = this.mvc.perform(formLogin()).andReturn(); + HttpSession session = mvcResult.getRequest().getSession(false); + assertThat(session).isNull(); + } + + @EnableWebSecurity + static class SecurityContextDisabledInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .formLogin(withDefaults()) + .securityContext(AbstractHttpConfigurer::disable); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + // @formatter:on + } + } + + @Test + public void requestWhenNullSecurityContextRepositoryInLambdaThenContextNotSavedInSession() throws Exception { + this.spring.register(NullSecurityContextRepositoryInLambdaConfig.class).autowire(); + + MvcResult mvcResult = this.mvc.perform(formLogin()).andReturn(); + HttpSession session = mvcResult.getRequest().getSession(false); + assertThat(session).isNull(); + } + + @EnableWebSecurity + static class NullSecurityContextRepositoryInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .formLogin(withDefaults()) + .securityContext(securityContext -> + securityContext + .securityContextRepository(new NullSecurityContextRepository()) + ); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + // @formatter:on + } + } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java index dcf96fa2d85..b390d004933 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java @@ -47,6 +47,7 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; @@ -230,6 +231,53 @@ protected void configure(HttpSecurity http) throws Exception { } } + @Test + public void requestWhenServletApiWithDefaultsInLambdaThenUsesDefaultRolePrefix() throws Exception { + this.spring.register(ServletApiWithDefaultsInLambdaConfig.class, AdminController.class).autowire(); + + this.mvc.perform(get("/admin") + .with(user("user").authorities(AuthorityUtils.createAuthorityList("ROLE_ADMIN")))) + .andExpect(status().isOk()); + } + + @EnableWebSecurity + static class ServletApiWithDefaultsInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .servletApi(withDefaults()); + // @formatter:on + } + } + + @Test + public void requestWhenRolePrefixInLambdaThenUsesCustomRolePrefix() throws Exception { + this.spring.register(RolePrefixInLambdaConfig.class, AdminController.class).autowire(); + + this.mvc.perform(get("/admin") + .with(user("user").authorities(AuthorityUtils.createAuthorityList("PERMISSION_ADMIN")))) + .andExpect(status().isOk()); + + this.mvc.perform(get("/admin") + .with(user("user").authorities(AuthorityUtils.createAuthorityList("ROLE_ADMIN")))) + .andExpect(status().isForbidden()); + } + + @EnableWebSecurity + static class RolePrefixInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .servletApi(servletApi -> + servletApi + .rolePrefix("PERMISSION_") + ); + // @formatter:on + } + } + @RestController static class AdminController { @GetMapping("/admin") diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java index 4278811084c..5eaa4d78a72 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java @@ -54,6 +54,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -201,6 +202,51 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception { } } + @Test + public void authenticateWhenNewSessionFixationProtectionInLambdaThenCreatesNewSession() throws Exception { + this.spring.register(SFPNewSessionInLambdaConfig.class).autowire(); + + MockHttpSession givenSession = new MockHttpSession(); + String givenSessionId = givenSession.getId(); + givenSession.setAttribute("name", "value"); + + MockHttpSession resultingSession = (MockHttpSession) + this.mvc.perform(get("/auth") + .session(givenSession) + .with(httpBasic("user", "password"))) + .andExpect(status().isNotFound()) + .andReturn().getRequest().getSession(false); + + assertThat(givenSessionId).isNotEqualTo(resultingSession.getId()); + assertThat(resultingSession.getAttribute("name")).isNull(); + } + + @EnableWebSecurity + static class SFPNewSessionInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .sessionManagement(sessionManagement -> + sessionManagement + .sessionFixation(sessionFixation -> + sessionFixation.newSession() + ) + ) + .httpBasic(withDefaults()); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + // @formatter:on + } + } + @Test public void loginWhenUserLoggedInAndMaxSessionsIsOneThenLoginPrevented() throws Exception { this.spring.register(ConcurrencyControlConfig.class).autowire(); @@ -262,6 +308,76 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception { } } + @Test + public void loginWhenUserLoggedInAndMaxSessionsOneInLambdaThenLoginPrevented() throws Exception { + this.spring.register(ConcurrencyControlInLambdaConfig.class).autowire(); + + this.mvc.perform(post("/login") + .with(csrf()) + .param("username", "user") + .param("password", "password")); + + this.mvc.perform(post("/login") + .with(csrf()) + .param("username", "user") + .param("password", "password")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?error")); + } + + @EnableWebSecurity + static class ConcurrencyControlInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + public void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .formLogin(withDefaults()) + .sessionManagement(sessionManagement -> + sessionManagement + .sessionConcurrency(sessionConcurrency -> + sessionConcurrency + .maximumSessions(1) + .maxSessionsPreventsLogin(true) + ) + ); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + // @formatter:on + } + } + + @Test + public void requestWhenSessionCreationPolicyStateLessInLambdaThenNoSessionCreated() throws Exception { + this.spring.register(SessionCreationPolicyStateLessInLambdaConfig.class).autowire(); + + MvcResult mvcResult = this.mvc.perform(get("/")) + .andReturn(); + HttpSession session = mvcResult.getRequest().getSession(false); + + assertThat(session).isNull(); + } + + @EnableWebSecurity + static class SessionCreationPolicyStateLessInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .sessionManagement(sessionManagement -> + sessionManagement + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + ); + // @formatter:on + } + } + @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnSessionManagementFilter() { ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java index 49ca4e431b5..f7c24c00135 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java @@ -38,6 +38,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -122,6 +123,69 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception { } } + @Test + public void x509WhenConfiguredInLambdaThenUsesDefaults() throws Exception { + this.spring.register(DefaultsInLambdaConfig.class).autowire(); + X509Certificate certificate = loadCert("rod.cer"); + + this.mvc.perform(get("/") + .with(x509(certificate))) + .andExpect(authenticated().withUsername("rod")); + } + + @EnableWebSecurity + static class DefaultsInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .x509(withDefaults()); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser("rod").password("password").roles("USER", "ADMIN"); + // @formatter:on + } + } + + @Test + public void x509WhenSubjectPrincipalRegexInLambdaThenUsesRegexToExtractPrincipal() throws Exception { + this.spring.register(SubjectPrincipalRegexInLambdaConfig.class).autowire(); + X509Certificate certificate = loadCert("rodatexampledotcom.cer"); + + this.mvc.perform(get("/") + .with(x509(certificate))) + .andExpect(authenticated().withUsername("rod")); + } + + @EnableWebSecurity + static class SubjectPrincipalRegexInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .x509(x509 -> + x509 + .subjectPrincipalRegex("CN=(.*?)@example.com(?:,|$)") + ); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser("rod").password("password").roles("USER", "ADMIN"); + // @formatter:on + } + } + private T loadCert(String location) { try (InputStream is = new ClassPathResource(location).getInputStream()) { CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java index 269641783f4..5a231dc28ab 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java @@ -65,6 +65,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -141,6 +142,19 @@ public void configureWhenAuthorizationCodeRequestThenRedirectForAuthorization() "redirect_uri=http://localhost/client-1"); } + @Test + public void configureWhenOauth2ClientInLambdaThenRedirectForAuthorization() throws Exception { + this.spring.register(OAuth2ClientInLambdaConfig.class).autowire(); + + MvcResult mvcResult = this.mockMvc.perform(get("/oauth2/authorization/registration-1")) + .andExpect(status().is3xxRedirection()) + .andReturn(); + assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?" + + "response_type=code&client_id=client-1&" + + "scope=user&state=.{15,}&" + + "redirect_uri=http://localhost/client-1"); + } + @Test public void configureWhenAuthorizationCodeResponseSuccessThenAuthorizedClientSaved() throws Exception { this.spring.register(OAuth2ClientConfig.class).autowire(); @@ -248,4 +262,30 @@ public String resource1(@RegisteredOAuth2AuthorizedClient("registration-1") OAut } } } + + @EnableWebSecurity + @EnableWebMvc + static class OAuth2ClientInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .oauth2Client(withDefaults()); + // @formatter:on + } + + @Bean + public ClientRegistrationRepository clientRegistrationRepository() { + return clientRegistrationRepository; + } + + @Bean + public OAuth2AuthorizedClientRepository authorizedClientRepository() { + return authorizedClientRepository; + } + } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java index f33c9d02016..a61a99f8726 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java @@ -176,6 +176,25 @@ public void oauth2Login() throws Exception { .isInstanceOf(OAuth2UserAuthority.class).hasToString("ROLE_USER"); } + @Test + public void requestWhenOauth2LoginInLambdaThenAuthenticationContainsOauth2UserAuthority() throws Exception { + loadConfig(OAuth2LoginInLambdaConfig.class); + OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); + this.authorizationRequestRepository.saveAuthorizationRequest( + authorizationRequest, this.request, this.response); + this.request.setParameter("code", "code123"); + this.request.setParameter("state", authorizationRequest.getState()); + + this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); + + Authentication authentication = this.securityContextRepository + .loadContext(new HttpRequestResponseHolder(this.request, this.response)) + .getAuthentication(); + assertThat(authentication.getAuthorities()).hasSize(1); + assertThat(authentication.getAuthorities()).first() + .isInstanceOf(OAuth2UserAuthority.class).hasToString("ROLE_USER"); + } + // gh-6009 @Test public void oauth2LoginWhenSuccessThenAuthenticationSuccessEventPublished() throws Exception { @@ -303,6 +322,29 @@ public void oauth2LoginWithCustomAuthorizationRequestParameters() throws Excepti assertThat(this.response.getRedirectedUrl()).isEqualTo("https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1"); } + @Test + public void requestWhenOauth2LoginWithCustomAuthorizationRequestParametersThenParametersInRedirectedUrl() + throws Exception { + loadConfig(OAuth2LoginConfigCustomAuthorizationRequestResolverInLambda.class); + OAuth2AuthorizationRequestResolver resolver = this.context.getBean( + OAuth2LoginConfigCustomAuthorizationRequestResolverInLambda.class).resolver; + OAuth2AuthorizationRequest result = OAuth2AuthorizationRequest.authorizationCode() + .authorizationUri("https://accounts.google.com/authorize") + .clientId("client-id") + .state("adsfa") + .authorizationRequestUri("https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1") + .build(); + when(resolver.resolve(any())).thenReturn(result); + + String requestUri = "/oauth2/authorization/google"; + this.request = new MockHttpServletRequest("GET", requestUri); + this.request.setServletPath(requestUri); + + this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); + + assertThat(this.response.getRedirectedUrl()).isEqualTo("https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1"); + } + // gh-5347 @Test public void oauth2LoginWithOneClientConfiguredThenRedirectForAuthorization() throws Exception { @@ -374,6 +416,19 @@ public void oauth2LoginWithCustomLoginPageThenRedirectCustomLoginPage() throws E assertThat(this.response.getRedirectedUrl()).matches("http://localhost/custom-login"); } + @Test + public void requestWhenOauth2LoginWithCustomLoginPageInLambdaThenRedirectCustomLoginPage() throws Exception { + loadConfig(OAuth2LoginConfigCustomLoginPageInLambda.class); + + String requestUri = "/"; + this.request = new MockHttpServletRequest("GET", requestUri); + this.request.setServletPath(requestUri); + + this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); + + assertThat(this.response.getRedirectedUrl()).matches("http://localhost/custom-login"); + } + @Test public void oidcLogin() throws Exception { // setup application context @@ -400,6 +455,32 @@ public void oidcLogin() throws Exception { .isInstanceOf(OidcUserAuthority.class).hasToString("ROLE_USER"); } + @Test + public void requestWhenOauth2LoginInLambdaAndOidcThenAuthenticationContainsOidcUserAuthority() throws Exception { + // setup application context + loadConfig(OAuth2LoginInLambdaConfig.class, JwtDecoderFactoryConfig.class); + + // setup authorization request + OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid"); + this.authorizationRequestRepository.saveAuthorizationRequest( + authorizationRequest, this.request, this.response); + + // setup authentication parameters + this.request.setParameter("code", "code123"); + this.request.setParameter("state", authorizationRequest.getState()); + + // perform test + this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); + + // assertions + Authentication authentication = this.securityContextRepository + .loadContext(new HttpRequestResponseHolder(this.request, this.response)) + .getAuthentication(); + assertThat(authentication.getAuthorities()).hasSize(1); + assertThat(authentication.getAuthorities()).first() + .isInstanceOf(OidcUserAuthority.class).hasToString("ROLE_USER"); + } + @Test public void oidcLoginCustomWithConfigurer() throws Exception { // setup application context @@ -521,6 +602,30 @@ public void onApplicationEvent(AuthenticationSuccessEvent event) { } } + @EnableWebSecurity + static class OAuth2LoginInLambdaConfig extends CommonLambdaWebSecurityConfigurerAdapter + implements ApplicationListener { + static List EVENTS = new ArrayList<>(); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .oauth2Login(oauth2Login -> + oauth2Login + .clientRegistrationRepository( + new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)) + ); + // @formatter:on + super.configure(http); + } + + @Override + public void onApplicationEvent(AuthenticationSuccessEvent event) { + EVENTS.add(event); + } + } + @EnableWebSecurity static class OAuth2LoginConfigCustomWithConfigurer extends CommonWebSecurityConfigurerAdapter { @Override @@ -586,6 +691,28 @@ protected void configure(HttpSecurity http) throws Exception { } } + @EnableWebSecurity + static class OAuth2LoginConfigCustomAuthorizationRequestResolverInLambda extends CommonLambdaWebSecurityConfigurerAdapter { + private ClientRegistrationRepository clientRegistrationRepository = + new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION); + + OAuth2AuthorizationRequestResolver resolver = mock(OAuth2AuthorizationRequestResolver.class); + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .oauth2Login(oauth2Login -> + oauth2Login + .clientRegistrationRepository(this.clientRegistrationRepository) + .authorizationEndpoint(authorizationEndpoint -> + authorizationEndpoint + .authorizationRequestResolver(this.resolver) + ) + ); + super.configure(http); + } + } + @EnableWebSecurity static class OAuth2LoginConfigMultipleClients extends CommonWebSecurityConfigurerAdapter { @Override @@ -612,6 +739,23 @@ protected void configure(HttpSecurity http) throws Exception { } } + @EnableWebSecurity + static class OAuth2LoginConfigCustomLoginPageInLambda extends CommonLambdaWebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .oauth2Login(oauth2Login -> + oauth2Login + .clientRegistrationRepository( + new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)) + .loginPage("/custom-login") + ); + // @formatter:on + super.configure(http); + } + } + @EnableWebSecurity static class OAuth2LoginConfigWithOidcLogoutSuccessHandler extends CommonWebSecurityConfigurerAdapter { @Override @@ -667,6 +811,45 @@ HttpSessionOAuth2AuthorizationRequestRepository oauth2AuthorizationRequestReposi } } + private static abstract class CommonLambdaWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .securityContext(securityContext -> + securityContext + .securityContextRepository(securityContextRepository()) + ) + .oauth2Login(oauth2Login -> + oauth2Login + .tokenEndpoint(tokenEndpoint -> + tokenEndpoint + .accessTokenResponseClient(createOauth2AccessTokenResponseClient()) + ) + .userInfoEndpoint(userInfoEndpoint -> + userInfoEndpoint + .userService(createOauth2UserService()) + .oidcUserService(createOidcUserService()) + ) + ); + // @formatter:on + } + + @Bean + SecurityContextRepository securityContextRepository() { + return new HttpSessionSecurityContextRepository(); + } + + @Bean + HttpSessionOAuth2AuthorizationRequestRepository oauth2AuthorizationRequestRepository() { + return new HttpSessionOAuth2AuthorizationRequestRepository(); + } + } + @Configuration static class JwtDecoderFactoryConfig { diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java index 995e7d98a8e..494ff7d74b8 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java @@ -127,6 +127,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.oauth2.core.TestOAuth2AccessTokens.noScopes; import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri; import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withPublicKey; @@ -184,6 +185,19 @@ public void getWhenUsingDefaultsWithValidBearerTokenThenAcceptsRequest() .andExpect(content().string("ok")); } + @Test + public void getWhenUsingDefaultsInLambdaWithValidBearerTokenThenAcceptsRequest() + throws Exception { + + this.spring.register(RestOperationsConfig.class, DefaultInLambdaConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("Default")); + String token = this.token("ValidNoScopes"); + + this.mvc.perform(get("/").with(bearerToken(token))) + .andExpect(status().isOk()) + .andExpect(content().string("ok")); + } + @Test public void getWhenUsingJwkSetUriThenAcceptsRequest() throws Exception { this.spring.register(WebServerConfig.class, JwkSetUriConfig.class, BasicController.class).autowire(); @@ -195,6 +209,16 @@ public void getWhenUsingJwkSetUriThenAcceptsRequest() throws Exception { .andExpect(content().string("ok")); } + @Test + public void getWhenUsingJwkSetUriInLambdaThenAcceptsRequest() throws Exception { + this.spring.register(WebServerConfig.class, JwkSetUriInLambdaConfig.class, BasicController.class).autowire(); + mockWebServer(jwks("Default")); + String token = this.token("ValidNoScopes"); + + this.mvc.perform(get("/").with(bearerToken(token))) + .andExpect(status().isOk()) + .andExpect(content().string("ok")); + } @Test public void getWhenUsingDefaultsWithExpiredBearerTokenThenInvalidToken() @@ -756,6 +780,23 @@ public void requestWhenCustomJwtDecoderWiredOnDslThenUsed() .andExpect(content().string(JWT_SUBJECT)); } + @Test + public void requestWhenCustomJwtDecoderInLambdaOnDslThenUsed() + throws Exception { + + this.spring.register(CustomJwtDecoderInLambdaOnDsl.class, BasicController.class).autowire(); + + CustomJwtDecoderInLambdaOnDsl config = this.spring.getContext().getBean(CustomJwtDecoderInLambdaOnDsl.class); + JwtDecoder decoder = config.decoder(); + + when(decoder.decode(anyString())).thenReturn(JWT); + + this.mvc.perform(get("/authenticated") + .with(bearerToken(JWT_TOKEN))) + .andExpect(status().isOk()) + .andExpect(content().string(JWT_SUBJECT)); + } + @Test public void requestWhenCustomJwtDecoderExposedAsBeanThenUsed() throws Exception { @@ -1067,6 +1108,17 @@ public void getWhenIntrospectingThenOk() throws Exception { .andExpect(content().string("test-subject")); } + @Test + public void getWhenOpaqueTokenInLambdaAndIntrospectingThenOk() throws Exception { + this.spring.register(RestOperationsConfig.class, OpaqueTokenInLambdaConfig.class, BasicController.class).autowire(); + mockRestOperations(json("Active")); + + this.mvc.perform(get("/authenticated") + .with(bearerToken("token"))) + .andExpect(status().isOk()) + .andExpect(content().string("test-subject")); + } + @Test public void getWhenIntrospectionFailsThenUnauthorized() throws Exception { this.spring.register(RestOperationsConfig.class, OpaqueTokenConfig.class).autowire(); @@ -1104,6 +1156,20 @@ public void getWhenCustomIntrospectionAuthenticationManagerThenUsed() throws Exc verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class)); } + @Test + public void getWhenCustomIntrospectionAuthenticationManagerInLambdaThenUsed() throws Exception { + this.spring.register(OpaqueTokenAuthenticationManagerInLambdaConfig.class, BasicController.class).autowire(); + + when(bean(AuthenticationProvider.class).authenticate(any(Authentication.class))) + .thenReturn(INTROSPECTION_AUTHENTICATION_TOKEN); + this.mvc.perform(get("/authenticated") + .with(bearerToken("token"))) + .andExpect(status().isOk()) + .andExpect(content().string("mock-test-subject")); + + verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class)); + } + @Test public void configureWhenOnlyIntrospectionUrlThenException() throws Exception { assertThatCode(() -> this.spring.register(OpaqueTokenHalfConfiguredConfig.class).autowire()) @@ -1311,6 +1377,26 @@ protected void configure(HttpSecurity http) throws Exception { } } + @EnableWebSecurity + static class DefaultInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/requires-read-scope").access("hasAuthority('SCOPE_message:read')") + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2ResourceServer -> + oauth2ResourceServer + .jwt(withDefaults()) + ); + // @formatter:on + } + } + @EnableWebSecurity static class JwkSetUriConfig extends WebSecurityConfigurerAdapter { @Value("${mockwebserver.url:https://example.org}") @@ -1331,6 +1417,31 @@ protected void configure(HttpSecurity http) throws Exception { } } + @EnableWebSecurity + static class JwkSetUriInLambdaConfig extends WebSecurityConfigurerAdapter { + @Value("${mockwebserver.url:https://example.org}") + String jwkSetUri; + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/requires-read-scope").access("hasAuthority('SCOPE_message:read')") + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2ResourceServer -> + oauth2ResourceServer + .jwt(jwt -> + jwt + .jwkSetUri(this.jwkSetUri) + ) + ); + // @formatter:on + } + } + @EnableWebSecurity static class CsrfDisabledConfig extends WebSecurityConfigurerAdapter { @Value("${mockwebserver.url:https://example.org}") @@ -1677,6 +1788,33 @@ JwtDecoder decoder() { } } + @EnableWebSecurity + static class CustomJwtDecoderInLambdaOnDsl extends WebSecurityConfigurerAdapter { + JwtDecoder decoder = mock(JwtDecoder.class); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2ResourceServer -> + oauth2ResourceServer + .jwt(jwt -> + jwt + .decoder(decoder()) + ) + ); + // @formatter:on + } + + JwtDecoder decoder() { + return this.decoder; + } + } + @EnableWebSecurity static class CustomJwtDecoderAsBean extends WebSecurityConfigurerAdapter { @Override @@ -1831,6 +1969,25 @@ protected void configure(HttpSecurity http) throws Exception { } } + @EnableWebSecurity + static class OpaqueTokenInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read") + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2ResourceServer -> + oauth2ResourceServer + .opaqueToken(withDefaults()) + ); + // @formatter:on + } + } + @EnableWebSecurity static class OpaqueTokenAuthenticationManagerConfig extends WebSecurityConfigurerAdapter { @Override @@ -1852,6 +2009,32 @@ public AuthenticationProvider authenticationProvider() { } } + @EnableWebSecurity + static class OpaqueTokenAuthenticationManagerInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2ResourceServer -> + oauth2ResourceServer + .opaqueToken(opaqueToken -> + opaqueToken + .authenticationManager(authenticationProvider()::authenticate) + ) + ); + // @formatter:on + } + + @Bean + public AuthenticationProvider authenticationProvider() { + return mock(AuthenticationProvider.class); + } + } + @EnableWebSecurity static class OpaqueAndJwtConfig extends WebSecurityConfigurerAdapter { @Override diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurerTests.java index d18efe0b374..159c54967ea 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurerTests.java @@ -16,8 +16,13 @@ package org.springframework.security.config.annotation.web.configurers.openid; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; import org.junit.Rule; import org.junit.Test; +import org.openid4java.consumer.ConsumerManager; +import org.openid4java.discovery.DiscoveryInformation; +import org.openid4java.message.AuthRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.ObjectPostProcessor; @@ -26,13 +31,23 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.test.SpringTestRule; +import org.springframework.security.openid.OpenIDAttribute; import org.springframework.security.openid.OpenIDAuthenticationFilter; import org.springframework.security.openid.OpenIDAuthenticationProvider; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.openid4java.discovery.yadis.YadisResolver.YADIS_XRDS_LOCATION; +import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -128,4 +143,167 @@ protected void configure(HttpSecurity http) throws Exception { // @formatter:on } } + + @Test + public void requestWhenOpenIdLoginPageInLambdaThenRedirectsToLoginPAge() throws Exception { + this.spring.register(OpenIdLoginPageInLambdaConfig.class).autowire(); + + this.mvc.perform(get("/")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("http://localhost/login/custom")); + } + + @EnableWebSecurity + static class OpenIdLoginPageInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .openidLogin(openIdLogin -> + openIdLogin + .loginPage("/login/custom") + ); + // @formatter:on + } + } + + @Test + public void requestWhenAttributeExchangeConfiguredThenFetchAttributesMatchAttributeList() throws Exception { + OpenIdAttributesInLambdaConfig.CONSUMER_MANAGER = mock(ConsumerManager.class); + AuthRequest mockAuthRequest = mock(AuthRequest.class); + DiscoveryInformation mockDiscoveryInformation = mock(DiscoveryInformation.class); + when(mockAuthRequest.getDestinationUrl(anyBoolean())).thenReturn("mockUrl"); + when(OpenIdAttributesInLambdaConfig.CONSUMER_MANAGER.associate(any())) + .thenReturn(mockDiscoveryInformation); + when(OpenIdAttributesInLambdaConfig.CONSUMER_MANAGER.authenticate(any(DiscoveryInformation.class), any(), any())) + .thenReturn(mockAuthRequest); + this.spring.register(OpenIdAttributesInLambdaConfig.class).autowire(); + + try ( MockWebServer server = new MockWebServer() ) { + String endpoint = server.url("/").toString(); + + server.enqueue(new MockResponse() + .addHeader(YADIS_XRDS_LOCATION, endpoint)); + server.enqueue(new MockResponse() + .setBody(String.format("%s", endpoint))); + + MvcResult mvcResult = this.mvc.perform(get("/login/openid") + .param(OpenIDAuthenticationFilter.DEFAULT_CLAIMED_IDENTITY_FIELD, endpoint)) + .andExpect(status().isFound()) + .andReturn(); + + Object attributeObject = mvcResult.getRequest().getSession().getAttribute("SPRING_SECURITY_OPEN_ID_ATTRIBUTES_FETCH_LIST"); + assertThat(attributeObject).isInstanceOf(List.class); + List attributeList = (List) attributeObject; + assertThat(attributeList.stream().anyMatch(attribute -> + "nickname".equals(attribute.getName()) + && "https://schema.openid.net/namePerson/friendly".equals(attribute.getType()))) + .isTrue(); + assertThat(attributeList.stream().anyMatch(attribute -> + "email".equals(attribute.getName()) + && "https://schema.openid.net/contact/email".equals(attribute.getType()) + && attribute.isRequired() + && attribute.getCount() == 2)) + .isTrue(); + } + } + + @EnableWebSecurity + static class OpenIdAttributesInLambdaConfig extends WebSecurityConfigurerAdapter { + static ConsumerManager CONSUMER_MANAGER; + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().permitAll() + ) + .openidLogin(openIdLogin -> + openIdLogin + .consumerManager(CONSUMER_MANAGER) + .attributeExchange(attributeExchange -> + attributeExchange + .identifierPattern(".*") + .attribute(nicknameAttribute -> + nicknameAttribute + .name("nickname") + .type("https://schema.openid.net/namePerson/friendly") + ) + .attribute(emailAttribute -> + emailAttribute + .name("email") + .type("https://schema.openid.net/contact/email") + .required(true) + .count(2) + ) + ) + ); + // @formatter:on + } + } + + @Test + public void requestWhenAttributeNameNotSpecifiedThenAttributeNameDefaulted() + throws Exception { + OpenIdAttributesNullNameConfig.CONSUMER_MANAGER = mock(ConsumerManager.class); + AuthRequest mockAuthRequest = mock(AuthRequest.class); + DiscoveryInformation mockDiscoveryInformation = mock(DiscoveryInformation.class); + when(mockAuthRequest.getDestinationUrl(anyBoolean())).thenReturn("mockUrl"); + when(OpenIdAttributesNullNameConfig.CONSUMER_MANAGER.associate(any())) + .thenReturn(mockDiscoveryInformation); + when(OpenIdAttributesNullNameConfig.CONSUMER_MANAGER.authenticate(any(DiscoveryInformation.class), any(), any())) + .thenReturn(mockAuthRequest); + this.spring.register(OpenIdAttributesNullNameConfig.class).autowire(); + + try ( MockWebServer server = new MockWebServer() ) { + String endpoint = server.url("/").toString(); + + server.enqueue(new MockResponse() + .addHeader(YADIS_XRDS_LOCATION, endpoint)); + server.enqueue(new MockResponse() + .setBody(String.format("%s", endpoint))); + + MvcResult mvcResult = this.mvc.perform(get("/login/openid") + .param(OpenIDAuthenticationFilter.DEFAULT_CLAIMED_IDENTITY_FIELD, endpoint)) + .andExpect(status().isFound()) + .andReturn(); + + Object attributeObject = mvcResult.getRequest().getSession().getAttribute("SPRING_SECURITY_OPEN_ID_ATTRIBUTES_FETCH_LIST"); + assertThat(attributeObject).isInstanceOf(List.class); + List attributeList = (List) attributeObject; + assertThat(attributeList).hasSize(1); + assertThat(attributeList.get(0).getName()).isEqualTo("default-attribute"); + } + } + + @EnableWebSecurity + static class OpenIdAttributesNullNameConfig extends WebSecurityConfigurerAdapter { + static ConsumerManager CONSUMER_MANAGER; + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().permitAll() + ) + .openidLogin(openIdLogin -> + openIdLogin + .consumerManager(CONSUMER_MANAGER) + .attributeExchange(attributeExchange -> + attributeExchange + .identifierPattern(".*") + .attribute(withDefaults()) + ) + ); + // @formatter:on + } + } } diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/additional-topics/mvc.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/additional-topics/mvc.adoc index a23b9bf0a18..7173999ca10 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/additional-topics/mvc.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/additional-topics/mvc.adoc @@ -104,8 +104,10 @@ If we wanted to restrict access to this controller method to admin users, a deve ---- protected configure(HttpSecurity http) throws Exception { http - .authorizeRequests() - .antMatchers("/admin").hasRole("ADMIN"); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/admin").hasRole("ADMIN") + ); } ---- @@ -133,8 +135,10 @@ The following configuration will protect the same URLs that Spring MVC will matc ---- protected configure(HttpSecurity http) throws Exception { http - .authorizeRequests() - .mvcMatchers("/admin").hasRole("ADMIN"); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .mvcMatchers("/admin").hasRole("ADMIN") + ); } ---- diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/additional-topics/oauth2.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/additional-topics/oauth2.adoc index 511de42d095..0fb146b07bb 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/additional-topics/oauth2.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/additional-topics/oauth2.adoc @@ -16,15 +16,25 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .oauth2Login() - .authorizationEndpoint() - ... - .redirectionEndpoint() - ... - .tokenEndpoint() - ... - .userInfoEndpoint() - ... + .oauth2Login(oauth2Login -> + oauth2Login + .authorizationEndpoint(authorizationEndpoint -> + authorizationEndpoint + ... + ) + .redirectionEndpoint(redirectionEndpoint -> + redirectionEndpoint + ... + ) + .tokenEndpoint(tokenEndpoint -> + tokenEndpoint + ... + ) + .userInfoEndpoint(userInfoEndpoint -> + userInfoEndpoint + ... + ) + ); } } ---- @@ -58,27 +68,34 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .oauth2Login() - .clientRegistrationRepository(this.clientRegistrationRepository()) - .authorizedClientRepository(this.authorizedClientRepository()) - .authorizedClientService(this.authorizedClientService()) - .loginPage("/login") - .authorizationEndpoint() - .baseUri(this.authorizationRequestBaseUri()) - .authorizationRequestRepository(this.authorizationRequestRepository()) - .authorizationRequestResolver(this.authorizationRequestResolver()) - .and() - .redirectionEndpoint() - .baseUri(this.authorizationResponseBaseUri()) - .and() - .tokenEndpoint() - .accessTokenResponseClient(this.accessTokenResponseClient()) - .and() - .userInfoEndpoint() - .userAuthoritiesMapper(this.userAuthoritiesMapper()) - .userService(this.oauth2UserService()) - .oidcUserService(this.oidcUserService()) - .customUserType(GitHubOAuth2User.class, "github"); + .oauth2Login(oauth2Login -> + oauth2Login + .clientRegistrationRepository(this.clientRegistrationRepository()) + .authorizedClientRepository(this.authorizedClientRepository()) + .authorizedClientService(this.authorizedClientService()) + .loginPage("/login") + .authorizationEndpoint(authorizationEndpoint -> + authorizationEndpoint + .baseUri(this.authorizationRequestBaseUri()) + .authorizationRequestRepository(this.authorizationRequestRepository()) + .authorizationRequestResolver(this.authorizationRequestResolver()) + ) + .redirectionEndpoint(redirectionEndpoint -> + redirectionEndpoint + .baseUri(this.authorizationResponseBaseUri()) + ) + .tokenEndpoint(tokenEndpoint -> + tokenEndpoint + .accessTokenResponseClient(this.accessTokenResponseClient()) + ) + .userInfoEndpoint(userInfoEndpoint -> + userInfoEndpoint + .userAuthoritiesMapper(this.userAuthoritiesMapper()) + .userService(this.oauth2UserService()) + .oidcUserService(this.oidcUserService()) + .customUserType(GitHubOAuth2User.class, "github") + ) + ); } } ---- @@ -123,12 +140,16 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .oauth2Login() - .loginPage("/login/oauth2") - ... - .authorizationEndpoint() - .baseUri("/login/oauth2/authorization") - .... + .oauth2Login(oauth2Login -> + oauth2Login + .loginPage("/login/oauth2") + ... + .authorizationEndpoint(authorizationEndpoint -> + authorizationEndpoint + .baseUri("/login/oauth2/authorization") + ... + ) + ); } } ---- @@ -171,10 +192,14 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .oauth2Login() - .redirectionEndpoint() - .baseUri("/login/oauth2/callback/*") - .... + .oauth2Login(oauth2Login -> + oauth2Login + .redirectionEndpoint(redirectionEndpoint -> + redirectionEndpoint + .baseUri("/login/oauth2/callback/*") + ... + ) + ); } } ---- @@ -234,10 +259,14 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .oauth2Login() - .userInfoEndpoint() - .userAuthoritiesMapper(this.userAuthoritiesMapper()) - ... + .oauth2Login(oauth2Login -> + oauth2Login + .userInfoEndpoint(userInfoEndpoint -> + userInfoEndpoint + .userAuthoritiesMapper(this.userAuthoritiesMapper()) + ... + ) + ); } private GrantedAuthoritiesMapper userAuthoritiesMapper() { @@ -280,7 +309,8 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { - http.oauth2Login(); + http + .oauth2Login(withDefaults()); } @Bean @@ -308,10 +338,14 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .oauth2Login() - .userInfoEndpoint() - .oidcUserService(this.oidcUserService()) - ... + .oauth2Login(oauth2Login -> + oauth2Login + .userInfoEndpoint(userInfoEndpoint -> + userInfoEndpoint + .oidcUserService(this.oidcUserService()) + ... + ) + ); } private OAuth2UserService oidcUserService() { @@ -355,10 +389,14 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .oauth2Login() - .userInfoEndpoint() - .customUserType(GitHubOAuth2User.class, "github") - ... + .oauth2Login(oauth2Login -> + oauth2Login + .userInfoEndpoint(userInfoEndpoint -> + userInfoEndpoint + .customUserType(GitHubOAuth2User.class, "github") + ... + ) + ); } } ---- @@ -469,10 +507,14 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .oauth2Login() - .userInfoEndpoint() - .userService(this.oauth2UserService()) - ... + .oauth2Login(oauth2Login -> + oauth2Login + .userInfoEndpoint(userInfoEndpoint -> + userInfoEndpoint + .userService(this.oauth2UserService()) + ... + ) + ); } private OAuth2UserService oauth2UserService() { @@ -501,10 +543,14 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .oauth2Login() - .userInfoEndpoint() - .oidcUserService(this.oidcUserService()) - ... + .oauth2Login(oauth2Login -> + oauth2Login + .userInfoEndpoint(userInfoEndpoint -> + userInfoEndpoint + .oidcUserService(this.oidcUserService()) + ... + ) + ); } private OAuth2UserService oidcUserService() { diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/expression-based.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/expression-based.adoc index aac5044dd1d..e2308674e19 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/expression-based.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/expression-based.adoc @@ -169,9 +169,11 @@ or in Java configuration [source,java] ---- http - .authorizeRequests() - .antMatchers("/user/{userId}/**").access("@webSecurity.checkUserId(authentication,#userId)") - ... + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/user/{userId}/**").access("@webSecurity.checkUserId(authentication,#userId)") + ... + ); ---- In both configurations URLs that match would pass in the path variable (and convert it) into checkUserId method. diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/preface/java-configuration.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/preface/java-configuration.adoc index 5990618e510..36141a4426e 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/preface/java-configuration.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/preface/java-configuration.adoc @@ -137,12 +137,12 @@ How does Spring Security know that we want to require all users to be authentica ---- protected void configure(HttpSecurity http) throws Exception { http - .authorizeRequests() - .anyRequest().authenticated() - .and() - .formLogin() - .and() - .httpBasic(); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .formLogin(withDefaults()) + .httpBasic(withDefaults()); } ---- @@ -163,10 +163,6 @@ You will notice that this configuration is quite similar the XML Namespace confi ---- -The Java Configuration equivalent of closing an XML tag is expressed using the `and()` method which allows us to continue configuring the parent. -If you read the code it also makes sense. -I want to configure authorized requests __and__ configure form login __and__ configure HTTP Basic authentication. - [[jc-form]] == Java Configuration and Form Login You might be wondering where the login form came from when you were prompted to log in, since we made no mention of any HTML files or JSPs. @@ -180,12 +176,15 @@ To do so we can update our configuration as seen below: ---- protected void configure(HttpSecurity http) throws Exception { http - .authorizeRequests() - .anyRequest().authenticated() - .and() - .formLogin() - .loginPage("/login") // <1> - .permitAll(); // <2> + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .formLogin(formLogin -> + formLogin + .loginPage("/login") // <1> + .permitAll() // <2> + ); } ---- @@ -245,14 +244,14 @@ For example: ---- protected void configure(HttpSecurity http) throws Exception { http - .authorizeRequests() <1> - .antMatchers("/resources/**", "/signup", "/about").permitAll() <2> - .antMatchers("/admin/**").hasRole("ADMIN") <3> - .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") <4> - .anyRequest().authenticated() <5> - .and() - // ... - .formLogin(); + .authorizeRequests(authorizeRequests -> // <1> + authorizeRequests + .antMatchers("/resources/**", "/signup", "/about").permitAll() // <2> + .antMatchers("/admin/**").hasRole("ADMIN") // <3> + .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") // <4> + .anyRequest().authenticated() // <5> + ) + .formLogin(withDefaults()); } ---- @@ -282,14 +281,15 @@ Similar to configuring login capabilities, however, you also have various option ---- protected void configure(HttpSecurity http) throws Exception { http - .logout() <1> - .logoutUrl("/my/logout") <2> - .logoutSuccessUrl("/my/index") <3> - .logoutSuccessHandler(logoutSuccessHandler) <4> - .invalidateHttpSession(true) <5> - .addLogoutHandler(logoutHandler) <6> - .deleteCookies(cookieNamesToClear) <7> - .and() + .logout(logout -> // <1> + logout + .logoutUrl("/my/logout") // <2> + .logoutSuccessUrl("/my/index") // <3> + .logoutSuccessHandler(logoutSuccessHandler) // <4> + .invalidateHttpSession(true) // <5> + .addLogoutHandler(logoutHandler) // <6> + .deleteCookies(cookieNamesToClear) // <7> + ) ... } ---- @@ -510,11 +510,14 @@ The first is a `WebSecurityConfigurerAdapter` that configures the app as a resou ```java protected void configure(HttpSecurity http) { http - .authorizeRequests() - .anyRequest().authenticated() - .and() - .oauth2ResourceServer() - .jwt(); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2ResourceServer -> + oauth2ResourceServer + .jwt(withDefaults()) + ); } ``` @@ -527,13 +530,18 @@ Replacing this is as simple as exposing the bean within the application: public class MyCustomSecurityConfiguration extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) { http - .authorizeRequests() - .mvcMatchers("/messages/**").hasAuthority("SCOPE_message:read") - .anyRequest().authenticated() - .and() - .oauth2ResourceServer() - .jwt() - .jwtAuthenticationConverter(myConverter()); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .mvcMatchers("/messages/**").hasAuthority("SCOPE_message:read") + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2ResourceServer -> + oauth2ResourceServer + .jwt(jwt -> + jwt + .jwtAuthenticationConverter(myConverter()) + ) + ); } } ``` @@ -565,12 +573,17 @@ An authorization server's JWK Set Uri can be configured < + authorizeRequests + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2ResourceServer -> + oauth2ResourceServer + .jwt(jwt -> + jwt + .jwkSetUri("https://idp.example.com/.well-known/jwks.json") + ) + ); } } ``` @@ -587,12 +600,17 @@ More powerful than `jwkSetUri()` is `decoder()`, which will completely replace a public class DirectlyConfiguredJwkSetUri extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) { http - .authorizeRequests() - .anyRequest().authenticated() - .and() - .oauth2ResourceServer() - .jwt() - .decoder(myCustomDecoder()); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2ResourceServer -> + oauth2ResourceServer + .jwt(jwt -> + jwt + .decoder(myCustomDecoder()) + ) + ); } } ``` @@ -627,13 +645,16 @@ This means that to protect an endpoint or method with a scope derived from a JWT public class DirectlyConfiguredJwkSetUri extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) { http - .authorizeRequests() - .mvcMatchers("/contacts/**").hasAuthority("SCOPE_contacts") - .mvcMatchers("/messages/**").hasAuthority("SCOPE_messages") - .anyRequest().authenticated() - .and() - .oauth2ResourceServer() - .jwt(); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .mvcMatchers("/contacts/**").hasAuthority("SCOPE_contacts") + .mvcMatchers("/messages/**").hasAuthority("SCOPE_messages") + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2ResourceServer -> + oauth2ResourceServer + .jwt(withDefaults()) + ); } } ``` @@ -659,12 +680,17 @@ To this end, the DSL exposes `jwtAuthenticationConverter()`: public class DirectlyConfiguredJwkSetUri extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) { http - .authorizeRequests() - .anyRequest().authenticated() - .and() - .oauth2ResourceServer() - .jwt() - .jwtAuthenticationConverter(grantedAuthoritiesExtractor()); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2ResourceServer -> + oauth2ResourceServer + .jwt(jwt -> + jwt + .jwtAuthenticationConverter(grantedAuthoritiesExtractor()) + ) + ); } } @@ -1078,10 +1104,11 @@ public class MultiHttpSecurityConfig { protected void configure(HttpSecurity http) throws Exception { http .antMatcher("/api/**") <3> - .authorizeRequests() - .anyRequest().hasRole("ADMIN") - .and() - .httpBasic(); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().hasRole("ADMIN") + ) + .httpBasic(withDefaults()); } } @@ -1091,10 +1118,11 @@ public class MultiHttpSecurityConfig { @Override protected void configure(HttpSecurity http) throws Exception { http - .authorizeRequests() - .anyRequest().authenticated() - .and() - .formLogin(); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .formLogin(withDefaults()); } } } @@ -1221,15 +1249,17 @@ For example, if you wanted to configure the `filterSecurityPublishAuthorizationS @Override protected void configure(HttpSecurity http) throws Exception { http - .authorizeRequests() - .anyRequest().authenticated() - .withObjectPostProcessor(new ObjectPostProcessor() { - public O postProcess( - O fsi) { - fsi.setPublishAuthorizationSuccess(true); - return fsi; - } - }); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + .withObjectPostProcessor(new ObjectPostProcessor() { + public O postProcess( + O fsi) { + fsi.setPublishAuthorizationSuccess(true); + return fsi; + } + }) + ); } ---- diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-client.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-client.adoc index f362a11f7c1..74fb98708a9 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-client.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-client.adoc @@ -20,14 +20,18 @@ public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .oauth2Client() - .clientRegistrationRepository(this.clientRegistrationRepository()) - .authorizedClientRepository(this.authorizedClientRepository()) - .authorizedClientService(this.authorizedClientService()) - .authorizationCodeGrant() - .authorizationRequestRepository(this.authorizationRequestRepository()) - .authorizationRequestResolver(this.authorizationRequestResolver()) - .accessTokenResponseClient(this.accessTokenResponseClient()); + .oauth2Client(oauth2Client -> + oauth2Client + .clientRegistrationRepository(this.clientRegistrationRepository()) + .authorizedClientRepository(this.authorizedClientRepository()) + .authorizedClientService(this.authorizedClientService()) + .authorizationCodeGrant(authorizationCodeGrant -> + authorizationCodeGrant + .authorizationRequestRepository(this.authorizationRequestRepository()) + .authorizationRequestResolver(this.authorizationRequestResolver()) + .accessTokenResponseClient(this.accessTokenResponseClient()) + ) + ); } } ---- @@ -245,10 +249,14 @@ public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .oauth2Client() - .authorizationCodeGrant() - .authorizationRequestRepository(this.cookieAuthorizationRequestRepository()) - ... + .oauth2Client(oauth2Client -> + oauth2Client + .authorizationCodeGrant(authorizationCodeGrant -> + authorizationCodeGrant + .authorizationRequestRepository(this.cookieAuthorizationRequestRepository()) + ... + ) + ); } private AuthorizationRequestRepository cookieAuthorizationRequestRepository() { @@ -285,14 +293,19 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .authorizeRequests() - .anyRequest().authenticated() - .and() - .oauth2Login() - .authorizationEndpoint() - .authorizationRequestResolver( - new CustomAuthorizationRequestResolver( - this.clientRegistrationRepository)); <1> + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .oauth2Login(oauth2Login -> + oauth2Login + .authorizationEndpoint(authorizationEndpoint -> + authorizationEndpoint + .authorizationRequestResolver( + new CustomAuthorizationRequestResolver( + this.clientRegistrationRepository)) <1> + ) + ); } } @@ -422,10 +435,14 @@ public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .oauth2Client() - .authorizationCodeGrant() - .accessTokenResponseClient(this.customAccessTokenResponseClient()) - ... + .oauth2Client(oauth2Client -> + oauth2Client + .authorizationCodeGrant(authorizationCodeGrant -> + authorizationCodeGrant + .accessTokenResponseClient(this.customAccessTokenResponseClient()) + ... + ) + ); } private OAuth2AccessTokenResponseClient customAccessTokenResponseClient() { diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-login.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-login.adoc index 5d5307e65de..adead1a92d1 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-login.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-login.adoc @@ -285,10 +285,11 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .authorizeRequests() - .anyRequest().authenticated() - .and() - .oauth2Login(); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .oauth2Login(withDefaults()); } } ---- @@ -310,10 +311,11 @@ public class OAuth2LoginConfig { @Override protected void configure(HttpSecurity http) throws Exception { http - .authorizeRequests() - .anyRequest().authenticated() - .and() - .oauth2Login(); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .oauth2Login(withDefaults()); } } @@ -358,10 +360,11 @@ public class OAuth2LoginConfig { @Override protected void configure(HttpSecurity http) throws Exception { http - .authorizeRequests() - .anyRequest().authenticated() - .and() - .oauth2Login(); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .oauth2Login(withDefaults()); } } diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/web/cors.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/web/cors.adoc index 11fd3f9e39e..17283ae6a9a 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/web/cors.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/web/cors.adoc @@ -18,7 +18,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) throws Exception { http // by default uses a Bean by the name of corsConfigurationSource - .cors().and() + .cors(withDefaults()) ... } @@ -59,7 +59,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { http // if Spring MVC is on classpath and no CorsConfigurationSource is provided, // Spring Security will use CORS configuration provided to Spring MVC - .cors().and() + .cors(withDefaults()) ... } } diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/web/csrf.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/web/csrf.adoc index 957950a5f48..aa4e6b8215a 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/web/csrf.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/web/csrf.adoc @@ -187,7 +187,9 @@ WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .csrf().disable(); + .csrf(csrf -> + csrf.disable() + ); } } ---- @@ -314,8 +316,10 @@ public class WebSecurityConfig extends @Override protected void configure(HttpSecurity http) throws Exception { http - .csrf() - .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); + .csrf(csrf -> + csrf + .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) + ); } } ---- @@ -391,8 +395,10 @@ WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .logout() - .logoutRequestMatcher(new AntPathRequestMatcher("/logout")); + .logout(logout -> + logout + .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) + ); } } ---- diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/web/headers.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/web/headers.adoc index 26e0fe51577..72cfc6c20ee 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/web/headers.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/web/headers.adoc @@ -60,9 +60,15 @@ public class WebSecurityConfig extends protected void configure(HttpSecurity http) throws Exception { http // ... - .headers() - .frameOptions().sameOrigin() - .httpStrictTransportSecurity().disable(); + .headers(headers -> + headers + .frameOptions(frameOptions -> + frameOptions.sameOrigin() + ) + .httpStrictTransportSecurity(hsts -> + hsts.disable() + ) + ); } } ---- @@ -92,15 +98,17 @@ If you are using Spring Security's Java Configuration the following will only ad public class WebSecurityConfig extends WebSecurityConfigurerAdapter { -@Override -protected void configure(HttpSecurity http) throws Exception { - http - // ... - .headers() - // do not use any default headers unless explicitly listed - .defaultsDisabled() - .cacheControl(); -} + @Override + protected void configure(HttpSecurity http) throws Exception { + http + // ... + .headers(headers -> + headers + // do not use any default headers unless explicitly listed + .defaultsDisabled() + .cacheControl(withDefaults()) + ); + } } ---- @@ -126,12 +134,14 @@ If necessary, you can disable all of the HTTP Security response headers with the public class WebSecurityConfig extends WebSecurityConfigurerAdapter { -@Override -protected void configure(HttpSecurity http) throws Exception { - http - // ... - .headers().disable(); -} + @Override + protected void configure(HttpSecurity http) throws Exception { + http + // ... + .headers(headers -> + headers.disable() + ); + } } ---- @@ -182,14 +192,16 @@ Similarly, you can enable only cache control within Java Configuration with the public class WebSecurityConfig extends WebSecurityConfigurerAdapter { -@Override -protected void configure(HttpSecurity http) throws Exception { - http - // ... - .headers() - .defaultsDisabled() - .cacheControl(); -} + @Override + protected void configure(HttpSecurity http) throws Exception { + http + // ... + .headers(headers -> + headers + .defaultsDisabled() + .cacheControl(withDefaults()) + ); + } } ---- @@ -263,14 +275,16 @@ If you want more control over the headers, you can explicitly specify the conten public class WebSecurityConfig extends WebSecurityConfigurerAdapter { -@Override -protected void configure(HttpSecurity http) throws Exception { - http - // ... - .headers() - .defaultsDisabled() - .contentTypeOptions(); -} + @Override + protected void configure(HttpSecurity http) throws Exception { + http + // ... + .headers(headers -> + headers + .defaultsDisabled() + .contentTypeOptions(withDefaults()) + ); + } } ---- @@ -327,16 +341,20 @@ Similarly, you can enable only HSTS headers with Java Configuration: public class WebSecurityConfig extends WebSecurityConfigurerAdapter { -@Override -protected void configure(HttpSecurity http) throws Exception { - http - // ... - .headers() - .httpStrictTransportSecurity() - .includeSubdomains(true) - .preload(true) - .maxAgeSeconds(31536000); -} + @Override + protected void configure(HttpSecurity http) throws Exception { + http + // ... + .headers(headers -> + headers + .httpStrictTransportSecurity(hsts -> + hsts + .includeSubDomains(true) + .preload(true) + .maxAgeInSeconds(31536000) + ) + ); + } } ---- @@ -399,16 +417,20 @@ Similarly, you can enable HPKP headers with Java Configuration: public class WebSecurityConfig extends WebSecurityConfigurerAdapter { - @Override - protected void configure(HttpSecurity http) throws Exception { - http - // ... - .headers() - .httpPublicKeyPinning() - .includeSubdomains(true) - .reportUri("https://example.net/pkp-report") - .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="; - } + @Override + protected void configure(HttpSecurity http) throws Exception { + http + // ... + .headers(headers -> + headers + .httpPublicKeyPinning(hpkp -> + hpkp + .includeSubDomains(true) + .reportUri("https://example.net/pkp-report") + .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=") + ) + ); + } } ---- @@ -461,14 +483,18 @@ Similarly, you can customize frame options to use the same origin within Java Co public class WebSecurityConfig extends WebSecurityConfigurerAdapter { -@Override -protected void configure(HttpSecurity http) throws Exception { - http - // ... - .headers() - .frameOptions() - .sameOrigin(); -} + @Override + protected void configure(HttpSecurity http) throws Exception { + http + // ... + .headers(headers -> + headers + .frameOptions(frameOptions -> + frameOptions + .sameOrigin() + ) + ); + } } ---- @@ -511,14 +537,18 @@ Similarly, you can customize XSS protection within Java Configuration with the f public class WebSecurityConfig extends WebSecurityConfigurerAdapter { -@Override -protected void configure(HttpSecurity http) throws Exception { - http - // ... - .headers() - .xssProtection() - .block(false); -} + @Override + protected void configure(HttpSecurity http) throws Exception { + http + // ... + .headers(headers -> + headers + .xssProtection(xssProtection -> + xssProtection + .block(false) + ) + ); + } } ---- @@ -625,13 +655,18 @@ Similarly, you can enable the CSP header using Java configuration as shown below public class WebSecurityConfig extends WebSecurityConfigurerAdapter { -@Override -protected void configure(HttpSecurity http) throws Exception { - http - // ... - .headers() - .contentSecurityPolicy("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/"); -} + @Override + protected void configure(HttpSecurity http) throws Exception { + http + // ... + .headers(headers -> + headers + .contentSecurityPolicy(csp -> + csp + .policyDirectives("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/") + ) + ); + } } ---- @@ -643,14 +678,19 @@ To enable the CSP _'report-only'_ header, provide the following Java configurati public class WebSecurityConfig extends WebSecurityConfigurerAdapter { -@Override -protected void configure(HttpSecurity http) throws Exception { - http - // ... - .headers() - .contentSecurityPolicy("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/") - .reportOnly(); -} + @Override + protected void configure(HttpSecurity http) throws Exception { + http + // ... + .headers(headers -> + headers + .contentSecurityPolicy(csp -> + csp + .policyDirectives("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/") + .reportOnly() + ) + ); + } } ---- @@ -707,13 +747,18 @@ Similarly, you can enable the Referrer Policy header using Java configuration as public class WebSecurityConfig extends WebSecurityConfigurerAdapter { -@Override -protected void configure(HttpSecurity http) throws Exception { - http - // ... - .headers() - .referrerPolicy(ReferrerPolicy.SAME_ORIGIN); -} + @Override + protected void configure(HttpSecurity http) throws Exception { + http + // ... + .headers(headers -> + headers + .referrerPolicy(referrerPolicy -> + referrerPolicy + .policy(ReferrerPolicy.SAME_ORIGIN) + ) + ); + } } ---- @@ -757,13 +802,15 @@ Similarly, you can enable the Feature Policy header using Java configuration as public class WebSecurityConfig extends WebSecurityConfigurerAdapter { -@Override -protected void configure(HttpSecurity http) throws Exception { - http - // ... - .headers() - .featurePolicy("geolocation 'self'"); -} + @Override + protected void configure(HttpSecurity http) throws Exception { + http + // ... + .headers(headers -> + headers + .featurePolicy("geolocation 'self'") + ); + } } ---- @@ -804,13 +851,15 @@ Similarly, the headers could be added to the response using Java Configuration a public class WebSecurityConfig extends WebSecurityConfigurerAdapter { -@Override -protected void configure(HttpSecurity http) throws Exception { - http - // ... - .headers() - .addHeaderWriter(new StaticHeadersWriter("X-Custom-Security-Header","header-value")); -} + @Override + protected void configure(HttpSecurity http) throws Exception { + http + // ... + .headers(headers -> + headers + .addHeaderWriter(new StaticHeadersWriter("X-Custom-Security-Header","header-value")) + ); + } } ---- @@ -849,13 +898,15 @@ We could also restrict framing of content to the same origin with Java configura public class WebSecurityConfig extends WebSecurityConfigurerAdapter { -@Override -protected void configure(HttpSecurity http) throws Exception { - http - // ... - .headers() - .addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN)); -} + @Override + protected void configure(HttpSecurity http) throws Exception { + http + // ... + .headers(headers -> + headers + .addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN)) + ); + } } ---- @@ -903,17 +954,21 @@ We could also prevent framing of content to the log in page using java configura public class WebSecurityConfig extends WebSecurityConfigurerAdapter { -@Override -protected void configure(HttpSecurity http) throws Exception { - RequestMatcher matcher = new AntPathRequestMatcher("/login"); - DelegatingRequestMatcherHeaderWriter headerWriter = - new DelegatingRequestMatcherHeaderWriter(matcher,new XFrameOptionsHeaderWriter()); - http - // ... - .headers() - .frameOptions().disabled() - .addHeaderWriter(headerWriter); -} + @Override + protected void configure(HttpSecurity http) throws Exception { + RequestMatcher matcher = new AntPathRequestMatcher("/login"); + DelegatingRequestMatcherHeaderWriter headerWriter = + new DelegatingRequestMatcherHeaderWriter(matcher,new XFrameOptionsHeaderWriter()); + http + // ... + .headers(headers -> + headers + .frameOptions(frameOptions -> + frameOptions.disable() + ) + .addHeaderWriter(headerWriter) + ); + } } ---- diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/web/websocket.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/web/websocket.adoc index 0553b460234..8dd70cb2c61 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/web/websocket.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/web/websocket.adoc @@ -323,9 +323,13 @@ public class WebSecurityConfig extends protected void configure(HttpSecurity http) throws Exception { http // ... - .headers() - .frameOptions() - .sameOrigin(); + .headers(headers -> + headers + .frameOptions(frameOptions -> + frameOptions + .sameOrigin() + ) + ); } } ---- @@ -356,18 +360,23 @@ public class WebSecurityConfig @Override protected void configure(HttpSecurity http) throws Exception { - http - .csrf() - // ignore our stomp endpoints since they are protected using Stomp headers - .ignoringAntMatchers("/chat/**") - .and() - .headers() - // allow same origin to frame our site to support iframe SockJS - .frameOptions().sameOrigin() - .and() - .authorizeRequests() - + .csrf(csrf -> + csrf + // ignore our stomp endpoints since they are protected using Stomp headers + .ignoringAntMatchers("/chat/**") + ) + .headers(headers -> + headers + // allow same origin to frame our site to support iframe SockJS + .frameOptions(frameOptions -> + frameOptions + .sameOrigin() + ) + ) + .authorizeRequests(authorizeRequests -> + ... + ) ... ---- diff --git a/samples/boot/helloworld/src/main/java/org/springframework/security/samples/config/SecurityConfig.java b/samples/boot/helloworld/src/main/java/org/springframework/security/samples/config/SecurityConfig.java index e6d09b2448c..7bb3d9e64d4 100644 --- a/samples/boot/helloworld/src/main/java/org/springframework/security/samples/config/SecurityConfig.java +++ b/samples/boot/helloworld/src/main/java/org/springframework/security/samples/config/SecurityConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2019 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. @@ -32,11 +32,16 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .authorizeRequests() - .antMatchers("/css/**", "/index").permitAll() - .antMatchers("/user/**").hasRole("USER") - .and() - .formLogin().loginPage("/login").failureUrl("/login-error"); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/css/**", "/index").permitAll() + .antMatchers("/user/**").hasRole("USER") + ) + .formLogin(formLogin -> + formLogin + .loginPage("/login") + .failureUrl("/login-error") + ); } // @formatter:on diff --git a/samples/boot/oauth2login/src/integration-test/java/org/springframework/security/samples/OAuth2LoginApplicationTests.java b/samples/boot/oauth2login/src/integration-test/java/org/springframework/security/samples/OAuth2LoginApplicationTests.java index d176bd694ca..848622a0470 100644 --- a/samples/boot/oauth2login/src/integration-test/java/org/springframework/security/samples/OAuth2LoginApplicationTests.java +++ b/samples/boot/oauth2login/src/integration-test/java/org/springframework/security/samples/OAuth2LoginApplicationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -358,15 +358,21 @@ public static class SecurityTestConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .authorizeRequests() - .anyRequest().authenticated() - .and() - .oauth2Login() - .tokenEndpoint() - .accessTokenResponseClient(this.mockAccessTokenResponseClient()) - .and() - .userInfoEndpoint() - .userService(this.mockUserService()); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .oauth2Login(oauth2Login -> + oauth2Login + .tokenEndpoint(tokenEndpoint -> + tokenEndpoint + .accessTokenResponseClient(this.mockAccessTokenResponseClient()) + ) + .userInfoEndpoint(userInfoEndpoint -> + userInfoEndpoint + .userService(this.mockUserService()) + ) + ); } // @formatter:on diff --git a/samples/boot/oauth2resourceserver-jwe/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java b/samples/boot/oauth2resourceserver-jwe/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java index 53e0a134a00..827a9628f7a 100644 --- a/samples/boot/oauth2resourceserver-jwe/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java +++ b/samples/boot/oauth2resourceserver-jwe/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -46,6 +46,8 @@ import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; +import static org.springframework.security.config.Customizer.withDefaults; + /** * @author Josh Cummings */ @@ -66,12 +68,15 @@ public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfig protected void configure(HttpSecurity http) throws Exception { // @formatter:off http - .authorizeRequests() - .antMatchers("/message/**").hasAuthority("SCOPE_message:read") - .anyRequest().authenticated() - .and() - .oauth2ResourceServer() - .jwt(); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/message/**").hasAuthority("SCOPE_message:read") + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2ResourceServer -> + oauth2ResourceServer + .jwt(withDefaults()) + ); // @formatter:on } diff --git a/samples/boot/oauth2resourceserver-multitenancy/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java b/samples/boot/oauth2resourceserver-multitenancy/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java index b8bd55b4671..1c65ea247f9 100644 --- a/samples/boot/oauth2resourceserver-multitenancy/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java +++ b/samples/boot/oauth2resourceserver-multitenancy/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java @@ -51,12 +51,15 @@ public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfig protected void configure(HttpSecurity http) throws Exception { // @formatter:off http - .authorizeRequests() - .antMatchers("/**/message/**").hasAuthority("SCOPE_message:read") - .anyRequest().authenticated() - .and() - .oauth2ResourceServer() - .authenticationManagerResolver(multitenantAuthenticationManager()); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/**/message/**").hasAuthority("SCOPE_message:read") + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2ResourceServer -> + oauth2ResourceServer + .authenticationManagerResolver(multitenantAuthenticationManager()) + ); // @formatter:on } diff --git a/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java b/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java index 03505b3d7e0..ae7cf6736c6 100644 --- a/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java +++ b/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java @@ -34,14 +34,19 @@ public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfig protected void configure(HttpSecurity http) throws Exception { // @formatter:off http - .authorizeRequests() - .mvcMatchers("/message/**").hasAuthority("SCOPE_message:read") - .anyRequest().authenticated() - .and() - .oauth2ResourceServer() - .opaqueToken() - .introspectionUri(this.introspectionUri) - .introspectionClientCredentials(this.clientId, this.clientSecret); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .mvcMatchers("/message/**").hasAuthority("SCOPE_message:read") + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2ResourceServer -> + oauth2ResourceServer + .opaqueToken(opaqueToken -> + opaqueToken + .introspectionUri(this.introspectionUri) + .introspectionClientCredentials(this.clientId, this.clientSecret) + ) + ); // @formatter:on } } diff --git a/samples/boot/oauth2resourceserver-static/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java b/samples/boot/oauth2resourceserver-static/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java index 135b6b5b247..67d74d9e256 100644 --- a/samples/boot/oauth2resourceserver-static/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java +++ b/samples/boot/oauth2resourceserver-static/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java @@ -38,13 +38,17 @@ public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfig protected void configure(HttpSecurity http) throws Exception { // @formatter:off http - .authorizeRequests() - .antMatchers("/message/**").hasAuthority("SCOPE_message:read") - .anyRequest().authenticated() - .and() - .oauth2ResourceServer() - .jwt() - .decoder(jwtDecoder()); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/message/**").hasAuthority("SCOPE_message:read") + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2ResourceServer -> + oauth2ResourceServer + .jwt(jwt -> + jwt.decoder(jwtDecoder()) + ) + ); // @formatter:on } diff --git a/samples/boot/oauth2resourceserver/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java b/samples/boot/oauth2resourceserver/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java index 70d5bc0b5bf..23c3a4a74c3 100644 --- a/samples/boot/oauth2resourceserver/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java +++ b/samples/boot/oauth2resourceserver/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -19,6 +19,8 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import static org.springframework.security.config.Customizer.withDefaults; + /** * @author Josh Cummings */ @@ -29,12 +31,15 @@ public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfig protected void configure(HttpSecurity http) throws Exception { // @formatter:off http - .authorizeRequests() - .antMatchers("/message/**").hasAuthority("SCOPE_message:read") - .anyRequest().authenticated() - .and() - .oauth2ResourceServer() - .jwt(); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/message/**").hasAuthority("SCOPE_message:read") + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2ResourceServer -> + oauth2ResourceServer + .jwt(withDefaults()) + ); // @formatter:on } } diff --git a/samples/boot/oauth2webclient/src/main/java/sample/config/SecurityConfig.java b/samples/boot/oauth2webclient/src/main/java/sample/config/SecurityConfig.java index de8b5b5a6f8..80cae2f6582 100644 --- a/samples/boot/oauth2webclient/src/main/java/sample/config/SecurityConfig.java +++ b/samples/boot/oauth2webclient/src/main/java/sample/config/SecurityConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -24,6 +24,8 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import static org.springframework.security.config.Customizer.withDefaults; + /** * @author Joe Grandja */ @@ -33,15 +35,14 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .authorizeRequests() - .mvcMatchers("/", "/public/**").permitAll() - .anyRequest().authenticated() - .and() - .formLogin() - .and() - .oauth2Login() - .and() - .oauth2Client(); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .mvcMatchers("/", "/public/**").permitAll() + .anyRequest().authenticated() + ) + .formLogin(withDefaults()) + .oauth2Login(withDefaults()) + .oauth2Client(withDefaults()); } @Bean diff --git a/samples/javaconfig/concurrency/src/main/java/org/springframework/security/samples/config/SecurityConfig.java b/samples/javaconfig/concurrency/src/main/java/org/springframework/security/samples/config/SecurityConfig.java index d2c9501cf70..d71dd73fc79 100644 --- a/samples/javaconfig/concurrency/src/main/java/org/springframework/security/samples/config/SecurityConfig.java +++ b/samples/javaconfig/concurrency/src/main/java/org/springframework/security/samples/config/SecurityConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2019 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. @@ -22,6 +22,8 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import static org.springframework.security.config.Customizer.withDefaults; + @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @@ -40,14 +42,19 @@ public void configureGlobal( protected void configure( HttpSecurity http) throws Exception { http - .authorizeRequests() - .anyRequest().authenticated() - .and() - .formLogin() - .and() - .sessionManagement() - .maximumSessions(1) - .expiredUrl("/login?expired"); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .formLogin(withDefaults()) + .sessionManagement(sessionManagement -> + sessionManagement + .sessionConcurrency(sessionConcurrency -> + sessionConcurrency + .maximumSessions(1) + .expiredUrl("/login?expired") + ) + ); } // @formatter:on } diff --git a/samples/javaconfig/form/src/main/java/org/springframework/security/samples/config/SecurityConfig.java b/samples/javaconfig/form/src/main/java/org/springframework/security/samples/config/SecurityConfig.java index e2ec33a61d5..5b002bade0d 100644 --- a/samples/javaconfig/form/src/main/java/org/springframework/security/samples/config/SecurityConfig.java +++ b/samples/javaconfig/form/src/main/java/org/springframework/security/samples/config/SecurityConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2019 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. @@ -29,16 +29,20 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .authorizeRequests() - .antMatchers("/resources/**").permitAll() - .anyRequest().authenticated() - .and() - .formLogin() - .loginPage("/login") - .permitAll() - .and() - .logout() - .permitAll(); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/resources/**").permitAll() + .anyRequest().authenticated() + ) + .formLogin(formLogin -> + formLogin + .loginPage("/login") + .permitAll() + ) + .logout(logout -> + logout + .permitAll() + ); } // @formatter:on diff --git a/samples/javaconfig/openid/src/main/java/org/springframework/security/samples/config/SecurityConfig.java b/samples/javaconfig/openid/src/main/java/org/springframework/security/samples/config/SecurityConfig.java index 0b21d482e9f..dc9134832c8 100644 --- a/samples/javaconfig/openid/src/main/java/org/springframework/security/samples/config/SecurityConfig.java +++ b/samples/javaconfig/openid/src/main/java/org/springframework/security/samples/config/SecurityConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2019 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. @@ -26,46 +26,71 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .authorizeRequests() - .antMatchers("/resources/**").permitAll() - .anyRequest().authenticated() - .and() - .openidLogin() - .loginPage("/login") - .permitAll() - .authenticationUserDetailsService(new CustomUserDetailsService()) - .attributeExchange("https://www.google.com/.*") - .attribute("email") - .type("https://axschema.org/contact/email") - .required(true) - .and() - .attribute("firstname") - .type("https://axschema.org/namePerson/first") - .required(true) - .and() - .attribute("lastname") - .type("https://axschema.org/namePerson/last") - .required(true) - .and() - .and() - .attributeExchange(".*yahoo.com.*") - .attribute("email") - .type("https://axschema.org/contact/email") - .required(true) - .and() - .attribute("fullname") - .type("https://axschema.org/namePerson") - .required(true) - .and() - .and() - .attributeExchange(".*myopenid.com.*") - .attribute("email") - .type("https://schema.openid.net/contact/email") - .required(true) - .and() - .attribute("fullname") - .type("https://schema.openid.net/namePerson") - .required(true); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/resources/**").permitAll() + .anyRequest().authenticated() + ) + .openidLogin(openidLogin -> + openidLogin + .loginPage("/login") + .permitAll() + .authenticationUserDetailsService(new CustomUserDetailsService()) + .attributeExchange(googleExchange -> + googleExchange + .identifierPattern("https://www.google.com/.*") + .attribute(emailAttribute -> + emailAttribute + .name("email") + .type("https://axschema.org/contact/email") + .required(true) + ) + .attribute(firstnameAttribute -> + firstnameAttribute + .name("firstname") + .type("https://axschema.org/namePerson/first") + .required(true) + ) + .attribute(lastnameAttribute -> + lastnameAttribute + .name("lastname") + .type("https://axschema.org/namePerson/last") + .required(true) + ) + ) + .attributeExchange(yahooExchange -> + yahooExchange + .identifierPattern(".*yahoo.com.*") + .attribute(emailAttribute -> + emailAttribute + .name("email") + .type("https://axschema.org/contact/email") + .required(true) + ) + .attribute(fullnameAttribute -> + fullnameAttribute + .name("fullname") + .type("https://axschema.org/namePerson") + .required(true) + ) + ) + .attributeExchange(myopenidExchange -> + myopenidExchange + .identifierPattern(".*myopenid.com.*") + .attribute(emailAttribute -> + emailAttribute + .name("email") + .type("https://schema.openid.net/contact/email") + .required(true) + ) + .attribute(fullnameAttribute -> + fullnameAttribute + .name("fullname") + .type("https://schema.openid.net/namePerson") + .required(true) + ) + ) + ); } // @formatter:on } diff --git a/samples/javaconfig/preauth/src/main/java/org/springframework/security/samples/config/SecurityConfig.java b/samples/javaconfig/preauth/src/main/java/org/springframework/security/samples/config/SecurityConfig.java index 97b95313b28..1544f08b3b6 100644 --- a/samples/javaconfig/preauth/src/main/java/org/springframework/security/samples/config/SecurityConfig.java +++ b/samples/javaconfig/preauth/src/main/java/org/springframework/security/samples/config/SecurityConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2019 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. @@ -26,12 +26,15 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http - .authorizeRequests() - .antMatchers("/login", "/resources/**").permitAll() - .anyRequest().authenticated() - .and() - .jee() - .mappableRoles("USER", "ADMIN"); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/login", "/resources/**").permitAll() + .anyRequest().authenticated() + ) + .jee(jee -> + jee + .mappableRoles("USER", "ADMIN") + ); } // @formatter:on } diff --git a/samples/javaconfig/rememberme/src/main/java/org/springframework/security/samples/config/SecurityConfig.java b/samples/javaconfig/rememberme/src/main/java/org/springframework/security/samples/config/SecurityConfig.java index 20e1e09a890..c2e36065a0b 100644 --- a/samples/javaconfig/rememberme/src/main/java/org/springframework/security/samples/config/SecurityConfig.java +++ b/samples/javaconfig/rememberme/src/main/java/org/springframework/security/samples/config/SecurityConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2019 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. @@ -21,6 +21,8 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import static org.springframework.security.config.Customizer.withDefaults; + @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @@ -39,15 +41,17 @@ public void configureGlobal( @Override protected void configure(HttpSecurity http) throws Exception { http - .authorizeRequests() - .antMatchers("/resources/**").permitAll() - .anyRequest().authenticated() - .and() - .formLogin() - .loginPage("/login") - .permitAll() - .and() - .rememberMe(); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/resources/**").permitAll() + .anyRequest().authenticated() + ) + .formLogin(formLogin -> + formLogin + .loginPage("/login") + .permitAll() + ) + .rememberMe(withDefaults()); } // @formatter:on } diff --git a/samples/javaconfig/x509/src/main/java/org/springframework/security/samples/config/SecurityConfig.java b/samples/javaconfig/x509/src/main/java/org/springframework/security/samples/config/SecurityConfig.java index ef7850625f0..1fa5356df43 100644 --- a/samples/javaconfig/x509/src/main/java/org/springframework/security/samples/config/SecurityConfig.java +++ b/samples/javaconfig/x509/src/main/java/org/springframework/security/samples/config/SecurityConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2019 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. @@ -21,6 +21,8 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import static org.springframework.security.config.Customizer.withDefaults; + @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @@ -40,10 +42,11 @@ public void configureGlobal(AuthenticationManagerBuilder auth) @Override protected void configure(HttpSecurity http) throws Exception { http - .authorizeRequests() - .anyRequest().authenticated() - .and() - .x509(); + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().authenticated() + ) + .x509(withDefaults()); } // @formatter:on } diff --git a/web/src/main/java/org/springframework/security/web/header/writers/ContentSecurityPolicyHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/ContentSecurityPolicyHeaderWriter.java index 12ad08e9785..4ae86198f36 100644 --- a/web/src/main/java/org/springframework/security/web/header/writers/ContentSecurityPolicyHeaderWriter.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/ContentSecurityPolicyHeaderWriter.java @@ -81,10 +81,20 @@ public final class ContentSecurityPolicyHeaderWriter implements HeaderWriter { private static final String CONTENT_SECURITY_POLICY_REPORT_ONLY_HEADER = "Content-Security-Policy-Report-Only"; + private static final String DEFAULT_SRC_SELF_POLICY = "default-src 'self'"; + private String policyDirectives; private boolean reportOnly; + /** + * Creates a new instance. Default value: default-src 'self' + */ + public ContentSecurityPolicyHeaderWriter() { + setPolicyDirectives(DEFAULT_SRC_SELF_POLICY); + this.reportOnly = false; + } + /** * Creates a new instance * diff --git a/web/src/test/java/org/springframework/security/web/header/writers/ContentSecurityPolicyHeaderWriterTests.java b/web/src/test/java/org/springframework/security/web/header/writers/ContentSecurityPolicyHeaderWriterTests.java index 5ff062f4342..2f3c7914e8d 100644 --- a/web/src/test/java/org/springframework/security/web/header/writers/ContentSecurityPolicyHeaderWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/header/writers/ContentSecurityPolicyHeaderWriterTests.java @@ -43,6 +43,15 @@ public void setup() { writer = new ContentSecurityPolicyHeaderWriter(DEFAULT_POLICY_DIRECTIVES); } + @Test + public void writeHeadersWhenNoPolicyDirectivesThenUsesDefault() { + ContentSecurityPolicyHeaderWriter noPolicyWriter = new ContentSecurityPolicyHeaderWriter(); + noPolicyWriter.writeHeaders(request, response); + + assertThat(response.getHeaderNames()).hasSize(1); + assertThat(response.getHeader("Content-Security-Policy")).isEqualTo(DEFAULT_POLICY_DIRECTIVES); + } + @Test public void writeHeadersContentSecurityPolicyDefault() { writer.writeHeaders(request, response); @@ -64,6 +73,16 @@ public void writeHeadersContentSecurityPolicyCustom() { assertThat(response.getHeader("Content-Security-Policy")).isEqualTo(policyDirectives); } + @Test + public void writeHeadersWhenNoPolicyDirectivesReportOnlyThenUsesDefault() { + ContentSecurityPolicyHeaderWriter noPolicyWriter = new ContentSecurityPolicyHeaderWriter(); + writer.setReportOnly(true); + noPolicyWriter.writeHeaders(request, response); + + assertThat(response.getHeaderNames()).hasSize(1); + assertThat(response.getHeader("Content-Security-Policy")).isEqualTo(DEFAULT_POLICY_DIRECTIVES); + } + @Test public void writeHeadersContentSecurityPolicyReportOnlyDefault() { writer.setReportOnly(true);