diff --git a/config/src/main/java/org/springframework/security/config/Elements.java b/config/src/main/java/org/springframework/security/config/Elements.java index 8553acdb3bf..35a4d0fa171 100644 --- a/config/src/main/java/org/springframework/security/config/Elements.java +++ b/config/src/main/java/org/springframework/security/config/Elements.java @@ -70,6 +70,10 @@ public abstract class Elements { public static final String CORS = "cors"; public static final String CSRF = "csrf"; + public static final String OAUTH2_RESOURCE_SERVER = "oauth2-resource-server"; + public static final String JWT = "jwt"; + public static final String OPAQUE_TOKEN = "opaque-token"; + public static final String WEBSOCKET_MESSAGE_BROKER = "websocket-message-broker"; public static final String INTERCEPT_MESSAGE = "intercept-message"; diff --git a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java index eedf87ad53a..fcfcbb5af18 100644 --- a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java @@ -15,8 +15,18 @@ */ package org.springframework.security.config.http; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import javax.servlet.http.HttpServletRequest; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Element; + import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanReference; @@ -36,7 +46,9 @@ import org.springframework.security.core.authority.mapping.SimpleMappableAttributesRetriever; import org.springframework.security.web.access.AccessDeniedHandlerImpl; import org.springframework.security.web.access.ExceptionTranslationFilter; +import org.springframework.security.web.access.RequestMatcherDelegatingAccessDeniedHandler; import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; +import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint; import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider; @@ -53,18 +65,10 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; -import org.w3c.dom.Element; - -import javax.servlet.http.HttpServletRequest; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.Function; import static org.springframework.security.config.http.SecurityFilters.ANONYMOUS_FILTER; import static org.springframework.security.config.http.SecurityFilters.BASIC_AUTH_FILTER; +import static org.springframework.security.config.http.SecurityFilters.BEARER_TOKEN_AUTH_FILTER; import static org.springframework.security.config.http.SecurityFilters.EXCEPTION_TRANSLATION_FILTER; import static org.springframework.security.config.http.SecurityFilters.FORM_LOGIN_FILTER; import static org.springframework.security.config.http.SecurityFilters.LOGIN_PAGE_FILTER; @@ -136,6 +140,8 @@ final class AuthenticationConfigBuilder { private BeanMetadataElement mainEntryPoint; private BeanMetadataElement accessDeniedHandler; + private BeanDefinition bearerTokenAuthenticationFilter; + private BeanDefinition logoutFilter; @SuppressWarnings("rawtypes") private ManagedList logoutHandlers; @@ -160,11 +166,15 @@ final class AuthenticationConfigBuilder { private BeanReference oauth2LoginAuthenticationProviderRef; private BeanReference oauth2LoginOidcAuthenticationProviderRef; private BeanDefinition oauth2LoginLinks; - private BeanDefinition authorizationRequestRedirectFilter; private BeanDefinition authorizationCodeGrantFilter; private BeanReference authorizationCodeAuthenticationProviderRef; + private final List authenticationProviders = new ManagedList<>(); + private final Map defaultDeniedHandlerMappings = new ManagedMap<>(); + private final Map defaultEntryPointMappings = new ManagedMap<>(); + private final List csrfIgnoreRequestMatchers = new ManagedList<>(); + AuthenticationConfigBuilder(Element element, boolean forceAutoConfig, ParserContext pc, SessionCreationPolicy sessionPolicy, BeanReference requestCache, BeanReference authenticationManager, @@ -184,6 +194,7 @@ final class AuthenticationConfigBuilder { createAnonymousFilter(); createRememberMeFilter(authenticationManager); createBasicFilter(authenticationManager); + createBearerTokenAuthenticationFilter(authenticationManager); createFormLoginFilter(sessionStrategy, authenticationManager); createOAuth2LoginFilter(sessionStrategy, authenticationManager); createOAuth2ClientFilter(requestCache, authenticationManager); @@ -194,7 +205,6 @@ final class AuthenticationConfigBuilder { createLoginPageFilterIfNeeded(); createUserDetailsServiceFactory(); createExceptionTranslationFilter(); - } void createRememberMeFilter(BeanReference authenticationManager) { @@ -498,6 +508,21 @@ void createBasicFilter(BeanReference authManager) { basicFilter = filterBuilder.getBeanDefinition(); } + void createBearerTokenAuthenticationFilter(BeanReference authManager) { + Element resourceServerElt = DomUtils.getChildElementByTagName(httpElt, + Elements.OAUTH2_RESOURCE_SERVER); + + if (resourceServerElt == null) { + // No resource server, do nothing + return; + } + + OAuth2ResourceServerBeanDefinitionParser resourceServerBuilder = + new OAuth2ResourceServerBeanDefinitionParser(authManager, authenticationProviders, + defaultEntryPointMappings, defaultDeniedHandlerMappings, csrfIgnoreRequestMatchers); + bearerTokenAuthenticationFilter = resourceServerBuilder.parse(resourceServerElt, pc); + } + void createX509Filter(BeanReference authManager) { Element x509Elt = DomUtils.getChildElementByTagName(httpElt, Elements.X509); RootBeanDefinition filter = null; @@ -708,6 +733,10 @@ BeanMetadataElement getAccessDeniedHandlerBean() { return accessDeniedHandler; } + List getCsrfIgnoreRequestMatchers() { + return csrfIgnoreRequestMatchers; + } + void createAnonymousFilter() { Element anonymousElt = DomUtils.getChildElementByTagName(httpElt, Elements.ANONYMOUS); @@ -801,6 +830,7 @@ private BeanMetadataElement createAccessDeniedHandler(Element element, } accessDeniedHandler.addPropertyValue("errorPage", errorPage); + return accessDeniedHandler.getBeanDefinition(); } else if (StringUtils.hasText(ref)) { return new RuntimeBeanReference(ref); @@ -808,6 +838,19 @@ else if (StringUtils.hasText(ref)) { } + if (this.defaultDeniedHandlerMappings.isEmpty()) { + return accessDeniedHandler.getBeanDefinition(); + } + if (this.defaultDeniedHandlerMappings.size() == 1) { + return this.defaultDeniedHandlerMappings.values().iterator().next(); + } + + accessDeniedHandler = BeanDefinitionBuilder + .rootBeanDefinition(RequestMatcherDelegatingAccessDeniedHandler.class); + accessDeniedHandler.addConstructorArgValue(this.defaultDeniedHandlerMappings); + accessDeniedHandler.addConstructorArgValue + (BeanDefinitionBuilder.rootBeanDefinition(AccessDeniedHandlerImpl.class)); + return accessDeniedHandler.getBeanDefinition(); } @@ -820,6 +863,16 @@ private BeanMetadataElement selectEntryPoint() { return new RuntimeBeanReference(customEntryPoint); } + if (!defaultEntryPointMappings.isEmpty()) { + if (defaultEntryPointMappings.size() == 1) { + return defaultEntryPointMappings.values().iterator().next(); + } + BeanDefinitionBuilder delegatingEntryPoint = BeanDefinitionBuilder + .rootBeanDefinition(DelegatingAuthenticationEntryPoint.class); + delegatingEntryPoint.addConstructorArgValue(defaultEntryPointMappings); + return delegatingEntryPoint.getBeanDefinition(); + } + Element basicAuthElt = DomUtils.getChildElementByTagName(httpElt, Elements.BASIC_AUTH); Element formLoginElt = DomUtils.getChildElementByTagName(httpElt, @@ -935,8 +988,12 @@ List getFilters() { filters.add(new OrderDecorator(basicFilter, BASIC_AUTH_FILTER)); } + if (bearerTokenAuthenticationFilter != null) { + filters.add(new OrderDecorator(bearerTokenAuthenticationFilter, BEARER_TOKEN_AUTH_FILTER)); + } + if (authorizationCodeGrantFilter != null) { - filters.add(new OrderDecorator(authorizationRequestRedirectFilter, OAUTH2_AUTHORIZATION_REQUEST_FILTER.getOrder()+1)); + filters.add(new OrderDecorator(authorizationRequestRedirectFilter, OAUTH2_AUTHORIZATION_REQUEST_FILTER.getOrder() + 1)); filters.add(new OrderDecorator(authorizationCodeGrantFilter, OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER)); } @@ -980,6 +1037,8 @@ List getProviders() { providers.add(authorizationCodeAuthenticationProviderRef); } + providers.addAll(this.authenticationProviders); + return providers; } diff --git a/config/src/main/java/org/springframework/security/config/http/CsrfBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/CsrfBeanDefinitionParser.java index 46f48a4780a..9ef1fed832b 100644 --- a/config/src/main/java/org/springframework/security/config/http/CsrfBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/CsrfBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2020 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. @@ -15,12 +15,19 @@ */ package org.springframework.security.config.http; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + import org.w3c.dom.Element; import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionParser; @@ -38,6 +45,10 @@ import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor; import org.springframework.security.web.session.InvalidSessionAccessDeniedHandler; import org.springframework.security.web.session.InvalidSessionStrategy; +import org.springframework.security.web.util.matcher.AndRequestMatcher; +import org.springframework.security.web.util.matcher.NegatedRequestMatcher; +import org.springframework.security.web.util.matcher.OrRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -58,6 +69,8 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser { private String csrfRepositoryRef; private BeanDefinition csrfFilter; + private String requestMatcherRef; + @Override public BeanDefinition parse(Element element, ParserContext pc) { boolean disabled = element != null @@ -77,10 +90,9 @@ public BeanDefinition parse(Element element, ParserContext pc) { } } - String matcherRef = null; if (element != null) { this.csrfRepositoryRef = element.getAttribute(ATT_REPOSITORY); - matcherRef = element.getAttribute(ATT_MATCHER); + this.requestMatcherRef = element.getAttribute(ATT_MATCHER); } if (!StringUtils.hasText(this.csrfRepositoryRef)) { @@ -100,8 +112,8 @@ public BeanDefinition parse(Element element, ParserContext pc) { .rootBeanDefinition(CsrfFilter.class); builder.addConstructorArgReference(this.csrfRepositoryRef); - if (StringUtils.hasText(matcherRef)) { - builder.addPropertyReference("requireCsrfProtectionMatcher", matcherRef); + if (StringUtils.hasText(this.requestMatcherRef)) { + builder.addPropertyReference("requireCsrfProtectionMatcher", this.requestMatcherRef); } this.csrfFilter = builder.getBeanDefinition(); @@ -172,4 +184,46 @@ BeanDefinition getCsrfLogoutHandler() { csrfAuthenticationStrategy.addConstructorArgReference(this.csrfRepositoryRef); return csrfAuthenticationStrategy.getBeanDefinition(); } + + void setIgnoreCsrfRequestMatchers(List requestMatchers) { + if (!requestMatchers.isEmpty()) { + BeanMetadataElement requestMatcher; + if (StringUtils.hasText(this.requestMatcherRef)) { + requestMatcher = new RuntimeBeanReference(this.requestMatcherRef); + } else { + requestMatcher = new RootBeanDefinition(DefaultRequiresCsrfMatcher.class); + } + BeanDefinitionBuilder and = BeanDefinitionBuilder + .rootBeanDefinition(AndRequestMatcher.class); + BeanDefinitionBuilder negated = BeanDefinitionBuilder + .rootBeanDefinition(NegatedRequestMatcher.class); + BeanDefinitionBuilder or = BeanDefinitionBuilder + .rootBeanDefinition(OrRequestMatcher.class); + or.addConstructorArgValue(requestMatchers); + negated.addConstructorArgValue(or.getBeanDefinition()); + List ands = new ManagedList<>(); + ands.add(requestMatcher); + ands.add(negated.getBeanDefinition()); + and.addConstructorArgValue(ands); + this.csrfFilter.getPropertyValues() + .add("requireCsrfProtectionMatcher", and.getBeanDefinition()); + } + } + + private static final class DefaultRequiresCsrfMatcher implements RequestMatcher { + private final HashSet allowedMethods = new HashSet<>( + Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS")); + + /* + * (non-Javadoc) + * + * @see + * org.springframework.security.web.util.matcher.RequestMatcher#matches(javax. + * servlet.http.HttpServletRequest) + */ + @Override + public boolean matches(HttpServletRequest request) { + return !this.allowedMethods.contains(request.getMethod()); + } + } } diff --git a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java index 24eb66974ed..2711ebdb9f5 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -238,6 +238,12 @@ void setAccessDeniedHandler(BeanMetadataElement accessDeniedHandler) { } } + void setCsrfIgnoreRequestMatchers(List requestMatchers) { + if (csrfParser != null) { + csrfParser.setIgnoreCsrfRequestMatchers(requestMatchers); + } + } + // Needed to account for placeholders static String createPath(String path, boolean lowerCase) { return lowerCase ? path.toLowerCase() : path; diff --git a/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java index ccb1abf7aed..256cae6dcb4 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java @@ -15,8 +15,14 @@ */ package org.springframework.security.config.http; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Element; + import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanReference; @@ -44,9 +50,6 @@ import org.springframework.security.web.util.matcher.AnyRequestMatcher; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; -import org.w3c.dom.Element; - -import java.util.*; /** * Sets up HTTP security: filter stack and protected URLs. @@ -156,6 +159,7 @@ private BeanReference createFilterChain(Element element, ParserContext pc) { httpBldr.setLogoutHandlers(authBldr.getLogoutHandlers()); httpBldr.setEntryPoint(authBldr.getEntryPointBean()); httpBldr.setAccessDeniedHandler(authBldr.getAccessDeniedHandlerBean()); + httpBldr.setCsrfIgnoreRequestMatchers(authBldr.getCsrfIgnoreRequestMatchers()); authenticationProviders.addAll(authBldr.getProviders()); diff --git a/config/src/main/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParser.java new file mode 100644 index 00000000000..5068a74b456 --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParser.java @@ -0,0 +1,358 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.config.http; + +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; + +import org.w3c.dom.Element; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanReference; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationManagerResolver; +import org.springframework.security.config.Elements; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; +import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider; +import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector; +import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint; +import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter; +import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; +import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; +import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; + +/** + * A {@link BeanDefinitionParser} for <http>'s <oauth2-resource-server> element. + * + * @since 5.3 + * @author Josh Cummings + */ +final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionParser { + static final String AUTHENTICATION_MANAGER_RESOLVER_REF = "authentication-manager-resolver-ref"; + static final String BEARER_TOKEN_RESOLVER_REF = "bearer-token-resolver-ref"; + static final String ENTRY_POINT_REF = "entry-point-ref"; + + static final String BEARER_TOKEN_RESOLVER = "bearerTokenResolver"; + static final String AUTHENTICATION_ENTRY_POINT = "authenticationEntryPoint"; + + private final BeanReference authenticationManager; + private final List authenticationProviders; + private final Map entryPoints; + private final Map deniedHandlers; + private final List ignoreCsrfRequestMatchers; + + private final BeanDefinition authenticationEntryPoint = + new RootBeanDefinition(BearerTokenAuthenticationEntryPoint.class); + private final BeanDefinition accessDeniedHandler = + new RootBeanDefinition(BearerTokenAccessDeniedHandler.class); + + OAuth2ResourceServerBeanDefinitionParser(BeanReference authenticationManager, + List authenticationProviders, + Map entryPoints, + Map deniedHandlers, + List ignoreCsrfRequestMatchers) { + this.authenticationManager = authenticationManager; + this.authenticationProviders = authenticationProviders; + this.entryPoints = entryPoints; + this.deniedHandlers = deniedHandlers; + this.ignoreCsrfRequestMatchers = ignoreCsrfRequestMatchers; + } + + /** + * Parse a <oauth2-resource-server> element and return the corresponding + * {@link BearerTokenAuthenticationFilter} + * + * @param oauth2ResourceServer the <oauth2-resource-server> element. + * @param pc the {@link ParserContext} + * @return a {@link BeanDefinition} representing a {@link BearerTokenAuthenticationFilter} definition + */ + @Override + public BeanDefinition parse(Element oauth2ResourceServer, ParserContext pc) { + Element jwt = DomUtils.getChildElementByTagName(oauth2ResourceServer, Elements.JWT); + Element opaqueToken = DomUtils.getChildElementByTagName(oauth2ResourceServer, Elements.OPAQUE_TOKEN); + + validateConfiguration(oauth2ResourceServer, jwt, opaqueToken, pc); + + if (jwt != null) { + BeanDefinition jwtAuthenticationProvider = + new JwtBeanDefinitionParser().parse(jwt, pc); + this.authenticationProviders.add(new RuntimeBeanReference + (pc.getReaderContext().registerWithGeneratedName(jwtAuthenticationProvider))); + } + + if (opaqueToken != null) { + BeanDefinition opaqueTokenAuthenticationProvider = + new OpaqueTokenBeanDefinitionParser().parse(opaqueToken, pc); + this.authenticationProviders.add(new RuntimeBeanReference + (pc.getReaderContext().registerWithGeneratedName(opaqueTokenAuthenticationProvider))); + } + + BeanMetadataElement bearerTokenResolver = getBearerTokenResolver(oauth2ResourceServer); + BeanDefinitionBuilder requestMatcherBuilder = BeanDefinitionBuilder + .rootBeanDefinition(BearerTokenRequestMatcher.class); + requestMatcherBuilder.addConstructorArgValue(bearerTokenResolver); + BeanDefinition requestMatcher = requestMatcherBuilder.getBeanDefinition(); + + BeanMetadataElement authenticationEntryPoint = getEntryPoint(oauth2ResourceServer); + + this.entryPoints.put(requestMatcher, authenticationEntryPoint); + this.deniedHandlers.put(requestMatcher, this.accessDeniedHandler); + this.ignoreCsrfRequestMatchers.add(requestMatcher); + + BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder + .rootBeanDefinition(BearerTokenAuthenticationFilter.class); + BeanMetadataElement authenticationManagerResolver = getAuthenticationManagerResolver(oauth2ResourceServer); + filterBuilder.addConstructorArgValue(authenticationManagerResolver); + filterBuilder.addPropertyValue(BEARER_TOKEN_RESOLVER, bearerTokenResolver); + filterBuilder.addPropertyValue(AUTHENTICATION_ENTRY_POINT, authenticationEntryPoint); + return filterBuilder.getBeanDefinition(); + } + + void validateConfiguration(Element oauth2ResourceServer, Element jwt, Element opaqueToken, ParserContext pc) { + if (!oauth2ResourceServer.hasAttribute(AUTHENTICATION_MANAGER_RESOLVER_REF)) { + if (jwt == null && opaqueToken == null) { + pc.getReaderContext().error + ("Didn't find authentication-manager-resolver-ref, , or . " + + "Please select one.", oauth2ResourceServer); + } + return; + } + + if (jwt != null) { + pc.getReaderContext().error + ("Found as well as authentication-manager-resolver-ref. " + + "Please select just one.", oauth2ResourceServer); + } + + if (opaqueToken != null) { + pc.getReaderContext().error + ("Found as well as authentication-manager-resolver-ref. " + + "Please select just one.", oauth2ResourceServer); + } + } + + BeanMetadataElement getAuthenticationManagerResolver(Element element) { + String authenticationManagerResolverRef = element.getAttribute(AUTHENTICATION_MANAGER_RESOLVER_REF); + if (!StringUtils.isEmpty(authenticationManagerResolverRef)) { + return new RuntimeBeanReference(authenticationManagerResolverRef); + } + BeanDefinitionBuilder authenticationManagerResolver = BeanDefinitionBuilder + .rootBeanDefinition(StaticAuthenticationManagerResolver.class); + authenticationManagerResolver.addConstructorArgValue(this.authenticationManager); + return authenticationManagerResolver.getBeanDefinition(); + } + + BeanMetadataElement getBearerTokenResolver(Element element) { + String bearerTokenResolverRef = element.getAttribute(BEARER_TOKEN_RESOLVER_REF); + if (StringUtils.isEmpty(bearerTokenResolverRef)) { + return new RootBeanDefinition(DefaultBearerTokenResolver.class); + } else { + return new RuntimeBeanReference(bearerTokenResolverRef); + } + } + + BeanMetadataElement getEntryPoint(Element element) { + String entryPointRef = element.getAttribute(ENTRY_POINT_REF); + if (StringUtils.isEmpty(entryPointRef)) { + return this.authenticationEntryPoint; + } else { + return new RuntimeBeanReference(entryPointRef); + } + } +} + +final class JwtBeanDefinitionParser implements BeanDefinitionParser { + static final String DECODER_REF = "decoder-ref"; + static final String JWK_SET_URI = "jwk-set-uri"; + static final String JWT_AUTHENTICATION_CONVERTER_REF = "jwt-authentication-converter-ref"; + static final String JWT_AUTHENTICATION_CONVERTER = "jwtAuthenticationConverter"; + + @Override + public BeanDefinition parse(Element element, ParserContext pc) { + validateConfiguration(element, pc); + + BeanDefinitionBuilder jwtProviderBuilder = + BeanDefinitionBuilder.rootBeanDefinition(JwtAuthenticationProvider.class); + jwtProviderBuilder.addConstructorArgValue(getDecoder(element)); + jwtProviderBuilder.addPropertyValue(JWT_AUTHENTICATION_CONVERTER, getJwtAuthenticationConverter(element)); + + return jwtProviderBuilder.getBeanDefinition(); + } + + void validateConfiguration(Element element, ParserContext pc) { + boolean usesDecoder = element.hasAttribute(DECODER_REF); + boolean usesJwkSetUri = element.hasAttribute(JWK_SET_URI); + + if (usesDecoder == usesJwkSetUri) { + pc.getReaderContext().error + ("Please specify either decoder-ref or jwk-set-uri.", element); + } + } + + Object getDecoder(Element element) { + String decoderRef = element.getAttribute(DECODER_REF); + if (!StringUtils.isEmpty(decoderRef)) { + return new RuntimeBeanReference(decoderRef); + } + + BeanDefinitionBuilder builder = BeanDefinitionBuilder + .rootBeanDefinition(NimbusJwtDecoderJwkSetUriFactoryBean.class); + builder.addConstructorArgValue(element.getAttribute(JWK_SET_URI)); + return builder.getBeanDefinition(); + } + + Object getJwtAuthenticationConverter(Element element) { + String jwtDecoderRef = element.getAttribute(JWT_AUTHENTICATION_CONVERTER_REF); + if (!StringUtils.isEmpty(jwtDecoderRef)) { + return new RuntimeBeanReference(jwtDecoderRef); + } + + return new JwtAuthenticationConverter(); + } + + JwtBeanDefinitionParser() {} +} + +final class OpaqueTokenBeanDefinitionParser implements BeanDefinitionParser { + static final String INTROSPECTOR_REF = "introspector-ref"; + static final String INTROSPECTION_URI = "introspection-uri"; + static final String CLIENT_ID = "client-id"; + static final String CLIENT_SECRET = "client-secret"; + + @Override + public BeanDefinition parse(Element element, ParserContext pc) { + validateConfiguration(element, pc); + + BeanMetadataElement introspector = getIntrospector(element); + BeanDefinitionBuilder opaqueTokenProviderBuilder = + BeanDefinitionBuilder.rootBeanDefinition(OpaqueTokenAuthenticationProvider.class); + opaqueTokenProviderBuilder.addConstructorArgValue(introspector); + + return opaqueTokenProviderBuilder.getBeanDefinition(); + } + + void validateConfiguration(Element element, ParserContext pc) { + boolean usesIntrospector = element.hasAttribute(INTROSPECTOR_REF); + boolean usesEndpoint = element.hasAttribute(INTROSPECTION_URI) || + element.hasAttribute(CLIENT_ID) || + element.hasAttribute(CLIENT_SECRET); + + if (usesIntrospector == usesEndpoint) { + pc.getReaderContext().error + ("Please specify either introspector-ref or all of " + + "introspection-uri, client-id, and client-secret.", element); + return; + } + + if (usesEndpoint) { + if (!(element.hasAttribute(INTROSPECTION_URI) && + element.hasAttribute(CLIENT_ID) && + element.hasAttribute(CLIENT_SECRET))) { + pc.getReaderContext().error + ("Please specify introspection-uri, client-id, and client-secret together", element); + } + } + } + + BeanMetadataElement getIntrospector(Element element) { + String introspectorRef = element.getAttribute(INTROSPECTOR_REF); + if (!StringUtils.isEmpty(introspectorRef)) { + return new RuntimeBeanReference(introspectorRef); + } + + String introspectionUri = element.getAttribute(INTROSPECTION_URI); + String clientId = element.getAttribute(CLIENT_ID); + String clientSecret = element.getAttribute(CLIENT_SECRET); + + BeanDefinitionBuilder introspectorBuilder = BeanDefinitionBuilder + .rootBeanDefinition(NimbusOpaqueTokenIntrospector.class); + introspectorBuilder.addConstructorArgValue(introspectionUri); + introspectorBuilder.addConstructorArgValue(clientId); + introspectorBuilder.addConstructorArgValue(clientSecret); + + return introspectorBuilder.getBeanDefinition(); + } + + OpaqueTokenBeanDefinitionParser() {} +} + +final class StaticAuthenticationManagerResolver implements + AuthenticationManagerResolver { + private final AuthenticationManager authenticationManager; + + StaticAuthenticationManagerResolver(AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + + @Override + public AuthenticationManager resolve(HttpServletRequest context) { + return this.authenticationManager; + } +} + +final class NimbusJwtDecoderJwkSetUriFactoryBean implements FactoryBean { + private final String jwkSetUri; + + NimbusJwtDecoderJwkSetUriFactoryBean(String jwkSetUri) { + this.jwkSetUri = jwkSetUri; + } + + @Override + public JwtDecoder getObject() { + return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri).build(); + } + + @Override + public Class getObjectType() { + return JwtDecoder.class; + } +} + +final class BearerTokenRequestMatcher implements RequestMatcher { + private final BearerTokenResolver bearerTokenResolver; + + BearerTokenRequestMatcher(BearerTokenResolver bearerTokenResolver) { + Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null"); + this.bearerTokenResolver = bearerTokenResolver; + } + + @Override + public boolean matches(HttpServletRequest request) { + try { + return this.bearerTokenResolver.resolve(request) != null; + } catch (OAuth2AuthenticationException e) { + return false; + } + } +} + diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-5.3.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-5.3.rnc index 6bd7fee3d26..79dfe1b74ef 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-5.3.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-5.3.rnc @@ -296,7 +296,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? & oauth2-login? & oauth2-client? & openid-login? & x509? & jee? & http-basic? & logout? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache? & expression-handler? & headers? & csrf? & cors?) } + element http {http.attlist, (intercept-url* & access-denied-handler? & form-login? & oauth2-login? & oauth2-client? & oauth2-resource-server? & 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 element. If omitted, the filter chain will match all requests. attribute pattern {xsd:token}? @@ -572,6 +572,48 @@ provider.attlist &= ## The URI used to discover the configuration information for an OAuth 2.0 or OpenID Connect 1.0 Provider. attribute issuer-uri {xsd:token}? +oauth2-resource-server = + ## Configures authentication support as an OAuth 2.0 Resource Server. + element oauth2-resource-server {oauth2-resource-server.attlist, (jwt? & opaque-token?)} +oauth2-resource-server.attlist &= + ## Reference to an AuthenticationManagerResolver + attribute authentication-manager-resolver-ref {xsd:token}? +oauth2-resource-server.attlist &= + ## Reference to a BearerTokenResolver + attribute bearer-token-resolver-ref {xsd:token}? +oauth2-resource-server.attlist &= + ## Reference to a AuthenticationEntryPoint + attribute entry-point-ref {xsd:token}? + +jwt = + ## Configures JWT authentication + element jwt {jwt.attlist} +jwt.attlist &= + ## The URI to use to collect the JWK Set for verifying JWTs + attribute jwk-set-uri {xsd:token}? +jwt.attlist &= + ## Reference to a JwtDecoder + attribute decoder-ref {xsd:token}? +jwt.attlist &= + ## Reference to a Converter + attribute jwt-authentication-converter-ref {xsd:token}? + +opaque-token = + ## Configuration Opaque Token authentication + element opaque-token {opaque-token.attlist} +opaque-token.attlist &= + ## The URI to use to introspect opaque token attributes + attribute introspection-uri {xsd:token}? +opaque-token.attlist &= + ## The Client ID to use to authenticate the introspection request + attribute client-id {xsd:token}? +opaque-token.attlist &= + ## The Client secret to use to authenticate the introspection request + attribute client-secret {xsd:token}? +opaque-token.attlist &= + ## Reference to an OpaqueTokenIntrospector + attribute introspector-ref {xsd:token}? + openid-login = ## Sets up form login for authentication with an Open ID identity element openid-login {form-login.attlist, user-service-ref?, attribute-exchange*} diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-5.3.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-5.3.xsd index 6fee0689b7a..88c5f2cb8b6 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-5.3.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-5.3.xsd @@ -957,6 +957,7 @@ + Sets up form login for authentication with an Open ID identity @@ -1780,6 +1781,103 @@ + + + Configures authentication support as an OAuth 2.0 Resource Server. + + + + + + + + + + + + + + Reference to an AuthenticationManagerResolver + + + + + + Reference to a BearerTokenResolver + + + + + + Reference to a AuthenticationEntryPoint + + + + + + + Configures JWT authentication + + + + + + + + + + The URI to use to collect the JWK Set for verifying JWTs + + + + + + Reference to a JwtDecoder + + + + + + Reference to a Converter<Jwt, AbstractAuthenticationToken> + + + + + + + Configuration Opaque Token authentication + + + + + + + + + + The URI to use to introspect opaque token attributes + + + + + + The Client ID to use to authenticate the introspection request + + + + + + The Client secret to use to authenticate the introspection request + + + + + + Reference to an OpaqueTokenIntrospector + + + + diff --git a/config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java new file mode 100644 index 00000000000..15186e0b7ef --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java @@ -0,0 +1,1239 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.config.http; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.security.interfaces.RSAPublicKey; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; + +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.JWSObject; +import com.nimbusds.jose.Payload; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.RSAKey; +import net.minidev.json.JSONObject; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.hamcrest.core.AllOf; +import org.hamcrest.core.StringContains; +import org.hamcrest.core.StringEndsWith; +import org.hamcrest.core.StringStartsWith; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.w3c.dom.Element; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanReference; +import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.beans.factory.xml.XmlReaderContext; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManagerResolver; +import org.springframework.security.config.test.SpringTestRule; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.OAuth2TokenValidator; +import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; +import org.springframework.security.oauth2.jose.TestKeys; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtException; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector; +import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; +import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; +import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.ResultMatcher; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestOperations; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.core.StringStartsWith.startsWith; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.powermock.api.mockito.PowerMockito.when; +import static org.springframework.security.config.http.JwtBeanDefinitionParser.DECODER_REF; +import static org.springframework.security.config.http.JwtBeanDefinitionParser.JWK_SET_URI; +import static org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParser.AUTHENTICATION_MANAGER_RESOLVER_REF; +import static org.springframework.security.config.http.OpaqueTokenBeanDefinitionParser.INTROSPECTION_URI; +import static org.springframework.security.config.http.OpaqueTokenBeanDefinitionParser.INTROSPECTOR_REF; +import static org.springframework.security.oauth2.jwt.JwtClaimNames.ISS; +import static org.springframework.security.oauth2.jwt.JwtClaimNames.SUB; +import static org.springframework.security.oauth2.jwt.TestJwts.jwt; +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; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * + * @author Josh Cummings + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SecurityTestExecutionListeners +public class OAuth2ResourceServerBeanDefinitionParserTests { + private static final String CONFIG_LOCATION_PREFIX = + "classpath:org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests"; + + @Rule + public final SpringTestRule spring = new SpringTestRule(); + + @Autowired + MockMvc mvc; + + @Autowired(required = false) + MockWebServer web; + + @Test + public void getWhenValidBearerTokenThenAcceptsRequest() throws Exception { + this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); + mockRestOperations(jwks("Default")); + String token = this.token("ValidNoScopes"); + + this.mvc.perform(get("/") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isNotFound()); + } + + @Test + public void getWhenUsingJwkSetUriThenAcceptsRequest() throws Exception { + this.spring.configLocations(xml("WebServer"), xml("JwkSetUri")).autowire(); + mockWebServer(jwks("Default")); + String token = this.token("ValidNoScopes"); + + this.mvc.perform(get("/") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isNotFound()); + } + + @Test + public void getWhenExpiredBearerTokenThenInvalidToken() + throws Exception { + + this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); + mockRestOperations(jwks("Default")); + String token = this.token("Expired"); + + this.mvc.perform(get("/") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isUnauthorized()) + .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); + } + + @Test + public void getWhenBadJwkEndpointThenInvalidToken() + throws Exception { + + this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); + mockRestOperations("malformed"); + String token = this.token("ValidNoScopes"); + + this.mvc.perform(get("/") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isUnauthorized()) + .andExpect(header().string("WWW-Authenticate", "Bearer")); + } + + @Test + public void getWhenUnavailableJwkEndpointThenInvalidToken() + throws Exception { + + this.spring.configLocations(xml("WebServer"), xml("JwkSetUri")).autowire(); + this.web.shutdown(); + String token = this.token("ValidNoScopes"); + + this.mvc.perform(get("/") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isUnauthorized()) + .andExpect(header().string("WWW-Authenticate", "Bearer")); + } + + @Test + public void getWhenMalformedBearerTokenThenInvalidToken() + throws Exception { + + this.spring.configLocations(xml("JwkSetUri")).autowire(); + + this.mvc.perform(get("/") + .header("Authorization", "Bearer an\"invalid\"token")) + .andExpect(status().isUnauthorized()) + .andExpect(invalidTokenHeader("Bearer token is malformed")); + } + + @Test + public void getWhenMalformedPayloadThenInvalidToken() + throws Exception { + + this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); + mockRestOperations(jwks("Default")); + String token = this.token("MalformedPayload"); + + this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) + .andExpect(status().isUnauthorized()) + .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt: Malformed payload")); + } + + @Test + public void getWhenUnsignedBearerTokenThenInvalidToken() + throws Exception { + + this.spring.configLocations(xml("JwkSetUri")).autowire(); + String token = this.token("Unsigned"); + + this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) + .andExpect(status().isUnauthorized()) + .andExpect(invalidTokenHeader("Unsupported algorithm of none")); + } + + @Test + public void getWhenBearerTokenBeforeNotBeforeThenInvalidToken() + throws Exception { + + this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); + this.mockRestOperations(jwks("Default")); + String token = this.token("TooEarly"); + + this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) + .andExpect(status().isUnauthorized()) + .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); + } + + @Test + public void getWhenBearerTokenInTwoPlacesThenInvalidRequest() + throws Exception { + + this.spring.configLocations(xml("JwkSetUri")).autowire(); + + this.mvc.perform(get("/") + .header("Authorization", "Bearer token") + .param("access_token", "token")) + .andExpect(status().isBadRequest()) + .andExpect(invalidRequestHeader("Found multiple bearer tokens in the request")); + } + + @Test + public void getWhenBearerTokenInTwoParametersThenInvalidRequest() + throws Exception { + + this.spring.configLocations(xml("JwkSetUri")).autowire(); + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("access_token", "token1"); + params.add("access_token", "token2"); + + this.mvc.perform(get("/") + .params(params)) + .andExpect(status().isBadRequest()) + .andExpect(invalidRequestHeader("Found multiple bearer tokens in the request")); + } + + @Test + public void postWhenBearerTokenAsFormParameterThenIgnoresToken() + throws Exception { + + this.spring.configLocations(xml("JwkSetUri")).autowire(); + + this.mvc.perform(post("/") // engage csrf + .param("access_token", "token")) + .andExpect(status().isForbidden()) + .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer")); // different from DSL + } + + @Test + public void getWhenNoBearerTokenThenUnauthorized() + throws Exception { + + this.spring.configLocations(xml("JwkSetUri")).autowire(); + + this.mvc.perform(get("/")) + .andExpect(status().isUnauthorized()) + .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer")); + } + + @Test + public void getWhenSufficientlyScopedBearerTokenThenAcceptsRequest() + throws Exception { + + this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); + mockRestOperations(jwks("Default")); + String token = this.token("ValidMessageReadScope"); + + this.mvc.perform(get("/requires-read-scope") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isNotFound()); + } + + @Test + public void getWhenInsufficientScopeThenInsufficientScopeError() + throws Exception { + + this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); + mockRestOperations(jwks("Default")); + String token = this.token("ValidNoScopes"); + + this.mvc.perform(get("/requires-read-scope") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isForbidden()) + .andExpect(insufficientScopeHeader()); + } + + @Test + public void getWhenInsufficientScpThenInsufficientScopeError() + throws Exception { + + this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); + mockRestOperations(jwks("Default")); + String token = this.token("ValidMessageWriteScp"); + + this.mvc.perform(get("/requires-read-scope") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isForbidden()) + .andExpect(insufficientScopeHeader()); + } + + @Test + public void getWhenAuthorizationServerHasNoMatchingKeyThenInvalidToken() + throws Exception { + + this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); + mockRestOperations(jwks("Empty")); + String token = this.token("ValidNoScopes"); + + this.mvc.perform(get("/") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isUnauthorized()) + .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); + } + + @Test + public void getWhenAuthorizationServerHasMultipleMatchingKeysThenOk() + throws Exception { + + this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); + mockRestOperations(jwks("TwoKeys")); + String token = this.token("ValidNoScopes"); + + this.mvc.perform(get("/authenticated") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isNotFound()); + } + + @Test + public void getWhenKeyMatchesByKidThenOk() + throws Exception { + + this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); + mockRestOperations(jwks("TwoKeys")); + String token = this.token("Kid"); + + this.mvc.perform(get("/authenticated") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isNotFound()); + } + + // -- Resource Server should not engage csrf + + @Test + public void postWhenValidBearerTokenAndNoCsrfTokenThenOk() + throws Exception { + + this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); + mockRestOperations(jwks("Default")); + String token = this.token("ValidNoScopes"); + + this.mvc.perform(post("/authenticated") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isNotFound()); + } + + @Test + public void postWhenNoBearerTokenThenCsrfDenies() + throws Exception { + + this.spring.configLocations(xml("JwkSetUri")).autowire(); + + this.mvc.perform(post("/authenticated")) + .andExpect(status().isForbidden()) + .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer")); // different from DSL + } + + @Test + public void postWhenExpiredBearerTokenAndNoCsrfThenInvalidToken() + throws Exception { + + this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); + mockRestOperations(jwks("Default")); + String token = this.token("Expired"); + + this.mvc.perform(post("/authenticated") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isUnauthorized()) + .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); + } + + // -- Resource Server should not create sessions + + @Test + public void requestWhenJwtThenSessionIsNotCreated() + throws Exception { + + this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); + mockRestOperations(jwks("Default")); + String token = this.token("ValidNoScopes"); + + MvcResult result = this.mvc.perform(get("/") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isNotFound()) + .andReturn(); + + assertThat(result.getRequest().getSession(false)).isNull(); + } + + @Test + public void requestWhenIntrospectionThenSessionIsNotCreated() + throws Exception { + + this.spring.configLocations(xml("WebServer"), xml("IntrospectionUri")).autowire(); + mockWebServer(json("Active")); + + MvcResult result = this.mvc.perform(get("/authenticated") + .header("Authorization", "Bearer token")) + .andExpect(status().isNotFound()) + .andReturn(); + + assertThat(result.getRequest().getSession(false)).isNull(); + } + + @Test + public void requestWhenNoBearerTokenThenSessionIsCreated() + throws Exception { + + this.spring.configLocations(xml("JwkSetUri")).autowire(); + + MvcResult result = this.mvc.perform(get("/")) + .andExpect(status().isUnauthorized()) + .andReturn(); + + assertThat(result.getRequest().getSession(false)).isNotNull(); + } + + @Test + public void requestWhenSessionManagementConfiguredThenUses() + throws Exception { + + this.spring.configLocations(xml("JwtRestOperations"), xml("AlwaysSessionCreation")).autowire(); + mockRestOperations(jwks("Default")); + String token = this.token("ValidNoScopes"); + + MvcResult result = this.mvc.perform(get("/") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isNotFound()) + .andReturn(); + + assertThat(result.getRequest().getSession(false)).isNotNull(); + } + + // -- custom bearer token resolver + + @Test + public void getWhenCustomBearerTokenResolverThenUses() throws Exception { + this.spring.configLocations(xml("MockBearerTokenResolver"), xml("MockJwtDecoder"), + xml("BearerTokenResolver")).autowire(); + + JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); + when(decoder.decode("token")).thenReturn(jwt().build()); + + BearerTokenResolver bearerTokenResolver = this.spring.getContext().getBean(BearerTokenResolver.class); + when(bearerTokenResolver.resolve(any(HttpServletRequest.class))) + .thenReturn("token"); + + this.mvc.perform(get("/")) + .andExpect(status().isNotFound()); + + verify(decoder).decode("token"); + verify(bearerTokenResolver).resolve(any(HttpServletRequest.class)); + } + + @Test + public void requestWhenBearerTokenResolverAllowsRequestBodyThenEitherHeaderOrRequestBodyIsAccepted() + throws Exception { + + this.spring.configLocations(xml("MockJwtDecoder"), xml("AllowBearerTokenInBody")).autowire(); + + JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); + when(decoder.decode(anyString())).thenReturn(jwt().build()); + + this.mvc.perform(get("/authenticated") + .header("Authorization", "Bearer token")) + .andExpect(status().isNotFound()); + + this.mvc.perform(post("/authenticated") + .param("access_token", "token")) + .andExpect(status().isNotFound()); + } + + @Test + public void requestWhenBearerTokenResolverAllowsQueryParameterThenEitherHeaderOrQueryParameterIsAccepted() + throws Exception { + + this.spring.configLocations(xml("MockJwtDecoder"), xml("AllowBearerTokenInQuery")).autowire(); + + JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); + Mockito.when(decoder.decode(anyString())).thenReturn(jwt().build()); + + this.mvc.perform(get("/authenticated") + .header("Authorization", "Bearer token")) + .andExpect(status().isNotFound()); + + this.mvc.perform(get("/authenticated") + .param("access_token", "token")) + .andExpect(status().isNotFound()); + + verify(decoder, times(2)).decode("token"); + } + + @Test + public void requestWhenBearerTokenResolverAllowsRequestBodyAndRequestContainsTwoTokensThenInvalidRequest() + throws Exception { + + this.spring.configLocations(xml("MockJwtDecoder"), xml("AllowBearerTokenInBody")).autowire(); + + this.mvc.perform(post("/authenticated") + .param("access_token", "token") + .header("Authorization", "Bearer token") + .with(csrf())) + .andExpect(status().isBadRequest()) + .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("invalid_request"))); + } + + @Test + public void requestWhenBearerTokenResolverAllowsQueryParameterAndRequestContainsTwoTokensThenInvalidRequest() + throws Exception { + + this.spring.configLocations(xml("MockJwtDecoder"), xml("AllowBearerTokenInQuery")).autowire(); + + this.mvc.perform(get("/authenticated") + .header("Authorization", "Bearer token") + .param("access_token", "token")) + .andExpect(status().isBadRequest()) + .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("invalid_request"))); + } + + @Test + public void getBearerTokenResolverWhenNoResolverSpecifiedThenTheDefaultIsUsed() { + OAuth2ResourceServerBeanDefinitionParser oauth2 = + new OAuth2ResourceServerBeanDefinitionParser + (mock(BeanReference.class), mock(List.class), mock(Map.class), + mock(Map.class), mock(List.class)); + + assertThat(oauth2.getBearerTokenResolver(mock(Element.class))) + .isInstanceOf(RootBeanDefinition.class); + } + + // -- custom jwt decoder + + @Test + public void requestWhenCustomJwtDecoderThenUsed() + throws Exception { + + this.spring.configLocations(xml("MockJwtDecoder"), xml("Jwt")).autowire(); + + JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); + + when(decoder.decode(anyString())).thenReturn(jwt().build()); + + this.mvc.perform(get("/authenticated") + .header("Authorization", "Bearer token")) + .andExpect(status().isNotFound()); + + verify(decoder).decode("token"); + } + + @Test + public void configureWhenDecoderAndJwkSetUriThenException() { + assertThatThrownBy(() -> this.spring.configLocations(xml("JwtDecoderAndJwkSetUri")).autowire()) + .isInstanceOf(BeanDefinitionParsingException.class); + } + + // -- exception handling + + @Test + public void requestWhenRealmNameConfiguredThenUsesOnUnauthenticated() + throws Exception { + + this.spring.configLocations(xml("MockJwtDecoder"), xml("AuthenticationEntryPoint")).autowire(); + + JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); + Mockito.when(decoder.decode(anyString())).thenThrow(JwtException.class); + + this.mvc.perform(get("/authenticated") + .header("Authorization", "Bearer invalid_token")) + .andExpect(status().isUnauthorized()) + .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer realm=\"myRealm\""))); + } + + @Test + public void requestWhenRealmNameConfiguredThenUsesOnAccessDenied() + throws Exception { + + this.spring.configLocations(xml("MockJwtDecoder"), xml("AccessDeniedHandler")).autowire(); + + JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); + Mockito.when(decoder.decode(anyString())).thenReturn(jwt().build()); + + this.mvc.perform(get("/authenticated") + .header("Authorization", "Bearer insufficiently_scoped")) + .andExpect(status().isForbidden()) + .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer realm=\"myRealm\""))); + } + + // -- token validator + + @Test + public void requestWhenCustomJwtValidatorFailsThenCorrespondingErrorMessage() + throws Exception { + + this.spring.configLocations(xml("MockJwtValidator"), xml("Jwt")).autowire(); + mockRestOperations(jwks("Default")); + String token = this.token("ValidNoScopes"); + + OAuth2TokenValidator jwtValidator = + this.spring.getContext().getBean(OAuth2TokenValidator.class); + + OAuth2Error error = new OAuth2Error("custom-error", "custom-description", "custom-uri"); + + when(jwtValidator.validate(any(Jwt.class))).thenReturn(OAuth2TokenValidatorResult.failure(error)); + + this.mvc.perform(get("/") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isUnauthorized()) + .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("custom-description"))); + } + + @Test + public void requestWhenClockSkewSetThenTimestampWindowRelaxedAccordingly() + throws Exception { + + this.spring.configLocations(xml("UnexpiredJwtClockSkew"), xml("Jwt")).autowire(); + mockRestOperations(jwks("Default")); + String token = this.token("ExpiresAt4687177990"); + + this.mvc.perform(get("/") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isNotFound()); + } + + @Test + public void requestWhenClockSkewSetButJwtStillTooLateThenReportsExpired() + throws Exception { + + this.spring.configLocations(xml("ExpiredJwtClockSkew"), xml("Jwt")).autowire(); + mockRestOperations(jwks("Default")); + String token = this.token("ExpiresAt4687177990"); + + this.mvc.perform(get("/") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isUnauthorized()) + .andExpect(invalidTokenHeader("Jwt expired at")); + } + + // -- converter + + @Test + public void requestWhenJwtAuthenticationConverterThenUsed() + throws Exception { + + this.spring.configLocations(xml("MockJwtDecoder"), xml("MockJwtAuthenticationConverter"), xml("JwtAuthenticationConverter")).autowire(); + + Converter jwtAuthenticationConverter = + (Converter) this.spring.getContext().getBean("jwtAuthenticationConverter"); + when(jwtAuthenticationConverter.convert(any(Jwt.class))) + .thenReturn(new JwtAuthenticationToken(jwt().build(), Collections.emptyList())); + + JwtDecoder jwtDecoder = this.spring.getContext().getBean(JwtDecoder.class); + Mockito.when(jwtDecoder.decode(anyString())).thenReturn(jwt().build()); + + this.mvc.perform(get("/") + .header("Authorization", "Bearer token")) + .andExpect(status().isNotFound()); + + verify(jwtAuthenticationConverter).convert(any(Jwt.class)); + } + + // -- single key + + @Test + public void requestWhenUsingPublicKeyAndValidTokenThenAuthenticates() + throws Exception { + + this.spring.configLocations(xml("SingleKey"), xml("Jwt")).autowire(); + String token = this.token("ValidNoScopes"); + + this.mvc.perform(get("/") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isNotFound()); + } + + @Test + public void requestWhenUsingPublicKeyAndSignatureFailsThenReturnsInvalidToken() + throws Exception { + + this.spring.configLocations(xml("SingleKey"), xml("Jwt")).autowire(); + String token = this.token("WrongSignature"); + + this.mvc.perform(get("/") + .header("Authorization", "Bearer " + token)) + .andExpect(invalidTokenHeader("signature")); + } + + @Test + public void requestWhenUsingPublicKeyAlgorithmDoesNotMatchThenReturnsInvalidToken() + throws Exception { + + this.spring.configLocations(xml("SingleKey"), xml("Jwt")).autowire(); + String token = this.token("WrongAlgorithm"); + + this.mvc.perform(get("/") + .header("Authorization", "Bearer " + token)) + .andExpect(invalidTokenHeader("algorithm")); + } + + // -- opaque + + @Test + public void getWhenIntrospectingThenOk() throws Exception { + this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueToken")).autowire(); + mockRestOperations(json("Active")); + + this.mvc.perform(get("/authenticated") + .header("Authorization", "Bearer token")) + .andExpect(status().isNotFound()); + } + + @Test + public void getWhenIntrospectionFailsThenUnauthorized() throws Exception { + this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueToken")).autowire(); + mockRestOperations(json("Inactive")); + + this.mvc.perform(get("/") + .header("Authorization", "Bearer token")) + .andExpect(status().isUnauthorized()) + .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, + containsString("Provided token isn't active"))); + } + + @Test + public void getWhenIntrospectionLacksScopeThenForbidden() throws Exception { + this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueToken")).autowire(); + mockRestOperations(json("ActiveNoScopes")); + + this.mvc.perform(get("/requires-read-scope") + .header("Authorization", "Bearer token")) + .andExpect(status().isForbidden()) + .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("scope"))); + } + + @Test + public void configureWhenOnlyIntrospectionUrlThenException() { + assertThatCode(() -> this.spring.configLocations(xml("OpaqueTokenHalfConfigured")).autowire()) + .isInstanceOf(BeanDefinitionParsingException.class); + } + + @Test + public void configureWhenIntrospectorAndIntrospectionUriThenError() { + assertThatCode(() -> this.spring.configLocations(xml("OpaqueTokenAndIntrospectionUri")).autowire()) + .isInstanceOf(BeanDefinitionParsingException.class); + } + + // -- authentication manager resolver + + @Test + public void getWhenAuthenticationManagerResolverThenUses() throws Exception { + this.spring.configLocations(xml("AuthenticationManagerResolver")).autowire(); + + AuthenticationManagerResolver authenticationManagerResolver = + this.spring.getContext().getBean(AuthenticationManagerResolver.class); + when(authenticationManagerResolver.resolve(any(HttpServletRequest.class))) + .thenReturn(authentication -> new JwtAuthenticationToken(jwt().build(), Collections.emptyList())); + + this.mvc.perform(get("/") + .header("Authorization", "Bearer token")) + .andExpect(status().isNotFound()); + + verify(authenticationManagerResolver).resolve(any(HttpServletRequest.class)); + } + + @Test + public void getWhenMultipleIssuersThenUsesIssuerClaimToDifferentiate() throws Exception { + this.spring.configLocations(xml("WebServer"), xml("MultipleIssuers")).autowire(); + + MockWebServer server = this.spring.getContext().getBean(MockWebServer.class); + String metadata = "{\n" + + " \"issuer\": \"%s\", \n" + + " \"jwks_uri\": \"%s/.well-known/jwks.json\" \n" + + "}"; + String jwkSet = jwkSet(); + String issuerOne = server.url("/issuerOne").toString(); + String issuerTwo = server.url("/issuerTwo").toString(); + String issuerThree = server.url("/issuerThree").toString(); + String jwtOne = jwtFromIssuer(issuerOne); + String jwtTwo = jwtFromIssuer(issuerTwo); + String jwtThree = jwtFromIssuer(issuerThree); + + mockWebServer(String.format(metadata, issuerOne, issuerOne)); + mockWebServer(jwkSet); + + this.mvc.perform(get("/authenticated") + .header("Authorization", "Bearer " + jwtOne)) + .andExpect(status().isNotFound()); + + mockWebServer(String.format(metadata, issuerTwo, issuerTwo)); + mockWebServer(jwkSet); + + this.mvc.perform(get("/authenticated") + .header("Authorization", "Bearer " + jwtTwo)) + .andExpect(status().isNotFound()); + + mockWebServer(String.format(metadata, issuerThree, issuerThree)); + mockWebServer(jwkSet); + + this.mvc.perform(get("/authenticated") + .header("Authorization", "Bearer " + jwtThree)) + .andExpect(status().isUnauthorized()) + .andExpect(invalidTokenHeader("Invalid issuer")); + } + + // -- In combination with other authentication providers + + @Test + public void requestWhenBasicAndResourceServerEntryPointsThenBearerTokenPresides() + throws Exception { // different from DSL + + this.spring.configLocations(xml("MockJwtDecoder"), xml("BasicAndResourceServer")).autowire(); + + JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); + when(decoder.decode(anyString())).thenThrow(JwtException.class); + + this.mvc.perform(get("/authenticated") + .with(httpBasic("some", "user"))) + .andExpect(status().isUnauthorized()) + .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Basic"))); + + this.mvc.perform(get("/authenticated")) + .andExpect(status().isUnauthorized()) + .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer"))); + + this.mvc.perform(get("/authenticated") + .header("Authorization", "Bearer invalid_token")) + .andExpect(status().isUnauthorized()) + .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer"))); + } + + @Test + public void requestWhenFormLoginAndResourceServerEntryPointsThenSessionCreatedByRequest() + throws Exception { // different from DSL + + this.spring.configLocations(xml("MockJwtDecoder"), xml("FormAndResourceServer")).autowire(); + + JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); + when(decoder.decode(anyString())).thenThrow(JwtException.class); + + MvcResult result = + this.mvc.perform(get("/authenticated")) + .andExpect(status().isUnauthorized()) + .andReturn(); + + assertThat(result.getRequest().getSession(false)).isNotNull(); + + result = + this.mvc.perform(get("/authenticated") + .header("Authorization", "Bearer token")) + .andExpect(status().isUnauthorized()) + .andReturn(); + + assertThat(result.getRequest().getSession(false)).isNull(); + } + + @Test + public void getWhenAlsoUsingHttpBasicThenCorrectProviderEngages() + throws Exception { + + this.spring.configLocations(xml("JwtRestOperations"), xml("BasicAndResourceServer")).autowire(); + mockRestOperations(jwks("Default")); + String token = this.token("ValidNoScopes"); + + this.mvc.perform(get("/authenticated") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isNotFound()); + + this.mvc.perform(get("/authenticated") + .with(httpBasic("user", "password"))) + .andExpect(status().isNotFound()); + } + + // -- Incorrect Configuration + + @Test + public void configuredWhenMissingJwtAuthenticationProviderThenWiringException() { + assertThatCode(() -> this.spring.configLocations(xml("Jwtless")).autowire()) + .isInstanceOf(BeanDefinitionParsingException.class) + .hasMessageContaining("Please select one"); + } + + @Test + public void configureWhenMissingJwkSetUriThenWiringException() { + assertThatCode(() -> this.spring.configLocations(xml("JwtHalfConfigured")).autowire()) + .isInstanceOf(BeanDefinitionParsingException.class) + .hasMessageContaining("Please specify either"); + } + + @Test + public void configureWhenUsingBothAuthenticationManagerResolverAndJwtThenException() { + assertThatCode(() -> this.spring.configLocations(xml("AuthenticationManagerResolverPlusOtherConfig")).autowire()) + .isInstanceOf(BeanDefinitionParsingException.class) + .hasMessageContaining("authentication-manager-resolver-ref"); + } + + @Test + public void validateConfigurationWhenMoreThanOneResourceServerModeThenError() { + OAuth2ResourceServerBeanDefinitionParser parser = new OAuth2ResourceServerBeanDefinitionParser + (null, null, null, null, null); + Element element = mock(Element.class); + when(element.hasAttribute(AUTHENTICATION_MANAGER_RESOLVER_REF)).thenReturn(true); + Element child = mock(Element.class); + ParserContext pc = new ParserContext(mock(XmlReaderContext.class), mock(BeanDefinitionParserDelegate.class)); + + parser.validateConfiguration(element, child, null, pc); + verify(pc.getReaderContext()).error(anyString(), eq(element)); + reset(pc.getReaderContext()); + + parser.validateConfiguration(element, null, child, pc); + verify(pc.getReaderContext()).error(anyString(), eq(element)); + } + + @Test + public void validateConfigurationWhenNoResourceServerModeThenError() { + OAuth2ResourceServerBeanDefinitionParser parser = new OAuth2ResourceServerBeanDefinitionParser + (null, null, null, null, null); + Element element = mock(Element.class); + when(element.hasAttribute(AUTHENTICATION_MANAGER_RESOLVER_REF)).thenReturn(false); + ParserContext pc = new ParserContext(mock(XmlReaderContext.class), mock(BeanDefinitionParserDelegate.class)); + parser.validateConfiguration(element, null, null, pc); + verify(pc.getReaderContext()).error(anyString(), eq(element)); + } + + @Test + public void validateConfigurationWhenBothJwtAttributesThenError() { + JwtBeanDefinitionParser parser = new JwtBeanDefinitionParser(); + Element element = mock(Element.class); + when(element.hasAttribute(JWK_SET_URI)).thenReturn(true); + when(element.hasAttribute(DECODER_REF)).thenReturn(true); + ParserContext pc = new ParserContext(mock(XmlReaderContext.class), mock(BeanDefinitionParserDelegate.class)); + parser.validateConfiguration(element, pc); + verify(pc.getReaderContext()).error(anyString(), eq(element)); + } + + @Test + public void validateConfigurationWhenNoJwtAttributesThenError() { + JwtBeanDefinitionParser parser = new JwtBeanDefinitionParser(); + Element element = mock(Element.class); + when(element.hasAttribute(JWK_SET_URI)).thenReturn(false); + when(element.hasAttribute(DECODER_REF)).thenReturn(false); + ParserContext pc = new ParserContext(mock(XmlReaderContext.class), mock(BeanDefinitionParserDelegate.class)); + parser.validateConfiguration(element, pc); + verify(pc.getReaderContext()).error(anyString(), eq(element)); + } + + @Test + public void validateConfigurationWhenBothOpaqueTokenModesThenError() { + OpaqueTokenBeanDefinitionParser parser = new OpaqueTokenBeanDefinitionParser(); + Element element = mock(Element.class); + when(element.hasAttribute(INTROSPECTION_URI)).thenReturn(true); + when(element.hasAttribute(INTROSPECTOR_REF)).thenReturn(true); + ParserContext pc = new ParserContext(mock(XmlReaderContext.class), mock(BeanDefinitionParserDelegate.class)); + parser.validateConfiguration(element, pc); + verify(pc.getReaderContext()).error(anyString(), eq(element)); + } + + @Test + public void validateConfigurationWhenNoOpaqueTokenModeThenError() { + OpaqueTokenBeanDefinitionParser parser = new OpaqueTokenBeanDefinitionParser(); + Element element = mock(Element.class); + when(element.hasAttribute(INTROSPECTION_URI)).thenReturn(false); + when(element.hasAttribute(INTROSPECTOR_REF)).thenReturn(false); + ParserContext pc = new ParserContext(mock(XmlReaderContext.class), mock(BeanDefinitionParserDelegate.class)); + parser.validateConfiguration(element, pc); + verify(pc.getReaderContext()).error(anyString(), eq(element)); + } + + static class JwtDecoderFactoryBean implements FactoryBean { + private RestOperations rest; + private RSAPublicKey key; + private OAuth2TokenValidator jwtValidator; + + @Override + public JwtDecoder getObject() { + NimbusJwtDecoder decoder; + if (this.key != null) { + decoder = NimbusJwtDecoder.withPublicKey(this.key).build(); + } else { + decoder = NimbusJwtDecoder.withJwkSetUri("https://idp.example.org") + .restOperations(this.rest).build(); + } + if (this.jwtValidator != null) { + decoder.setJwtValidator(this.jwtValidator); + } + return decoder; + } + + @Override + public Class getObjectType() { + return JwtDecoder.class; + } + + public void setJwtValidator(OAuth2TokenValidator jwtValidator) { + this.jwtValidator = jwtValidator; + } + + public void setKey(RSAPublicKey key) { + this.key = key; + } + + public void setRest(RestOperations rest) { + this.rest = rest; + } + } + + static class OpaqueTokenIntrospectorFactoryBean implements FactoryBean { + private RestOperations rest; + + @Override + public OpaqueTokenIntrospector getObject() throws Exception { + return new NimbusOpaqueTokenIntrospector("https://idp.example.org", this.rest); + } + + @Override + public Class getObjectType() { + return OpaqueTokenIntrospector.class; + } + + public void setRest(RestOperations rest) { + this.rest = rest; + } + } + + static class MockWebServerFactoryBean implements FactoryBean, DisposableBean { + private final MockWebServer web = new MockWebServer(); + + @Override + public void destroy() throws Exception { + this.web.shutdown(); + } + + @Override + public MockWebServer getObject() { + return this.web; + } + + @Override + public Class getObjectType() { + return MockWebServer.class; + } + } + + static class MockWebServerPropertiesFactoryBean + implements FactoryBean, DisposableBean { + + MockWebServer web; + + MockWebServerPropertiesFactoryBean(MockWebServer web) { + this.web = web; + } + + @Override + public Properties getObject() { + Properties p = new Properties(); + p.setProperty("jwk-set-uri", this.web.url("").toString()); + p.setProperty("introspection-uri", this.web.url("").toString()); + p.setProperty("issuer-one", this.web.url("issuerOne").toString()); + p.setProperty("issuer-two", this.web.url("issuerTwo").toString()); + return p; + } + + @Override + public Class getObjectType() { + return Properties.class; + } + + @Override + public void destroy() throws Exception { + this.web.shutdown(); + } + } + + static class ClockFactoryBean + implements FactoryBean { + + Clock clock; + + @Override + public Clock getObject() { + return this.clock; + } + + @Override + public Class getObjectType() { + return Clock.class; + } + + public void setMillis(long millis) { + this.clock = Clock.fixed(Instant.ofEpochMilli(millis), ZoneId.systemDefault()); + } + } + + private static ResultMatcher invalidRequestHeader(String message) { + return header().string(HttpHeaders.WWW_AUTHENTICATE, + AllOf.allOf( + new StringStartsWith("Bearer " + + "error=\"invalid_request\", " + + "error_description=\""), + new StringContains(message), + new StringEndsWith(", " + + "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"") + ) + ); + } + + private static ResultMatcher invalidTokenHeader(String message) { + return header().string(HttpHeaders.WWW_AUTHENTICATE, + AllOf.allOf( + new StringStartsWith("Bearer " + + "error=\"invalid_token\", " + + "error_description=\""), + new StringContains(message), + new StringEndsWith(", " + + "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"") + ) + ); + } + + private static ResultMatcher insufficientScopeHeader() { + return header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer " + + "error=\"insufficient_scope\"" + + ", error_description=\"The request requires higher privileges than provided by the access token.\"" + + ", error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""); + } + + private String jwkSet() { + return new JWKSet(new RSAKey.Builder(TestKeys.DEFAULT_PUBLIC_KEY) + .keyID("1").build()).toString(); + } + + private String jwtFromIssuer(String issuer) throws Exception { + Map claims = new HashMap<>(); + claims.put(ISS, issuer); + claims.put(SUB, "test-subject"); + claims.put("scope", "message:read"); + JWSObject jws = new JWSObject( + new JWSHeader.Builder(JWSAlgorithm.RS256).keyID("1").build(), + new Payload(new JSONObject(claims))); + jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY)); + return jws.serialize(); + } + + private void mockWebServer(String response) { + this.web.enqueue(new MockResponse() + .setResponseCode(200) + .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .setBody(response)); + } + + private void mockRestOperations(String response) { + RestOperations rest = this.spring.getContext().getBean(RestOperations.class); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + ResponseEntity entity = new ResponseEntity<>(response, headers, HttpStatus.OK); + Mockito.when(rest.exchange(any(RequestEntity.class), eq(String.class))) + .thenReturn(entity); + } + + private String json(String name) throws IOException { + return resource(name + ".json"); + } + + private String jwks(String name) throws IOException { + return resource(name + ".jwks"); + } + + private String token(String name) throws IOException { + return resource(name + ".token"); + } + + private String resource(String suffix) throws IOException { + String name = this.getClass().getSimpleName() + "-" + suffix; + ClassPathResource resource = new ClassPathResource(name, this.getClass()); + try ( BufferedReader reader = new BufferedReader(new FileReader(resource.getFile())) ) { + return reader.lines().collect(Collectors.joining()); + } + } + + private T bean(Class beanClass) { + return this.spring.getContext().getBean(beanClass); + } + + private String xml(String configName) { + return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; + } +} diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AccessDeniedHandler.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AccessDeniedHandler.xml new file mode 100644 index 00000000000..9b9bf55425f --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AccessDeniedHandler.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Active.json b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Active.json new file mode 100644 index 00000000000..3e3dfac24b5 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Active.json @@ -0,0 +1,6 @@ +{ + "active" : true, + "sub": "test-subject", + "scope": "message:read", + "exp": 4683883211 +} diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ActiveNoScopes.json b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ActiveNoScopes.json new file mode 100644 index 00000000000..e1e2438e2b0 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ActiveNoScopes.json @@ -0,0 +1,5 @@ +{ + "active" : true, + "sub": "test-subject", + "exp": 4683883211 +} diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AllowBearerTokenInBody.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AllowBearerTokenInBody.xml new file mode 100644 index 00000000000..198702f610a --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AllowBearerTokenInBody.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AllowBearerTokenInQuery.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AllowBearerTokenInQuery.xml new file mode 100644 index 00000000000..58432b71708 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AllowBearerTokenInQuery.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AlwaysSessionCreation.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AlwaysSessionCreation.xml new file mode 100644 index 00000000000..1234ca763d4 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AlwaysSessionCreation.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationEntryPoint.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationEntryPoint.xml new file mode 100644 index 00000000000..fc4ae1cf247 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationEntryPoint.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationManagerResolver.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationManagerResolver.xml new file mode 100644 index 00000000000..7b9fcb8d029 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationManagerResolver.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationManagerResolverPlusOtherConfig.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationManagerResolverPlusOtherConfig.xml new file mode 100644 index 00000000000..dbf45c1bd5e --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationManagerResolverPlusOtherConfig.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-BasicAndResourceServer.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-BasicAndResourceServer.xml new file mode 100644 index 00000000000..731cf329a57 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-BasicAndResourceServer.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-BearerTokenResolver.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-BearerTokenResolver.xml new file mode 100644 index 00000000000..73dfbc5340b --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-BearerTokenResolver.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Default.jwks b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Default.jwks new file mode 100644 index 00000000000..ce5e6fbf2b4 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Default.jwks @@ -0,0 +1 @@ +{"keys":[{"p":"49neceJFs8R6n7WamRGy45F5Tv0YM-R2ODK3eSBUSLOSH2tAqjEVKOkLE5fiNA3ygqq15NcKRadB2pTVf-Yb5ZIBuKzko8bzYIkIqYhSh_FAdEEr0vHF5fq_yWSvc6swsOJGqvBEtuqtJY027u-G2gAQasCQdhyejer68zsTn8M","kty":"RSA","q":"tWR-ysspjZ73B6p2vVRVyHwP3KQWL5KEQcdgcmMOE_P_cPs98vZJfLhxobXVmvzuEWBpRSiqiuyKlQnpstKt94Cy77iO8m8ISfF3C9VyLWXi9HUGAJb99irWABFl3sNDff5K2ODQ8CmuXLYM25OwN3ikbrhEJozlXg_NJFSGD4E","d":"FkZHYZlw5KSoqQ1i2RA2kCUygSUOf1OqMt3uomtXuUmqKBm_bY7PCOhmwbvbn4xZYEeHuTR8Xix-0KpHe3NKyWrtRjkq1T_un49_1LLVUhJ0dL-9_x0xRquVjhl_XrsRXaGMEHs8G9pLTvXQ1uST585gxIfmCe0sxPZLvwoic-bXf64UZ9BGRV3lFexWJQqCZp2S21HfoU7wiz6kfLRNi-K4xiVNB1gswm_8o5lRuY7zB9bRARQ3TS2G4eW7p5sxT3CgsGiQD3_wPugU8iDplqAjgJ5ofNJXZezoj0t6JMB_qOpbrmAM1EnomIPebSLW7Ky9SugEd6KMdL5lW6AuAQ","e":"AQAB","use":"sig","kid":"one","qi":"wdkFu_tV2V1l_PWUUimG516Zvhqk2SWDw1F7uNDD-Lvrv_WNRIJVzuffZ8WYiPy8VvYQPJUrT2EXL8P0ocqwlaSTuXctrORcbjwgxDQDLsiZE0C23HYzgi0cofbScsJdhcBg7d07LAf7cdJWG0YVl1FkMCsxUlZ2wTwHfKWf-v4","dp":"uwnPxqC-IxG4r33-SIT02kZC1IqC4aY7PWq0nePiDEQMQWpjjNH50rlq9EyLzbtdRdIouo-jyQXB01K15-XXJJ60dwrGLYNVqfsTd0eGqD1scYJGHUWG9IDgCsxyEnuG3s0AwbW2UolWVSsU2xMZGb9PurIUZECeD1XDZwMp2s0","dq":"hra786AunB8TF35h8PpROzPoE9VJJMuLrc6Esm8eZXMwopf0yhxfN2FEAvUoTpLJu93-UH6DKenCgi16gnQ0_zt1qNNIVoRfg4rw_rjmsxCYHTVL3-RDeC8X_7TsEySxW0EgFTHh-nr6I6CQrAJjPM88T35KHtdFATZ7BCBB8AE","n":"oXJ8OyOv_eRnce4akdanR4KYRfnC2zLV4uYNQpcFn6oHL0dj7D6kxQmsXoYgJV8ZVDn71KGmuLvolxsDncc2UrhyMBY6DVQVgMSVYaPCTgW76iYEKGgzTEw5IBRQL9w3SRJWd3VJTZZQjkXef48Ocz06PGF3lhbz4t5UEZtdF4rIe7u-977QwHuh7yRPBQ3sII-cVoOUMgaXB9SHcGF2iZCtPzL_IffDUcfhLQteGebhW8A6eUHgpD5A1PQ-JCw_G7UOzZAjjDjtNM2eqm8j-Ms_gqnm4MiCZ4E-9pDN77CAAPVN7kuX6ejs9KBXpk01z48i9fORYk9u7rAkh1HuQw"}]} diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Empty.jwks b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Empty.jwks new file mode 100644 index 00000000000..9d15e791b4f --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Empty.jwks @@ -0,0 +1 @@ +{"keys":[]} diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Expired.token b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Expired.token new file mode 100644 index 00000000000..8010d048938 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Expired.token @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE1MzAyMzE3MTB9.c8vXYFwe1cBuglaZbmZFXJOmLsu_IQf-OsOiiOGhEJYOzu6h6v_qEzf2xxbu5TSvwAERmDITUSK41UIIvgU75WebtgilNnTR83B_gPM-7_FI2FLzlgVH7WayzvbYTQqepE_ZUMLFkGkK4r-dRiOyB9_cfl6jq_b5hE_biH1qrgPQrjlEhU8YxeK2EE05wsARLzyjoIYifkStjPC6rC-MLFIVk5JoITNzkTh7zYYSWtKWEgwd8S_vluVtJaPk-yKPb4tXcFRzCFl_qd7aCF8_LHyhw-4wvhWRIi8DmQmRU_a1RxR0mi-UCp0jMwmBZxxkSdqJ4l_EHI1yVqpgnbMLDw diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ExpiredJwtClockSkew.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ExpiredJwtClockSkew.xml new file mode 100644 index 00000000000..c132c7b98e0 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ExpiredJwtClockSkew.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ExpiresAt4687177990.token b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ExpiresAt4687177990.token new file mode 100644 index 00000000000..df5ab8ac23a --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ExpiresAt4687177990.token @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjQ2ODcxNzc5OTB9.RRQvqIZzLweq0iwWUZk1Dpiz6iUmT4bAVhGWqvWNWK3UwJ6aBIYsCRhdVeKQp-g1TxXovMALeAu_2oPmV0wOEEanesAKxjKYcJZQIe8HnVqgug6Ibs04uQ1mJ4RgfntPM-ebsJs-2tjFFkLEYJSkpq2o6SEFW9jBJyW8b8C5UJJahqynonA-Dw5GH1nin5bhhliLuFOmu0Ityt0uJ1Y_vuGsSA-ltVcY52jE4x6GH9NQxLX4ceO1bHSOmdspBoGsE_yo9-zsQw0g1_Iy7uqEjos3xrrboH6Z_u7pRL7AQJ7GNzZlinjYYPANQbYknieZD6beddTK7lvr4DYiPBmXzA diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-FormAndResourceServer.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-FormAndResourceServer.xml new file mode 100644 index 00000000000..3cc5c9b1489 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-FormAndResourceServer.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Inactive.json b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Inactive.json new file mode 100644 index 00000000000..1e01d475d8c --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Inactive.json @@ -0,0 +1,3 @@ +{ + "active" : false +} diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-IntrospectionUri.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-IntrospectionUri.xml new file mode 100644 index 00000000000..dd615d32d69 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-IntrospectionUri.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwkSetUri.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwkSetUri.xml new file mode 100644 index 00000000000..6b4241b25b2 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwkSetUri.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Jwt.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Jwt.xml new file mode 100644 index 00000000000..f3b38aa8770 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Jwt.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtAuthenticationConverter.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtAuthenticationConverter.xml new file mode 100644 index 00000000000..e59c7423f09 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtAuthenticationConverter.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtDecoderAndJwkSetUri.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtDecoderAndJwkSetUri.xml new file mode 100644 index 00000000000..41957ba36fa --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtDecoderAndJwkSetUri.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtHalfConfigured.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtHalfConfigured.xml new file mode 100644 index 00000000000..d45c76a2ced --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtHalfConfigured.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtRestOperations.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtRestOperations.xml new file mode 100644 index 00000000000..934e9809fbc --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtRestOperations.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Jwtless.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Jwtless.xml new file mode 100644 index 00000000000..eac1fae75c2 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Jwtless.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Kid.token b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Kid.token new file mode 100644 index 00000000000..4f53fdea73f --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Kid.token @@ -0,0 +1 @@ +eyJraWQiOiJvbmUiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJleHAiOjQ2ODM4ODM1NzJ9.UhukjNEowC5lLCccvdjCUJad5J9FGNModegMZGe9qKIbXxmfseTttZUNn3_K_6aNCfimtmRktCRbw3fUTcje2TFJOJ6SmomLcQyjq7S41Wq6oBSA2fdqOOU4vNvrk8_pSExsSyN9bfWiJ51I8Agzbq5eUDNo_HEpaJZimrIe9f2_njU1GxvAWsq_h4UhHEgPPb3kY9kN9hVYX_oShhh7JxbLJBnfsKBOKGEWOsE65GlmDgQV4om6RGjJaz6jFHKJTCpH08ADA3j2dqT0LNy4PrUmbnjPjWVtSQJkGcgUkcQW6qz0K86ZfJZZng_iB2VadRm5qO-99ySKmlxa5A-_Iw diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MalformedPayload.token b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MalformedPayload.token new file mode 100644 index 00000000000..a6c9be5a210 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MalformedPayload.token @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiJ9.eyJuYmYiOnt9LCJleHAiOjQ2ODM4OTEyMTl9.kpdv6ZXyYszZUzA4mJpviCBPzPftk6tIbIn5OoMuM09MKZCUCAFD8Y1tDmjzbWdkR_5CYiFMvSLq6DzAlugtGRAShc93dmDlyZmhcct2G477FxWaRKbtmFDjzuCjGyn7xHWpS7Wz6-Ngb-JyGI2m7FxXCgCpiYYBl-4-ONTuAT0fArJi_voA8K6YLnnjEjEprI3wsQRoS3Twa_fVdGkpMNlOGsQOqmlfjDrXpyfiANOe_ZztHxbDtJEZ9zfELxx9fzkZgTL1fD2Sj6HueDU-tMt-6IaGpBCLsg7d85RK001-U9u3Ph9awQC4QZK-8-F9OUUCY5RNcRJ57KEh9PjUfA diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockBearerTokenResolver.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockBearerTokenResolver.xml new file mode 100644 index 00000000000..421e5f7b5d8 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockBearerTokenResolver.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwkSetUri.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwkSetUri.xml new file mode 100644 index 00000000000..8c89f87422c --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwkSetUri.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwtAuthenticationConverter.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwtAuthenticationConverter.xml new file mode 100644 index 00000000000..ab9785e7ea0 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwtAuthenticationConverter.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwtDecoder.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwtDecoder.xml new file mode 100644 index 00000000000..341e1f1a6a6 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwtDecoder.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwtValidator.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwtValidator.xml new file mode 100644 index 00000000000..764f5aaefec --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwtValidator.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockOpaqueTokenIntrospector.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockOpaqueTokenIntrospector.xml new file mode 100644 index 00000000000..49f18973080 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockOpaqueTokenIntrospector.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MultipleIssuers.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MultipleIssuers.xml new file mode 100644 index 00000000000..438714966af --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MultipleIssuers.xml @@ -0,0 +1,41 @@ + + + + + + + + + ${issuer-one} + ${issuer-two} + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueToken.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueToken.xml new file mode 100644 index 00000000000..e26489535e3 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueToken.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenAndIntrospectionUri.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenAndIntrospectionUri.xml new file mode 100644 index 00000000000..21b470d7cb2 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenAndIntrospectionUri.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenHalfConfigured.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenHalfConfigured.xml new file mode 100644 index 00000000000..86aa4f72d24 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenHalfConfigured.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenRestOperations.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenRestOperations.xml new file mode 100644 index 00000000000..5b855415f72 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenRestOperations.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-SingleKey.pub b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-SingleKey.pub new file mode 100644 index 00000000000..42e5d78e255 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-SingleKey.pub @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoXJ8OyOv/eRnce4akdan +R4KYRfnC2zLV4uYNQpcFn6oHL0dj7D6kxQmsXoYgJV8ZVDn71KGmuLvolxsDncc2 +UrhyMBY6DVQVgMSVYaPCTgW76iYEKGgzTEw5IBRQL9w3SRJWd3VJTZZQjkXef48O +cz06PGF3lhbz4t5UEZtdF4rIe7u+977QwHuh7yRPBQ3sII+cVoOUMgaXB9SHcGF2 +iZCtPzL/IffDUcfhLQteGebhW8A6eUHgpD5A1PQ+JCw/G7UOzZAjjDjtNM2eqm8j ++Ms/gqnm4MiCZ4E+9pDN77CAAPVN7kuX6ejs9KBXpk01z48i9fORYk9u7rAkh1Hu +QwIDAQAB +-----END PUBLIC KEY----- diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-SingleKey.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-SingleKey.xml new file mode 100644 index 00000000000..cba71824548 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-SingleKey.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-TooEarly.token b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-TooEarly.token new file mode 100644 index 00000000000..780cf108364 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-TooEarly.token @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiJ9.eyJuYmYiOjQ2ODM4OTI2NTUsImV4cCI6NDY4Mzg5MjY1NX0.MIaECJrmYjAByKNJoWHlP5ewg2xiW7GIxL8Vepp3ZIKf_jjM2OSMQlAWGmfD3Kf3bfesvSI7glw5qg_ZIv4FdIPaTvnmLRjWQkpk-QiLTJr_HM2wWeNbUJ1zciGWQlWAvabtQuyeGt1dsfQq53QLVNpvuioYdVg-gz_76uwDTxCKQU_99ksQhMMJsYJVDA_-uWGTzBANszcZykqwWFMaoXF4lkVPK4U68n18ISBB761wFusUCtyGWzwevX7wBAEJxcRy6ZVk3h7GyxZBsbRAd5fPn3dPMxNvL_CEp5jUYSAH-arAdDkvAph5Vk1yXof7FFRcffJpAy76HC66hR2JQA diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-TwoKeys.jwks b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-TwoKeys.jwks new file mode 100644 index 00000000000..16d3a00859f --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-TwoKeys.jwks @@ -0,0 +1 @@ +{"keys":[{"p":"49neceJFs8R6n7WamRGy45F5Tv0YM-R2ODK3eSBUSLOSH2tAqjEVKOkLE5fiNA3ygqq15NcKRadB2pTVf-Yb5ZIBuKzko8bzYIkIqYhSh_FAdEEr0vHF5fq_yWSvc6swsOJGqvBEtuqtJY027u-G2gAQasCQdhyejer68zsTn8M","kty":"RSA","q":"tWR-ysspjZ73B6p2vVRVyHwP3KQWL5KEQcdgcmMOE_P_cPs98vZJfLhxobXVmvzuEWBpRSiqiuyKlQnpstKt94Cy77iO8m8ISfF3C9VyLWXi9HUGAJb99irWABFl3sNDff5K2ODQ8CmuXLYM25OwN3ikbrhEJozlXg_NJFSGD4E","d":"FkZHYZlw5KSoqQ1i2RA2kCUygSUOf1OqMt3uomtXuUmqKBm_bY7PCOhmwbvbn4xZYEeHuTR8Xix-0KpHe3NKyWrtRjkq1T_un49_1LLVUhJ0dL-9_x0xRquVjhl_XrsRXaGMEHs8G9pLTvXQ1uST585gxIfmCe0sxPZLvwoic-bXf64UZ9BGRV3lFexWJQqCZp2S21HfoU7wiz6kfLRNi-K4xiVNB1gswm_8o5lRuY7zB9bRARQ3TS2G4eW7p5sxT3CgsGiQD3_wPugU8iDplqAjgJ5ofNJXZezoj0t6JMB_qOpbrmAM1EnomIPebSLW7Ky9SugEd6KMdL5lW6AuAQ","e":"AQAB","use":"sig","kid":"one","qi":"wdkFu_tV2V1l_PWUUimG516Zvhqk2SWDw1F7uNDD-Lvrv_WNRIJVzuffZ8WYiPy8VvYQPJUrT2EXL8P0ocqwlaSTuXctrORcbjwgxDQDLsiZE0C23HYzgi0cofbScsJdhcBg7d07LAf7cdJWG0YVl1FkMCsxUlZ2wTwHfKWf-v4","dp":"uwnPxqC-IxG4r33-SIT02kZC1IqC4aY7PWq0nePiDEQMQWpjjNH50rlq9EyLzbtdRdIouo-jyQXB01K15-XXJJ60dwrGLYNVqfsTd0eGqD1scYJGHUWG9IDgCsxyEnuG3s0AwbW2UolWVSsU2xMZGb9PurIUZECeD1XDZwMp2s0","dq":"hra786AunB8TF35h8PpROzPoE9VJJMuLrc6Esm8eZXMwopf0yhxfN2FEAvUoTpLJu93-UH6DKenCgi16gnQ0_zt1qNNIVoRfg4rw_rjmsxCYHTVL3-RDeC8X_7TsEySxW0EgFTHh-nr6I6CQrAJjPM88T35KHtdFATZ7BCBB8AE","n":"oXJ8OyOv_eRnce4akdanR4KYRfnC2zLV4uYNQpcFn6oHL0dj7D6kxQmsXoYgJV8ZVDn71KGmuLvolxsDncc2UrhyMBY6DVQVgMSVYaPCTgW76iYEKGgzTEw5IBRQL9w3SRJWd3VJTZZQjkXef48Ocz06PGF3lhbz4t5UEZtdF4rIe7u-977QwHuh7yRPBQ3sII-cVoOUMgaXB9SHcGF2iZCtPzL_IffDUcfhLQteGebhW8A6eUHgpD5A1PQ-JCw_G7UOzZAjjDjtNM2eqm8j-Ms_gqnm4MiCZ4E-9pDN77CAAPVN7kuX6ejs9KBXpk01z48i9fORYk9u7rAkh1HuQw"},{"p":"_CI5g5In9T4ZgakV1i62UU6yjorEr5t2URHfRYqxN7S4aKsQOzggcPoqa78xRj8PAPuf3P0ArPEAHdS6bFK7RLrFXdvyEmSNTJa1gcLCf2Zmep8bsrhrCvh6seZNvfrSMV0ULmk0B75Fs8mqE7nwcIbPtBYkinlSIw-sKRv62DM","kty":"RSA","q":"pqfexT3HBAagH-iydGsWbjG6CcYyvSQZdFtUu4LIOBCYVA0dvkN9s7uU1eoevHN_ksf-hfrF5AQH0a5P0dIJ2pp1bFa9uo9DJ7khU9sIBk9_o8nST2QLHwPQmGTW8vVlcSF7Vffvzm2fV3cQ3dfI5lvtkqfX_Z3WkF8UjFjADe8","d":"FzB5xChO8e89JisxSueY5j1RUBmatIAs_8Z3LUHOw16GlAhBhbSNl-7bXkbcUWLq9M1zTLCD91SSZXBohf9j1ebqWnbjMqQmdkxlQcVRoKcnMJ5YBabCTMBXghQnJetUMh6x6hXRnR1CSBNRdZPf-K2bnxL3xRNRSfY_7bjpb_q5pyUsK66ugSKwuEOUDNf1ttOZi4PBTsxWMDyXi_7fNFjl-B831uWNDVwdY4j68PVwGPT87zjZYjZRTZXB4ILUP11ztw4s3s_bU1Lj0PeZJsA5rmjU1iBzqCNdzgYxNlfV7M62VCkE1Wtd6M97jtysiT-5wQUMxNugoOTc9thc1Q","e":"AQAB","use":"sig","kid":"two","qi":"bnGriiVGVea9vSaN_48YYTEoKYM1kF7TrCRKERkMWdi4EHF7pZNWBv8arxaLUzElllvtGlVTNwkZlG0gOhXBoLYbcfqVikDklkBxtsuZEBKgvX7zFlDIBlNjh98lcZqDqz7Rqwr-tavxTCq2LNNlK6x-dYL61Agw_LOilYqbSfA","dp":"MmT4z-ZnnCn0WSkdlziw8iFjqP_tfhf5lwyWbsTg1PyHG0yNqvh1637k-bI2PA8ghZbFhhr_hpGI7210cXA7w-n8xtzOToTQhS1eS_hMfcBO3VVt6NPZeVDe3S3l_gHi_0DWZsxaPO336o51MwooF6WqYBlI5nCHTUC1rWXNRmc","dq":"dd_ybywc4boV87vQzQsZWGOPpG4tYR5xap1WtzHvj8gdFgYY7YQrGr8orIzlpIFE0Hroibcv1PEM3sAd8NhQ4--v8isAEz5VT3lgG0Gm0V_VdfG_8StfulYmakOYzUvIrlXyOIIfebCLrX-nzGFd1aFbzgktelLzejXmAMadQL0","n":"pCOHBsaoxlt9-qVE_INhrbkmxm7WqwEeqUBBIgHvm_JzXbmJ4iQzVF5tzAbRayxUmPbZ4E80R5HlIC2CQ7yyweTbIIWIw_TcQzXR4u3twEN1awP4s1n-00Eeurr-s9c_txZQQiDkyrCMYc9vlmsneFfubyoTvg9h_rckd8w34AyE8-wxgBRqUbm1x4ozcVmUJHkaPbQfbhIighl7osoQ4t_wXjAhTN_c9XttVjXlRwqVYPFNYUcC9GoaXWJRHjydHNFeBboOZY3E8ND6DbJ4nVtxydpUQSjTC-N-wQmhKmtYadd2hh2yywvtXpL5Q98XSphrrIHK-GWY0j8kimpunQ"}]} diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-UnexpiredJwtClockSkew.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-UnexpiredJwtClockSkew.xml new file mode 100644 index 00000000000..d4119e7ce7a --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-UnexpiredJwtClockSkew.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Unsigned.token b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Unsigned.token new file mode 100644 index 00000000000..f0b557652d3 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Unsigned.token @@ -0,0 +1 @@ +eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJuYmYiOjE1MzAzMDA4MzgsImV4cCI6MjE0NjAwMzE5OSwiaWF0IjoxNTMwMzAwODM4LCJ0eXAiOiJKV1QifQ. diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ValidMessageReadScope.token b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ValidMessageReadScope.token new file mode 100644 index 00000000000..0020772ffe3 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ValidMessageReadScope.token @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJzY29wZSI6Im1lc3NhZ2U6cmVhZCIsImV4cCI6NDY4Mzg4MzIxMX0.cM7Eq9H20503czYVy1aVo8MqTQd8YsYGpv_lAV4PKr3y8NgvvosNjCSUs8rrGjQ0Sp3c4iXK6UVXq8pOJVeWXbSZa1IKAsIhiMIcg2xPFM6e71MVdX4bo255Yh8Nuh0p3xxP9isK_iAKNdMuVBOGfe9KATlmp2dOi0OpAjwSmxPJD1A7AC5f62YIe3Yx2gO6mbfANZJWQ7TxlUuCT_D5FEqg2FfYFqlFaluqWd_2X-esIsiDTxa1R9oF5XwgT6tsgvS7iYSiJw_uNKX0yU4eyLzYuIhnN_hVsr4jOZqPlsqCrkEohOGZg_Jir-7tLxZu0PqoH4ejC24FeDtC9xVa0w diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ValidMessageWriteScp.token b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ValidMessageWriteScp.token new file mode 100644 index 00000000000..7cdb29ea59e --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ValidMessageWriteScp.token @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJzY3AiOlsibWVzc2FnZTp3cml0ZSJdLCJleHAiOjQ2ODM4OTY0OTl9.mxAFzoNjjo-7E4D_XYVme69Y7F-J--q41x6lHDTSOxzVNfQqtJ-U-N4pn7St5jElm9y3mSUxTtmwCnukaVVZkeI8aJjUc8V8nxUAsiZIDvQWjr9uW4xUIcE6MiwC0A9rhY-3I87u6No-KBTxyT80zLnCjtS2XpTId-NSd3vcYmM7Vzn4-8KoR_m-7XrjvrO69HlRrH2uUAXGnr1sn6vLp7YruupqKrHqa0e9pIpN-VRzC8Bx2LQP9mVMlQy4b1hx5MdjOTV3HUSnWiT-93z4rTMOoHScKDwmzFYoS7e00F5hyd4jzbpHdpDKnjLdwPQYz_HCmQ5MV21-Q4Q1jparIg diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ValidNoScopes.token b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ValidNoScopes.token new file mode 100644 index 00000000000..7d4a3251d24 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ValidNoScopes.token @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJleHAiOjQ2ODM4Mjg2NzR9.LV_i9lzN_gAB2MUuZHJKm2tOfa3xWq_qfE2lx67eoYJZsY_20Ma98A3Hh2k0wnb_mNn6jfQhXbqvUy1llmQtsx3gMNhN2Axfe3UccSKYEb2Ow5OFlrMFYby1d_D4GfXKUFKq8jyMWVlrjk_XrfJyfzeo0MyZVzURSOXv1Ehbl5-xAS_N72jiAI7cIHlHGm93Hwdk8h7Tkkf_5t2dOMJM0mh0fOT9ou3J2_ngaNDfvlAmBLxHQiJ6JrFH5njqe4lSBTxJocDcgZwGVKd0WvV4W-jwA267tZjssDFmS3xZ9hoDO_M-EjlOiEPuWLd9nQCGJpBJ3z3WeC4qrKYghHTNLA diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-WebServer.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-WebServer.xml new file mode 100644 index 00000000000..6cbc1c5c1e0 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-WebServer.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-WrongAlgorithm.token b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-WrongAlgorithm.token new file mode 100644 index 00000000000..2708ac1e45c --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-WrongAlgorithm.token @@ -0,0 +1 @@ +eyJhbGciOiJSUzUxMiJ9.eyJleHAiOjQ2ODczNDQ2NzN9.hvVUW_xwUXd7nGm27E5tLTZ21x64YjP0o-TMW6t_bOkfG1Vp1AMEX8fXvSqeG0vK8TWiB2_keOGtH-eFmAGBEYXq1o1zj1BgMHeaZAVio9n-77DkTzQ7CiOF5M1M7B_Ng4K8ra4DpieZZXVjHTWsuOiU1hWoI1tIna8VucAxZln-oh7PkrYmgwFTlsL2Z9aZZYN_X7ECyRQDf3lRrLwr4Go_XpJ5i9F-GT5LvUYa42uggGjvq_frfb0t5wcmPgjtqiE6l2mnrYFjjKTq1nQRYrJ5wFWOHUTRxNsGS8PwrNxzh6JW1ZZTS0n_JIOvSh__w0WAB241QLoKBx4AETMLQA diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-WrongSignature.token b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-WrongSignature.token new file mode 100644 index 00000000000..61389f9c71f --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-WrongSignature.token @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjQ2ODczNDQ0MTd9.jfqDyHvpRXWF6KaRQS3cGT0HUSix09xwTPvUCtg9UJ2QR1Rx4MclGCli3yIHNm0vsRed4s-gZduVGfbj7enyKnpXCZE7dNxZENfm7P54OfJmlyJY3DvhzlyH_rtuOD4c_Q88J9uELd_pghikLlMtu8090UzTtwRfdo_JsDfMRAcDeYq7TTaL60w3AVarStwZAAy_dpi6bTEanm5hwkz4-deA4Bz4KentpvlcwB01IXw9DVYrW1lpzLgycwk_VbCK_LA1hjFnnjc3OnQaxvqydrBAlFD3ziklVAxGnKnrYzppixdwwztuga4XS36OhicIGXEkMf3oT3nzgcR309DP_A diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc index 8664c1f6caf..a4b7b65c788 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc @@ -166,6 +166,7 @@ The default value is true. * <> * <> * <> +* <> * <> * <> * <> @@ -1160,6 +1161,90 @@ The URI used to retrieve the https://tools.ietf.org/html/rfc7517[JSON Web Key (J * **issuer-uri** The URI used to initially configure a `ClientRegistration` using discovery of an OpenID Connect Provider's https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[Configuration endpoint] or an Authorization Server's https://tools.ietf.org/html/rfc8414#section-3[Metadata endpoint]. +[[nsa-oauth2-resource-server]] +==== +Adds a `BearerTokenAuthenticationFilter`, `BearerTokenAuthenticationEntryPoint`, and `BearerTokenAccessDeniedHandler` to the configuration. +In addition, either `` or `` must be specified. + +[[nsa-oauth2-resource-server-parents]] +===== Parents Elements of + +* <> + +[[nsa-oauth2-resource-server-children]] +===== Child Elements of + +* <> +* <> + +[[nsa-oauth2-resource-server-attributes]] +===== Attributes + +[[nsa-oauth2-resource-server-authentication-manager-resolver-ref]] +* **authentication-manager-resolver-ref** +Reference to an `AuthenticationManagerResolver` which will resolve the `AuthenticationManager` at request time + +[[nsa-oauth2-resource-server-bearer-token-resolver-ref]] +* **bearer-token-resolver-ref** +Reference to a `BearerTokenResolver` which will retrieve the bearer token from the request + +[[nsa-oauth2-resource-server-entry-point-ref]] +* **entry-point-ref** +Reference to a `AuthenticationEntryPoint` which will handle unauthorized requests + +[[nsa-jwt]] +==== +Represents an OAuth 2.0 Resource Server that will authorize JWTs + + +[[nsa-jwt-parents]] +===== Parent Elements of + +* <> + + +[[nsa-jwt-attributes]] +===== Attributes + +[[nsa-jwt-jwt-authentication-converter-ref]] +* **jwt-authentication-converter-ref** +Reference to a `Converter` + +[[nsa-jwt-decoder-ref]] +* **jwt-decoder-ref** +Reference to a `JwtDecoder`. This is a larger component that overrides `jwk-set-uri` + +[[nsa-jwt-jwk-set-uri]] +* **jwk-set-uri** +The JWK Set Uri used to load signing verification keys from an OAuth 2.0 Authorization Server + +[[nsa-opaque-token]] +==== +Represents an OAuth 2.0 Resource Server that will authorize opaque tokens + +[[nsa-opaque-token-parents]] +===== Parent Elements of + +* <> + +[[nsa-opaque-token-attributes]] +===== Attributes + +[[nsa-opaque-token-introspector-ref]] +* **introspector-ref** +Reference to an `OpaqueTokenIntrospector`. This is a larger component that overrides `introspection-uri`, `client-id`, and `client-secret`. + +[[nsa-opaque-token-introspection-uri]] +* **introspection-uri** +The Introspection Uri used to introspect the details of an opaque token. Should be accompanied with a `client-id` and `client-secret`. + +[[nsa-opaque-token-client-id]] +* **client-id** +The Client Id to use for client authentication against the provided `introspection-uri`. + +[[nsa-opaque-token-client-secret]] +* **client-secret** +The Client Secret to use for client authentication against the provided `introspection-uri`. [[nsa-http-basic]] ====