Add CorsFilter support
This commit is contained in:
@@ -68,6 +68,7 @@ public abstract class Elements {
|
||||
public static final String DEBUG = "debug";
|
||||
public static final String HTTP_FIREWALL = "http-firewall";
|
||||
public static final String HEADERS = "headers";
|
||||
public static final String CORS = "cors";
|
||||
public static final String CSRF = "csrf";
|
||||
|
||||
public static final String WEBSOCKET_MESSAGE_BROKER = "websocket-message-broker";
|
||||
|
||||
+3
@@ -44,6 +44,7 @@ import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
|
||||
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
|
||||
import org.springframework.security.web.session.ConcurrentSessionFilter;
|
||||
import org.springframework.security.web.session.SessionManagementFilter;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
/**
|
||||
* An internal use only {@link Comparator} that sorts the Security {@link Filter}
|
||||
@@ -70,6 +71,8 @@ final class FilterComparator implements Comparator<Filter>, Serializable {
|
||||
order += STEP;
|
||||
put(HeaderWriterFilter.class, order);
|
||||
order += STEP;
|
||||
put(CorsFilter.class, order);
|
||||
order += STEP;
|
||||
put(CsrfFilter.class, order);
|
||||
order += STEP;
|
||||
put(LogoutFilter.class, order);
|
||||
|
||||
+17
@@ -38,6 +38,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.ChannelSecurityConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.CorsConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
|
||||
@@ -69,6 +70,9 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
|
||||
/**
|
||||
* A {@link HttpSecurity} is similar to Spring Security's XML <http> element in the
|
||||
@@ -317,6 +321,19 @@ public final class HttpSecurity extends
|
||||
return getOrApply(new HeadersConfigurer<HttpSecurity>());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return the {@link CorsConfigurer} for customizations
|
||||
* @throws Exception
|
||||
*/
|
||||
public CorsConfigurer<HttpSecurity> cors() throws Exception {
|
||||
return getOrApply(new CorsConfigurer<HttpSecurity>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows configuring of Session Management.
|
||||
*
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
|
||||
/**
|
||||
* Adds {@link CorsFilter} to the Spring Security filter chain. 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.
|
||||
*
|
||||
* @param <H> the builder to return.
|
||||
* @author Rob Winch
|
||||
* @since 4.1.1
|
||||
*/
|
||||
public class CorsConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
extends AbstractHttpConfigurer<CorsConfigurer<H>, H> {
|
||||
|
||||
private static final String HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector";
|
||||
private static final String CORS_CONFIGURATION_SOURCE_BEAN_NAME = "corsConfigurationSource";
|
||||
private static final String CORS_FILTER_BEAN_NAME = "corsFilter";
|
||||
|
||||
private CorsConfigurationSource configurationSource;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @see HttpSecurity#cors()
|
||||
*/
|
||||
public CorsConfigurer() {
|
||||
}
|
||||
|
||||
public CorsConfigurer<H> configurationSource(
|
||||
CorsConfigurationSource configurationSource) {
|
||||
this.configurationSource = configurationSource;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(H http) throws Exception {
|
||||
ApplicationContext context = http.getSharedObject(ApplicationContext.class);
|
||||
|
||||
CorsFilter corsFilter = getCorsFilter(context);
|
||||
if (corsFilter == null) {
|
||||
throw new IllegalStateException(
|
||||
"Please configure either a " + CORS_FILTER_BEAN_NAME + " bean or a "
|
||||
+ CORS_CONFIGURATION_SOURCE_BEAN_NAME + "bean.");
|
||||
}
|
||||
http.addFilter(corsFilter);
|
||||
}
|
||||
|
||||
private CorsFilter getCorsFilter(ApplicationContext context) {
|
||||
if (this.configurationSource != null) {
|
||||
return new CorsFilter(this.configurationSource);
|
||||
}
|
||||
|
||||
boolean containsCorsFilter = context
|
||||
.containsBeanDefinition(CORS_FILTER_BEAN_NAME);
|
||||
if (containsCorsFilter) {
|
||||
return context.getBean(CORS_FILTER_BEAN_NAME, CorsFilter.class);
|
||||
}
|
||||
|
||||
boolean containsCorsSource = context
|
||||
.containsBean(CORS_CONFIGURATION_SOURCE_BEAN_NAME);
|
||||
if (containsCorsSource) {
|
||||
CorsConfigurationSource configurationSource = context.getBean(
|
||||
CORS_CONFIGURATION_SOURCE_BEAN_NAME, CorsConfigurationSource.class);
|
||||
return new CorsFilter(configurationSource);
|
||||
}
|
||||
|
||||
boolean mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR,
|
||||
context.getClassLoader());
|
||||
if (mvcPresent) {
|
||||
return MvcCorsFilter.getMvcCorsFilter(context);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static class MvcCorsFilter {
|
||||
/**
|
||||
* This needs to be isolated into a separate class as Spring MVC is an optional
|
||||
* dependency and will potentially cause ClassLoading issues
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
private static CorsFilter getMvcCorsFilter(ApplicationContext context) {
|
||||
HandlerMappingIntrospector mappingIntrospector = new HandlerMappingIntrospector(
|
||||
context);
|
||||
return new CorsFilter(mappingIntrospector);
|
||||
}
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.config.http;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
/**
|
||||
* Parser for the {@code CorsFilter}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 4.1.1
|
||||
*/
|
||||
public class CorsBeanDefinitionParser {
|
||||
private static final String HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector";
|
||||
|
||||
private static final String ATT_SOURCE = "configuration-source-ref";
|
||||
private static final String ATT_REF = "ref";
|
||||
|
||||
public BeanMetadataElement parse(Element element, ParserContext parserContext) {
|
||||
if(element == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String filterRef = element.getAttribute(ATT_REF);
|
||||
if(StringUtils.hasText(filterRef)) {
|
||||
return new RuntimeBeanReference(filterRef);
|
||||
}
|
||||
|
||||
BeanMetadataElement configurationSource = getSource(element, parserContext);
|
||||
if(configurationSource == null) {
|
||||
throw new BeanCreationException("Could not create CorsFilter");
|
||||
}
|
||||
|
||||
BeanDefinitionBuilder filterBldr = BeanDefinitionBuilder.rootBeanDefinition(CorsFilter.class);
|
||||
filterBldr.addConstructorArgValue(configurationSource);
|
||||
return filterBldr.getBeanDefinition();
|
||||
}
|
||||
|
||||
public BeanMetadataElement getSource(Element element, ParserContext parserContext) {
|
||||
String configurationSourceRef = element.getAttribute(ATT_SOURCE);
|
||||
if (StringUtils.hasText(configurationSourceRef)) {
|
||||
return new RuntimeBeanReference(configurationSourceRef);
|
||||
}
|
||||
|
||||
boolean mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR,
|
||||
getClass().getClassLoader());
|
||||
if(!mvcPresent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
BeanDefinitionBuilder configurationSourceBldr = BeanDefinitionBuilder.rootBeanDefinition(HANDLER_MAPPING_INTROSPECTOR);
|
||||
configurationSourceBldr.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
|
||||
return configurationSourceBldr.getBeanDefinition();
|
||||
}
|
||||
}
|
||||
+11
@@ -127,6 +127,7 @@ class HttpConfigurationBuilder {
|
||||
private BeanReference fsi;
|
||||
private BeanReference requestCache;
|
||||
private BeanDefinition addHeadersFilter;
|
||||
private BeanMetadataElement corsFilter;
|
||||
private BeanDefinition csrfFilter;
|
||||
private BeanMetadataElement csrfLogoutHandler;
|
||||
private BeanMetadataElement csrfAuthStrategy;
|
||||
@@ -176,6 +177,7 @@ class HttpConfigurationBuilder {
|
||||
createChannelProcessingFilter();
|
||||
createFilterSecurityInterceptor(authenticationManager);
|
||||
createAddHeadersFilter();
|
||||
createCorsFilter();
|
||||
}
|
||||
|
||||
private SessionCreationPolicy createPolicy(String createSession) {
|
||||
@@ -737,6 +739,11 @@ class HttpConfigurationBuilder {
|
||||
private void createAddHeadersFilter() {
|
||||
Element elmt = DomUtils.getChildElementByTagName(httpElt, Elements.HEADERS);
|
||||
this.addHeadersFilter = new HeadersBeanDefinitionParser().parse(elmt, pc);
|
||||
}
|
||||
|
||||
private void createCorsFilter() {
|
||||
Element elmt = DomUtils.getChildElementByTagName(this.httpElt, Elements.CORS);
|
||||
this.corsFilter = new CorsBeanDefinitionParser().parse(elmt, this.pc);
|
||||
|
||||
}
|
||||
|
||||
@@ -808,6 +815,10 @@ class HttpConfigurationBuilder {
|
||||
filters.add(new OrderDecorator(requestCacheAwareFilter, REQUEST_CACHE_FILTER));
|
||||
}
|
||||
|
||||
if (this.corsFilter != null) {
|
||||
filters.add(new OrderDecorator(this.corsFilter, CORS_FILTER));
|
||||
}
|
||||
|
||||
if (addHeadersFilter != null) {
|
||||
filters.add(new OrderDecorator(addHeadersFilter, HEADERS_FILTER));
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.springframework.security.web.context.request.async.WebAsyncManagerInt
|
||||
enum SecurityFilters {
|
||||
FIRST(Integer.MIN_VALUE), CHANNEL_FILTER, SECURITY_CONTEXT_FILTER, CONCURRENT_SESSION_FILTER,
|
||||
/** {@link WebAsyncManagerIntegrationFilter} */
|
||||
WEB_ASYNC_MANAGER_FILTER, HEADERS_FILTER, CSRF_FILTER, LOGOUT_FILTER, X509_FILTER, PRE_AUTH_FILTER, CAS_FILTER, FORM_LOGIN_FILTER, OPENID_FILTER, LOGIN_PAGE_FILTER, DIGEST_AUTH_FILTER, BASIC_AUTH_FILTER, REQUEST_CACHE_FILTER, SERVLET_API_SUPPORT_FILTER, JAAS_API_SUPPORT_FILTER, REMEMBER_ME_FILTER, ANONYMOUS_FILTER, SESSION_MANAGEMENT_FILTER, EXCEPTION_TRANSLATION_FILTER, FILTER_SECURITY_INTERCEPTOR, SWITCH_USER_FILTER, LAST(
|
||||
WEB_ASYNC_MANAGER_FILTER, HEADERS_FILTER, CORS_FILTER, CSRF_FILTER, LOGOUT_FILTER, X509_FILTER, PRE_AUTH_FILTER, CAS_FILTER, FORM_LOGIN_FILTER, OPENID_FILTER, LOGIN_PAGE_FILTER, DIGEST_AUTH_FILTER, BASIC_AUTH_FILTER, REQUEST_CACHE_FILTER, SERVLET_API_SUPPORT_FILTER, JAAS_API_SUPPORT_FILTER, REMEMBER_ME_FILTER, ANONYMOUS_FILTER, SESSION_MANAGEMENT_FILTER, EXCEPTION_TRANSLATION_FILTER, FILTER_SECURITY_INTERCEPTOR, SWITCH_USER_FILTER, LAST(
|
||||
Integer.MAX_VALUE);
|
||||
|
||||
private static final int INTERVAL = 100;
|
||||
|
||||
+12
-3
@@ -303,7 +303,7 @@ http-firewall =
|
||||
|
||||
http =
|
||||
## Container element for HTTP security configuration. Multiple elements can now be defined, each with a specific pattern to which the enclosed security configuration applies. A pattern can also be configured to bypass Spring Security's filters completely by setting the "security" attribute to "none".
|
||||
element http {http.attlist, (intercept-url* & access-denied-handler? & form-login? & openid-login? & x509? & jee? & http-basic? & logout? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache? & expression-handler? & headers? & csrf?) }
|
||||
element http {http.attlist, (intercept-url* & access-denied-handler? & form-login? & openid-login? & x509? & jee? & http-basic? & logout? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache? & expression-handler? & headers? & csrf? & cors?) }
|
||||
http.attlist &=
|
||||
## The request URL pattern which will be mapped to the filter chain created by this <http> element. If omitted, the filter chain will match all requests.
|
||||
attribute pattern {xsd:token}?
|
||||
@@ -771,12 +771,21 @@ hsts-options.attlist &=
|
||||
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
|
||||
attribute request-matcher-ref { xsd:token }?
|
||||
|
||||
cors =
|
||||
## Element for configuration of CorsFilter. If no CorsFilter or CorsConfigurationSource is specified a HandlerMappingIntrospector is used as the CorsConfigurationSource
|
||||
element cors { cors-options.attlist }
|
||||
cors-options.attlist &=
|
||||
ref?
|
||||
cors-options.attlist &=
|
||||
## Specifies a bean id that is a CorsConfigurationSource used to construct the CorsFilter to use
|
||||
attribute configuration-source-ref {xsd:token}?
|
||||
|
||||
hpkp =
|
||||
## Adds support for HTTP Public Key Pinning (HPKP).
|
||||
element hpkp {hpkp.pins,hpkp.attlist}
|
||||
hpkp.pins =
|
||||
## The list with pins
|
||||
element pins {hpkp.pin+}
|
||||
element pins {hpkp.pin+}
|
||||
hpkp.pin =
|
||||
## A pin is specified using the base64-encoded SPKI fingerprint as value and the cryptographic hash algorithm as attribute
|
||||
element pin {
|
||||
@@ -895,4 +904,4 @@ position =
|
||||
## The explicit position at which the custom-filter should be placed in the chain. Use if you are replacing a standard filter.
|
||||
attribute position {named-security-filter}
|
||||
|
||||
named-security-filter = "FIRST" | "CHANNEL_FILTER" | "SECURITY_CONTEXT_FILTER" | "CONCURRENT_SESSION_FILTER" | "WEB_ASYNC_MANAGER_FILTER" | "HEADERS_FILTER" | "CSRF_FILTER" | "LOGOUT_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_FILTER" | "FORM_LOGIN_FILTER" | "OPENID_FILTER" | "LOGIN_PAGE_FILTER" | "DIGEST_AUTH_FILTER" | "BASIC_AUTH_FILTER" | "REQUEST_CACHE_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "JAAS_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "SESSION_MANAGEMENT_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER" | "LAST"
|
||||
named-security-filter = "FIRST" | "CHANNEL_FILTER" | "SECURITY_CONTEXT_FILTER" | "CONCURRENT_SESSION_FILTER" | "WEB_ASYNC_MANAGER_FILTER" | "HEADERS_FILTER" | "CORS_FILTER" | "CSRF_FILTER" | "LOGOUT_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_FILTER" | "FORM_LOGIN_FILTER" | "OPENID_FILTER" | "LOGIN_PAGE_FILTER" | "DIGEST_AUTH_FILTER" | "BASIC_AUTH_FILTER" | "REQUEST_CACHE_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "JAAS_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "SESSION_MANAGEMENT_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER" | "LAST"
|
||||
|
||||
@@ -1111,6 +1111,7 @@
|
||||
</xs:element>
|
||||
<xs:element ref="security:headers"/>
|
||||
<xs:element ref="security:csrf"/>
|
||||
<xs:element ref="security:cors"/>
|
||||
</xs:choice>
|
||||
<xs:attributeGroup ref="security:http.attlist"/>
|
||||
</xs:complexType>
|
||||
@@ -2385,6 +2386,31 @@
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:attributeGroup>
|
||||
<xs:element name="cors">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Element for configuration of CorsFilter. If no CorsFilter or CorsConfigurationSource is
|
||||
specified a HandlerMappingIntrospector is used as the CorsConfigurationSource
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:attributeGroup ref="security:cors-options.attlist"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:attributeGroup name="cors-options.attlist">
|
||||
<xs:attribute name="ref" type="xs:token">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Defines a reference to a Spring bean Id.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="configuration-source-ref" type="xs:token">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies a bean id that is a CorsConfigurationSource used to construct the CorsFilter to
|
||||
use
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:attributeGroup>
|
||||
<xs:element name="hpkp">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Adds support for HTTP Public Key Pinning (HPKP).
|
||||
@@ -2716,6 +2742,7 @@
|
||||
<xs:enumeration value="CONCURRENT_SESSION_FILTER"/>
|
||||
<xs:enumeration value="WEB_ASYNC_MANAGER_FILTER"/>
|
||||
<xs:enumeration value="HEADERS_FILTER"/>
|
||||
<xs:enumeration value="CORS_FILTER"/>
|
||||
<xs:enumeration value="CSRF_FILTER"/>
|
||||
<xs:enumeration value="LOGOUT_FILTER"/>
|
||||
<xs:enumeration value="X509_FILTER"/>
|
||||
|
||||
+12
-9
@@ -17,20 +17,22 @@ package org.springframework.security.config.annotation;
|
||||
|
||||
import javax.servlet.Filter
|
||||
|
||||
import spock.lang.AutoCleanup
|
||||
import spock.lang.Specification
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException
|
||||
import org.springframework.context.ConfigurableApplicationContext
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.web.MockFilterChain
|
||||
import org.springframework.mock.web.MockHttpServletRequest
|
||||
import org.springframework.mock.web.MockHttpServletResponse
|
||||
import org.springframework.mock.web.MockServletContext
|
||||
import org.springframework.security.authentication.AuthenticationManager
|
||||
import org.springframework.security.authentication.AuthenticationProvider
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
|
||||
import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.authority.AuthorityUtils
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
@@ -40,11 +42,9 @@ import org.springframework.security.web.access.intercept.FilterSecurityIntercept
|
||||
import org.springframework.security.web.context.HttpRequestResponseHolder
|
||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository
|
||||
import org.springframework.security.web.csrf.CsrfToken
|
||||
import org.springframework.security.web.csrf.DefaultCsrfToken;
|
||||
import org.springframework.security.web.csrf.DefaultCsrfToken
|
||||
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository
|
||||
|
||||
import spock.lang.AutoCleanup
|
||||
import spock.lang.Specification
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -88,7 +88,10 @@ abstract class BaseSpringSpec extends Specification {
|
||||
}
|
||||
|
||||
def loadConfig(Class<?>... configs) {
|
||||
context = new AnnotationConfigApplicationContext(configs)
|
||||
context = new AnnotationConfigWebApplicationContext()
|
||||
context.register(configs)
|
||||
context.setServletContext(new MockServletContext())
|
||||
context.refresh()
|
||||
context
|
||||
}
|
||||
|
||||
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.config.annotation.web.configurers
|
||||
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.http.*
|
||||
import org.springframework.security.config.annotation.BaseSpringSpec
|
||||
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.web.bind.annotation.*
|
||||
import org.springframework.web.cors.CorsConfiguration
|
||||
import org.springframework.web.cors.CorsConfigurationSource
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
|
||||
import org.springframework.web.filter.CorsFilter
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
class CorsConfigurerTests extends BaseSpringSpec {
|
||||
|
||||
def "HandlerMappingIntrospector default"() {
|
||||
setup:
|
||||
loadConfig(DefaultCorsConfig)
|
||||
when:
|
||||
addCors()
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then:
|
||||
responseHeaders == ['X-Content-Type-Options':'nosniff',
|
||||
'X-Frame-Options':'DENY',
|
||||
'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
|
||||
'Expires' : '0',
|
||||
'Pragma':'no-cache',
|
||||
'X-XSS-Protection' : '1; mode=block']
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class DefaultCorsConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.cors()
|
||||
}
|
||||
}
|
||||
|
||||
def "HandlerMappingIntrospector explicit"() {
|
||||
setup:
|
||||
loadConfig(MvcCorsConfig)
|
||||
when:
|
||||
addCors()
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'Ensure we a CORS response w/ Spring Security headers too'
|
||||
responseHeaders['Access-Control-Allow-Origin']
|
||||
responseHeaders['X-Content-Type-Options']
|
||||
when:
|
||||
setupWeb()
|
||||
addCors(true)
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'Ensure we a CORS response w/ Spring Security headers too'
|
||||
responseHeaders['Access-Control-Allow-Origin']
|
||||
responseHeaders['X-Content-Type-Options']
|
||||
response.status == HttpServletResponse.SC_OK
|
||||
}
|
||||
|
||||
@EnableWebMvc
|
||||
@EnableWebSecurity
|
||||
static class MvcCorsConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.cors()
|
||||
}
|
||||
|
||||
@RestController
|
||||
@CrossOrigin(methods = [
|
||||
RequestMethod.GET, RequestMethod.POST
|
||||
])
|
||||
static class CorsController {
|
||||
@RequestMapping("/")
|
||||
String hello() {
|
||||
"Hello"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "CorsConfigurationSource"() {
|
||||
setup:
|
||||
loadConfig(ConfigSourceConfig)
|
||||
when:
|
||||
addCors()
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'Ensure we a CORS response w/ Spring Security headers too'
|
||||
responseHeaders['Access-Control-Allow-Origin']
|
||||
responseHeaders['X-Content-Type-Options']
|
||||
when:
|
||||
setupWeb()
|
||||
addCors(true)
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'Ensure we a CORS response w/ Spring Security headers too'
|
||||
responseHeaders['Access-Control-Allow-Origin']
|
||||
responseHeaders['X-Content-Type-Options']
|
||||
response.status == HttpServletResponse.SC_OK
|
||||
}
|
||||
|
||||
|
||||
@EnableWebSecurity
|
||||
static class ConfigSourceConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.cors()
|
||||
}
|
||||
|
||||
@Bean
|
||||
CorsConfigurationSource corsConfigurationSource() {
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource()
|
||||
source.registerCorsConfiguration("/**", new CorsConfiguration(allowedOrigins : ['*'], allowedMethods : [
|
||||
RequestMethod.GET.name(),
|
||||
RequestMethod.POST.name()
|
||||
]))
|
||||
source
|
||||
}
|
||||
}
|
||||
|
||||
def "CorsFilter"() {
|
||||
setup:
|
||||
loadConfig(CorsFilterConfig)
|
||||
when:
|
||||
addCors()
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'Ensure we a CORS response w/ Spring Security headers too'
|
||||
responseHeaders['Access-Control-Allow-Origin']
|
||||
responseHeaders['X-Content-Type-Options']
|
||||
when:
|
||||
setupWeb()
|
||||
addCors(true)
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'Ensure we a CORS response w/ Spring Security headers too'
|
||||
responseHeaders['Access-Control-Allow-Origin']
|
||||
responseHeaders['X-Content-Type-Options']
|
||||
response.status == HttpServletResponse.SC_OK
|
||||
}
|
||||
|
||||
|
||||
@EnableWebSecurity
|
||||
static class CorsFilterConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.cors()
|
||||
}
|
||||
|
||||
@Bean
|
||||
CorsFilter corsFilter() {
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource()
|
||||
source.registerCorsConfiguration("/**", new CorsConfiguration(allowedOrigins : ['*'], allowedMethods : [
|
||||
RequestMethod.GET.name(),
|
||||
RequestMethod.POST.name()
|
||||
]))
|
||||
new CorsFilter(source)
|
||||
}
|
||||
}
|
||||
|
||||
def addCors(boolean isPreflight=false) {
|
||||
request.addHeader(HttpHeaders.ORIGIN,"https://example.com")
|
||||
if(!isPreflight) {
|
||||
return
|
||||
}
|
||||
request.method = HttpMethod.OPTIONS.name()
|
||||
request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.name())
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.config.http
|
||||
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
import org.springframework.http.*
|
||||
import org.springframework.mock.web.*
|
||||
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import org.springframework.web.filter.CorsFilter
|
||||
import org.springframework.web.cors.CorsConfiguration
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
class HttpCorsConfigTests extends AbstractHttpConfigTests {
|
||||
MockHttpServletRequest request
|
||||
MockHttpServletResponse response
|
||||
MockFilterChain chain
|
||||
|
||||
def setup() {
|
||||
request = new MockHttpServletRequest(method:"GET")
|
||||
response = new MockHttpServletResponse()
|
||||
chain = new MockFilterChain()
|
||||
}
|
||||
|
||||
def "HandlerMappingIntrospector default"() {
|
||||
setup:
|
||||
xml.http('entry-point-ref' : 'ep') {
|
||||
'cors'()
|
||||
'intercept-url'(pattern:'/**', access: 'authenticated')
|
||||
}
|
||||
bean('ep', Http403ForbiddenEntryPoint)
|
||||
createAppContext()
|
||||
when:
|
||||
addCors()
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then:
|
||||
responseHeaders == ['X-Content-Type-Options':'nosniff',
|
||||
'X-Frame-Options':'DENY',
|
||||
'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
|
||||
'Expires' : '0',
|
||||
'Pragma':'no-cache',
|
||||
'X-XSS-Protection' : '1; mode=block']
|
||||
}
|
||||
|
||||
def "HandlerMappingIntrospector explicit"() {
|
||||
setup:
|
||||
xml.http('entry-point-ref' : 'ep') {
|
||||
'cors'()
|
||||
'intercept-url'(pattern:'/**', access: 'authenticated')
|
||||
}
|
||||
bean('ep', Http403ForbiddenEntryPoint)
|
||||
bean('controller', CorsController)
|
||||
xml.'mvc:annotation-driven'()
|
||||
createAppContext()
|
||||
when:
|
||||
addCors()
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'Ensure we a CORS response w/ Spring Security headers too'
|
||||
responseHeaders['Access-Control-Allow-Origin']
|
||||
responseHeaders['X-Content-Type-Options']
|
||||
when:
|
||||
setup()
|
||||
addCors(true)
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'Ensure we a CORS response w/ Spring Security headers too'
|
||||
responseHeaders['Access-Control-Allow-Origin']
|
||||
responseHeaders['X-Content-Type-Options']
|
||||
response.status == HttpServletResponse.SC_OK
|
||||
}
|
||||
|
||||
def "CorsConfigurationSource"() {
|
||||
setup:
|
||||
xml.http('entry-point-ref' : 'ep') {
|
||||
'cors'('configuration-source-ref':'ccs')
|
||||
'intercept-url'(pattern:'/**', access: 'authenticated')
|
||||
}
|
||||
bean('ep', Http403ForbiddenEntryPoint)
|
||||
bean('ccs', MyCorsConfigurationSource)
|
||||
createAppContext()
|
||||
when:
|
||||
addCors()
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'Ensure we a CORS response w/ Spring Security headers too'
|
||||
responseHeaders['Access-Control-Allow-Origin']
|
||||
responseHeaders['X-Content-Type-Options']
|
||||
when:
|
||||
setup()
|
||||
addCors(true)
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'Ensure we a CORS response w/ Spring Security headers too'
|
||||
responseHeaders['Access-Control-Allow-Origin']
|
||||
responseHeaders['X-Content-Type-Options']
|
||||
response.status == HttpServletResponse.SC_OK
|
||||
}
|
||||
|
||||
def "CorsFilter"() {
|
||||
setup:
|
||||
xml.http('entry-point-ref' : 'ep') {
|
||||
'cors'('ref' : 'cf')
|
||||
'intercept-url'(pattern:'/**', access: 'authenticated')
|
||||
}
|
||||
xml.'b:bean'(id: 'cf', 'class': CorsFilter.name) {
|
||||
'b:constructor-arg'(ref: 'ccs')
|
||||
}
|
||||
bean('ep', Http403ForbiddenEntryPoint)
|
||||
bean('ccs', MyCorsConfigurationSource)
|
||||
createAppContext()
|
||||
when:
|
||||
addCors()
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'Ensure we a CORS response w/ Spring Security headers too'
|
||||
responseHeaders['Access-Control-Allow-Origin']
|
||||
responseHeaders['X-Content-Type-Options']
|
||||
when:
|
||||
setup()
|
||||
addCors(true)
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'Ensure we a CORS response w/ Spring Security headers too'
|
||||
responseHeaders['Access-Control-Allow-Origin']
|
||||
responseHeaders['X-Content-Type-Options']
|
||||
response.status == HttpServletResponse.SC_OK
|
||||
}
|
||||
|
||||
def addCors(boolean isPreflight=false) {
|
||||
request.addHeader(HttpHeaders.ORIGIN,"https://example.com")
|
||||
if(!isPreflight) {
|
||||
return
|
||||
}
|
||||
request.method = HttpMethod.OPTIONS.name()
|
||||
request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.name())
|
||||
}
|
||||
|
||||
def getResponseHeaders() {
|
||||
def headers = [:]
|
||||
response.headerNames.each { name ->
|
||||
headers.put(name, response.getHeaderValues(name).join(','))
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
@RestController
|
||||
@CrossOrigin(methods = [
|
||||
RequestMethod.GET, RequestMethod.POST
|
||||
])
|
||||
static class CorsController {
|
||||
@RequestMapping("/")
|
||||
String hello() {
|
||||
"Hello"
|
||||
}
|
||||
}
|
||||
|
||||
static class MyCorsConfigurationSource extends UrlBasedCorsConfigurationSource {
|
||||
MyCorsConfigurationSource() {
|
||||
registerCorsConfiguration('/**', new CorsConfiguration(allowedOrigins : ['*'], allowedMethods : [
|
||||
RequestMethod.GET.name(),
|
||||
RequestMethod.POST.name()
|
||||
]))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user