2
0
Эх сурвалжийг харах

Add Resource Server XML Support

Fixes gh-5185
Josh Cummings 5 жил өмнө
parent
commit
e97396b9c7
58 өөрчлөгдсөн 3030 нэмэгдсэн , 2 устгасан
  1. 4 0
      config/src/main/java/org/springframework/security/config/Elements.java
  2. 24 1
      config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java
  3. 358 0
      config/src/main/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParser.java
  4. 43 1
      config/src/main/resources/org/springframework/security/config/spring-security-5.3.rnc
  5. 98 0
      config/src/main/resources/org/springframework/security/config/spring-security-5.3.xsd
  6. 1239 0
      config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java
  7. 38 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AccessDeniedHandler.xml
  8. 6 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Active.json
  9. 5 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ActiveNoScopes.json
  10. 37 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AllowBearerTokenInBody.xml
  11. 37 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AllowBearerTokenInQuery.xml
  12. 32 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AlwaysSessionCreation.xml
  13. 37 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationEntryPoint.xml
  14. 35 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationManagerResolver.xml
  15. 40 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationManagerResolverPlusOtherConfig.xml
  16. 36 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-BasicAndResourceServer.xml
  17. 32 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-BearerTokenResolver.xml
  18. 1 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Default.jwks
  19. 1 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Empty.jwks
  20. 1 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Expired.token
  21. 42 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ExpiredJwtClockSkew.xml
  22. 1 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ExpiresAt4687177990.token
  23. 36 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-FormAndResourceServer.xml
  24. 3 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Inactive.json
  25. 41 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-IntrospectionUri.xml
  26. 37 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwkSetUri.xml
  27. 35 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Jwt.xml
  28. 32 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtAuthenticationConverter.xml
  29. 41 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtDecoderAndJwkSetUri.xml
  30. 35 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtHalfConfigured.xml
  31. 32 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtRestOperations.xml
  32. 33 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Jwtless.xml
  33. 1 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Kid.token
  34. 1 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MalformedPayload.token
  35. 27 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockBearerTokenResolver.xml
  36. 33 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwkSetUri.xml
  37. 27 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwtAuthenticationConverter.xml
  38. 27 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwtDecoder.xml
  39. 37 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwtValidator.xml
  40. 27 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockOpaqueTokenIntrospector.xml
  41. 41 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MultipleIssuers.xml
  42. 33 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueToken.xml
  43. 36 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenAndIntrospectionUri.xml
  44. 32 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenHalfConfigured.xml
  45. 32 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenRestOperations.xml
  46. 9 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-SingleKey.pub
  47. 30 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-SingleKey.xml
  48. 1 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-TooEarly.token
  49. 0 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-TwoKeys.jwks
  50. 42 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-UnexpiredJwtClockSkew.xml
  51. 1 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Unsigned.token
  52. 1 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ValidMessageReadScope.token
  53. 1 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ValidMessageWriteScp.token
  54. 1 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ValidNoScopes.token
  55. 31 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-WebServer.xml
  56. 1 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-WrongAlgorithm.token
  57. 1 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-WrongSignature.token
  58. 85 0
      docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc

+ 4 - 0
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 CORS = "cors";
 	public static final String CSRF = "csrf";
 	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 WEBSOCKET_MESSAGE_BROKER = "websocket-message-broker";
 	public static final String INTERCEPT_MESSAGE = "intercept-message";
 	public static final String INTERCEPT_MESSAGE = "intercept-message";
 
 

+ 24 - 1
config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java

@@ -68,6 +68,7 @@ import org.springframework.util.xml.DomUtils;
 
 
 import static org.springframework.security.config.http.SecurityFilters.ANONYMOUS_FILTER;
 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.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.EXCEPTION_TRANSLATION_FILTER;
 import static org.springframework.security.config.http.SecurityFilters.FORM_LOGIN_FILTER;
 import static org.springframework.security.config.http.SecurityFilters.FORM_LOGIN_FILTER;
 import static org.springframework.security.config.http.SecurityFilters.LOGIN_PAGE_FILTER;
 import static org.springframework.security.config.http.SecurityFilters.LOGIN_PAGE_FILTER;
@@ -139,6 +140,8 @@ final class AuthenticationConfigBuilder {
 	private BeanMetadataElement mainEntryPoint;
 	private BeanMetadataElement mainEntryPoint;
 	private BeanMetadataElement accessDeniedHandler;
 	private BeanMetadataElement accessDeniedHandler;
 
 
+	private BeanDefinition bearerTokenAuthenticationFilter;
+
 	private BeanDefinition logoutFilter;
 	private BeanDefinition logoutFilter;
 	@SuppressWarnings("rawtypes")
 	@SuppressWarnings("rawtypes")
 	private ManagedList logoutHandlers;
 	private ManagedList logoutHandlers;
@@ -191,6 +194,7 @@ final class AuthenticationConfigBuilder {
 		createAnonymousFilter();
 		createAnonymousFilter();
 		createRememberMeFilter(authenticationManager);
 		createRememberMeFilter(authenticationManager);
 		createBasicFilter(authenticationManager);
 		createBasicFilter(authenticationManager);
+		createBearerTokenAuthenticationFilter(authenticationManager);
 		createFormLoginFilter(sessionStrategy, authenticationManager);
 		createFormLoginFilter(sessionStrategy, authenticationManager);
 		createOAuth2LoginFilter(sessionStrategy, authenticationManager);
 		createOAuth2LoginFilter(sessionStrategy, authenticationManager);
 		createOAuth2ClientFilter(requestCache, authenticationManager);
 		createOAuth2ClientFilter(requestCache, authenticationManager);
@@ -504,6 +508,21 @@ final class AuthenticationConfigBuilder {
 		basicFilter = filterBuilder.getBeanDefinition();
 		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) {
 	void createX509Filter(BeanReference authManager) {
 		Element x509Elt = DomUtils.getChildElementByTagName(httpElt, Elements.X509);
 		Element x509Elt = DomUtils.getChildElementByTagName(httpElt, Elements.X509);
 		RootBeanDefinition filter = null;
 		RootBeanDefinition filter = null;
@@ -969,8 +988,12 @@ final class AuthenticationConfigBuilder {
 			filters.add(new OrderDecorator(basicFilter, BASIC_AUTH_FILTER));
 			filters.add(new OrderDecorator(basicFilter, BASIC_AUTH_FILTER));
 		}
 		}
 
 
+		if (bearerTokenAuthenticationFilter != null) {
+			filters.add(new OrderDecorator(bearerTokenAuthenticationFilter, BEARER_TOKEN_AUTH_FILTER));
+		}
+
 		if (authorizationCodeGrantFilter != null) {
 		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));
 			filters.add(new OrderDecorator(authorizationCodeGrantFilter, OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER));
 		}
 		}
 
 

+ 358 - 0
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<BeanReference> authenticationProviders;
+	private final Map<BeanDefinition, BeanMetadataElement> entryPoints;
+	private final Map<BeanDefinition, BeanMetadataElement> deniedHandlers;
+	private final List<BeanDefinition> ignoreCsrfRequestMatchers;
+
+	private final BeanDefinition authenticationEntryPoint =
+			new RootBeanDefinition(BearerTokenAuthenticationEntryPoint.class);
+	private final BeanDefinition accessDeniedHandler =
+			new RootBeanDefinition(BearerTokenAccessDeniedHandler.class);
+
+	OAuth2ResourceServerBeanDefinitionParser(BeanReference authenticationManager,
+			List<BeanReference> authenticationProviders,
+			Map<BeanDefinition, BeanMetadataElement> entryPoints,
+			Map<BeanDefinition, BeanMetadataElement> deniedHandlers,
+			List<BeanDefinition> ignoreCsrfRequestMatchers) {
+		this.authenticationManager = authenticationManager;
+		this.authenticationProviders = authenticationProviders;
+		this.entryPoints = entryPoints;
+		this.deniedHandlers = deniedHandlers;
+		this.ignoreCsrfRequestMatchers = ignoreCsrfRequestMatchers;
+	}
+
+	/**
+	 * Parse a &lt;oauth2-resource-server&gt; element and return the corresponding
+	 * {@link BearerTokenAuthenticationFilter}
+	 *
+	 * @param oauth2ResourceServer the &lt;oauth2-resource-server&gt; 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, <jwt>, or <opaque-token>. " +
+								"Please select one.", oauth2ResourceServer);
+			}
+			return;
+		}
+
+		if (jwt != null) {
+			pc.getReaderContext().error
+					("Found <jwt> as well as authentication-manager-resolver-ref. " +
+							"Please select just one.", oauth2ResourceServer);
+		}
+
+		if (opaqueToken != null) {
+			pc.getReaderContext().error
+					("Found <opaque-token> 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<HttpServletRequest> {
+	private final AuthenticationManager authenticationManager;
+
+	StaticAuthenticationManagerResolver(AuthenticationManager authenticationManager) {
+		this.authenticationManager = authenticationManager;
+	}
+
+	@Override
+	public AuthenticationManager resolve(HttpServletRequest context) {
+		return this.authenticationManager;
+	}
+}
+
+final class NimbusJwtDecoderJwkSetUriFactoryBean implements FactoryBean<JwtDecoder> {
+	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;
+		}
+	}
+}
+

+ 43 - 1
config/src/main/resources/org/springframework/security/config/spring-security-5.3.rnc

@@ -296,7 +296,7 @@ http-firewall =
 
 
 http =
 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".
 	## 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 &=
 http.attlist &=
 	## The request URL pattern which will be mapped to the filter chain created by this <http> element. If omitted, the filter chain will match all requests.
 	## The request URL pattern which will be mapped to the filter chain created by this <http> element. If omitted, the filter chain will match all requests.
 	attribute pattern {xsd:token}?
 	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.
 	## The URI used to discover the configuration information for an OAuth 2.0 or OpenID Connect 1.0 Provider.
 	attribute issuer-uri {xsd:token}?
 	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<Jwt, AbstractAuthenticationToken>
+    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 =
 openid-login =
 	## Sets up form login for authentication with an Open ID identity
 	## Sets up form login for authentication with an Open ID identity
 	element openid-login {form-login.attlist, user-service-ref?, attribute-exchange*}
 	element openid-login {form-login.attlist, user-service-ref?, attribute-exchange*}

+ 98 - 0
config/src/main/resources/org/springframework/security/config/spring-security-5.3.xsd

@@ -957,6 +957,7 @@
             </xs:element>
             </xs:element>
             <xs:element ref="security:oauth2-login"/>
             <xs:element ref="security:oauth2-login"/>
             <xs:element ref="security:oauth2-client"/>
             <xs:element ref="security:oauth2-client"/>
+            <xs:element ref="security:oauth2-resource-server"/>
             <xs:element name="openid-login">
             <xs:element name="openid-login">
                <xs:annotation>
                <xs:annotation>
                   <xs:documentation>Sets up form login for authentication with an Open ID identity
                   <xs:documentation>Sets up form login for authentication with an Open ID identity
@@ -1780,6 +1781,103 @@
          </xs:annotation>
          </xs:annotation>
       </xs:attribute>
       </xs:attribute>
   </xs:attributeGroup>
   </xs:attributeGroup>
+  <xs:element name="oauth2-resource-server">
+      <xs:annotation>
+         <xs:documentation>Configures authentication support as an OAuth 2.0 Resource Server.
+                </xs:documentation>
+      </xs:annotation>
+      <xs:complexType>
+         <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:element ref="security:jwt"/>
+            <xs:element ref="security:opaque-token"/>
+         </xs:choice>
+         <xs:attributeGroup ref="security:oauth2-resource-server.attlist"/>
+      </xs:complexType>
+   </xs:element>
+  <xs:attributeGroup name="oauth2-resource-server.attlist">
+      <xs:attribute name="authentication-manager-resolver-ref" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>Reference to an AuthenticationManagerResolver
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="bearer-token-resolver-ref" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>Reference to a BearerTokenResolver
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="entry-point-ref" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>Reference to a AuthenticationEntryPoint
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+  </xs:attributeGroup>
+  <xs:element name="jwt">
+      <xs:annotation>
+         <xs:documentation>Configures JWT authentication
+                </xs:documentation>
+      </xs:annotation>
+      <xs:complexType>
+         <xs:attributeGroup ref="security:jwt.attlist"/>
+      </xs:complexType>
+   </xs:element>
+  <xs:attributeGroup name="jwt.attlist">
+      <xs:attribute name="jwk-set-uri" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>The URI to use to collect the JWK Set for verifying JWTs
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="decoder-ref" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>Reference to a JwtDecoder
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="jwt-authentication-converter-ref" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>Reference to a Converter&lt;Jwt, AbstractAuthenticationToken&gt;
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+  </xs:attributeGroup>
+  <xs:element name="opaque-token">
+      <xs:annotation>
+         <xs:documentation>Configuration Opaque Token authentication
+                </xs:documentation>
+      </xs:annotation>
+      <xs:complexType>
+         <xs:attributeGroup ref="security:opaque-token.attlist"/>
+      </xs:complexType>
+   </xs:element>
+  <xs:attributeGroup name="opaque-token.attlist">
+      <xs:attribute name="introspection-uri" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>The URI to use to introspect opaque token attributes
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="client-id" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>The Client ID to use to authenticate the introspection request
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="client-secret" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>The Client secret to use to authenticate the introspection request
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="introspector-ref" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>Reference to an OpaqueTokenIntrospector
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+  </xs:attributeGroup>
   
   
   <xs:element name="attribute-exchange">
   <xs:element name="attribute-exchange">
       <xs:annotation>
       <xs:annotation>

+ 1239 - 0
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<String, String> 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<Jwt> 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<Jwt, JwtAuthenticationToken> jwtAuthenticationConverter =
+				(Converter<Jwt, JwtAuthenticationToken>) 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<HttpServletRequest> 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<JwtDecoder> {
+		private RestOperations rest;
+		private RSAPublicKey key;
+		private OAuth2TokenValidator<Jwt> 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<Jwt> 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<OpaqueTokenIntrospector> {
+		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<MockWebServer>, 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<Properties>, 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 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<String, Object> 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<String> 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> T bean(Class<T> beanClass) {
+		return this.spring.getContext().getBean(beanClass);
+	}
+
+	private String xml(String configName) {
+		return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml";
+	}
+}

+ 38 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AccessDeniedHandler.xml

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:bean name="accessDeniedHandler"
+			class="org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler">
+		<b:property name="realmName" value="myRealm"/>
+	</b:bean>
+
+	<http use-expressions="true">
+		<access-denied-handler ref="accessDeniedHandler"/>
+		<intercept-url pattern="/**" access="hasAuthority('SCOPE_read')"/>
+		<oauth2-resource-server>
+			<jwt decoder-ref="decoder"/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 6 - 0
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
+}

+ 5 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ActiveNoScopes.json

@@ -0,0 +1,5 @@
+{
+  "active" : true,
+  "sub": "test-subject",
+  "exp": 4683883211
+}

+ 37 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AllowBearerTokenInBody.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:bean id="bearerTokenResolver"
+		  class="org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver">
+		<b:property name="allowFormEncodedBodyParameter" value="true"/>
+	</b:bean>
+
+	<http>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<oauth2-resource-server bearer-token-resolver-ref="bearerTokenResolver">
+			<jwt decoder-ref="decoder"/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 37 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AllowBearerTokenInQuery.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:bean id="bearerTokenResolver"
+		  class="org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver">
+		<b:property name="allowUriQueryParameter" value="true"/>
+	</b:bean>
+
+	<http>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<oauth2-resource-server bearer-token-resolver-ref="bearerTokenResolver">
+			<jwt decoder-ref="decoder"/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 32 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AlwaysSessionCreation.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<http create-session="always">
+		<intercept-url pattern="/**" access="authenticated"/>
+		<oauth2-resource-server>
+			<jwt decoder-ref="decoder"/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 37 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationEntryPoint.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:bean name="authenticationEntryPoint"
+			class="org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint">
+		<b:property name="realmName" value="myRealm"/>
+	</b:bean>
+
+	<http>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<oauth2-resource-server entry-point-ref="authenticationEntryPoint">
+			<jwt decoder-ref="decoder"/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 35 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationManagerResolver.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:bean name="authenticationManagerResolver" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.security.authentication.AuthenticationManagerResolver"/>
+	</b:bean>
+
+	<http>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<intercept-url pattern="/requires-read-scope" access="hasAuthority('SCOPE_message:read')"/>
+		<oauth2-resource-server authentication-manager-resolver-ref="authenticationManagerResolver"/>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 40 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationManagerResolverPlusOtherConfig.xml

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:bean name="authenticationManagerResolver" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.security.authentication.AuthenticationManagerResolver"/>
+	</b:bean>
+	<b:bean name="decoder" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.security.oauth2.jwt.JwtDecoder"/>
+	</b:bean>
+
+	<http>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<intercept-url pattern="/requires-read-scope" access="hasAuthority('SCOPE_message:read')"/>
+		<oauth2-resource-server authentication-manager-resolver-ref="authenticationManagerResolver">
+			<jwt decoder-ref="decoder"/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 36 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-BasicAndResourceServer.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+
+
+	<http>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<intercept-url pattern="/requires-read-scope" access="hasAuthority('SCOPE_message:read')"/>
+		<http-basic/>
+		<oauth2-resource-server>
+			<jwt decoder-ref="decoder"/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 32 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-BearerTokenResolver.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<http>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<oauth2-resource-server bearer-token-resolver-ref="bearerTokenResolver">
+			<jwt decoder-ref="decoder"/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 1 - 0
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"}]}

+ 1 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Empty.jwks

@@ -0,0 +1 @@
+{"keys":[]}

+ 1 - 0
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

+ 42 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-ExpiredJwtClockSkew.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:bean name="clock" class="org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParserTests.ClockFactoryBean">
+		<b:property name="millis" value="4687181595000"/>
+	</b:bean>
+
+	<b:bean name="jwtValidator" class="org.springframework.security.oauth2.jwt.JwtTimestampValidator">
+		<b:constructor-arg value="#{T(java.time.Duration).ofHours(1)}"/>
+		<b:property name="clock" ref="clock"/>
+	</b:bean>
+
+	<b:bean name="rest" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.web.client.RestOperations"/>
+	</b:bean>
+
+	<b:bean name="decoder"
+			class="org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParserTests$JwtDecoderFactoryBean">
+		<b:property name="jwtValidator" ref="jwtValidator"/>
+		<b:property name="rest" ref="rest"/>
+	</b:bean>
+</b:beans>

+ 1 - 0
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

+ 36 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-FormAndResourceServer.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+
+
+	<http>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<intercept-url pattern="/requires-read-scope" access="hasAuthority('SCOPE_message:read')"/>
+		<form-login/>
+		<oauth2-resource-server>
+			<jwt decoder-ref="decoder"/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 3 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Inactive.json

@@ -0,0 +1,3 @@
+{
+  "active" : false
+}

+ 41 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-IntrospectionUri.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:c="http://www.springframework.org/schema/context"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
+		http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
+
+	<b:bean name="web" class="org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParserTests.MockWebServerFactoryBean"/>
+	<b:bean name="webProperties" class="org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParserTests.MockWebServerPropertiesFactoryBean">
+		<b:constructor-arg ref="web"/>
+	</b:bean>
+	<c:property-placeholder properties-ref="webProperties" local-override="true"/>
+
+	<http>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<oauth2-resource-server>
+			<opaque-token client-id="client" client-secret="secret"
+						  introspection-uri="${introspection-uri:https://idp.example.org}"/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 37 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwkSetUri.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:c="http://www.springframework.org/schema/context"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
+		http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
+
+	<c:property-placeholder local-override="true"/>
+
+	<http>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<intercept-url pattern="/requires-read-scope" access="hasAuthority('SCOPE_message:read')"/>
+		<oauth2-resource-server>
+			<jwt jwk-set-uri="${jwk-set-uri:https://idp.example.org}"/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 35 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Jwt.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+
+
+	<http>
+		<intercept-url pattern="/requires-read-scope" access="hasAuthority('SCOPE_message:read')"/>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<oauth2-resource-server>
+			<jwt decoder-ref="decoder"/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 32 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtAuthenticationConverter.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<http>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<oauth2-resource-server>
+			<jwt decoder-ref="decoder" jwt-authentication-converter-ref="jwtAuthenticationConverter"/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 41 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtDecoderAndJwkSetUri.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:c="http://www.springframework.org/schema/context"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
+		http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
+
+	<c:component-scan base-package="org.springframework.security.config.http"/>
+	<c:property-placeholder local-override="true"/>
+	<b:bean name="decoder" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.security.oauth2.jwt.JwtDecoder"/>
+	</b:bean>
+
+	<http>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<intercept-url pattern="/requires-read-scope" access="hasAuthority('SCOPE_message:read')"/>
+		<oauth2-resource-server>
+			<jwt jwk-set-uri="${jwk-set-uri:https://idp.example.org}" decoder-ref="decoder"/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 35 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtHalfConfigured.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+
+
+	<http>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<intercept-url pattern="/requires-read-scope" access="hasAuthority('SCOPE_message:read')"/>
+		<oauth2-resource-server>
+			<jwt/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 32 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtRestOperations.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:bean name="rest" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.web.client.RestOperations"/>
+	</b:bean>
+
+	<b:bean name="decoder"
+			class="org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParserTests$JwtDecoderFactoryBean">
+		<b:property name="rest" ref="rest"/>
+	</b:bean>
+</b:beans>

+ 33 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Jwtless.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+
+
+	<http>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<intercept-url pattern="/requires-read-scope" access="hasAuthority('SCOPE_message:read')"/>
+		<oauth2-resource-server/>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 1 - 0
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

+ 1 - 0
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

+ 27 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockBearerTokenResolver.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:bean name="bearerTokenResolver" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.security.oauth2.server.resource.web.BearerTokenResolver"/>
+	</b:bean>
+</b:beans>

+ 33 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwkSetUri.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:c="http://www.springframework.org/schema/context"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
+		http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
+
+	<b:bean name="web" class="org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParserTests.MockWebServerFactoryBean"/>
+	<b:bean name="webProperties" class="org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParserTests.MockWebServerPropertiesFactoryBean">
+		<b:constructor-arg ref="web"/>
+	</b:bean>
+	<c:property-placeholder properties-ref="webProperties" local-override="true"/>
+</b:beans>
+

+ 27 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwtAuthenticationConverter.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:bean name="jwtAuthenticationConverter" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.core.convert.converter.Converter"/>
+	</b:bean>
+</b:beans>

+ 27 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwtDecoder.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:bean name="decoder" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.security.oauth2.jwt.JwtDecoder"/>
+	</b:bean>
+</b:beans>

+ 37 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockJwtValidator.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:bean name="jwtValidator" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.security.oauth2.core.OAuth2TokenValidator"/>
+	</b:bean>
+
+	<b:bean name="rest" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.web.client.RestOperations"/>
+	</b:bean>
+
+	<b:bean name="decoder"
+			class="org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParserTests$JwtDecoderFactoryBean">
+		<b:property name="jwtValidator" ref="jwtValidator"/>
+		<b:property name="rest" ref="rest"/>
+	</b:bean>
+</b:beans>

+ 27 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockOpaqueTokenIntrospector.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:bean name="introspector" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector"/>
+	</b:bean>
+</b:beans>

+ 41 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MultipleIssuers.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:bean name="authenticationManagerResolver"
+			class="org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver">
+		<b:constructor-arg>
+			<b:list>
+				<b:value>${issuer-one}</b:value>
+				<b:value>${issuer-two}</b:value>
+			</b:list>
+		</b:constructor-arg>
+	</b:bean>
+
+	<http>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<intercept-url pattern="/requires-read-scope" access="hasAuthority('SCOPE_message:read')"/>
+		<oauth2-resource-server authentication-manager-resolver-ref="authenticationManagerResolver"/>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 33 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueToken.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<http>
+		<intercept-url pattern="/requires-read-scope" access="hasAuthority('SCOPE_message:read')"/>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<oauth2-resource-server>
+			<opaque-token introspector-ref="introspector"/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 36 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenAndIntrospectionUri.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:bean name="introspector" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector"/>
+	</b:bean>
+
+	<http>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<oauth2-resource-server>
+			<opaque-token introspector-ref="introspector" introspection-uri="https://idp.example.org"/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 32 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenHalfConfigured.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<http>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<oauth2-resource-server>
+			<opaque-token introspection-uri="https://idp.example.org"/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 32 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenRestOperations.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:bean name="rest" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.web.client.RestOperations"/>
+	</b:bean>
+
+	<b:bean name="introspector"
+			class="org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParserTests$OpaqueTokenIntrospectorFactoryBean">
+		<b:property name="rest" ref="rest"/>
+	</b:bean>
+</b:beans>

+ 9 - 0
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-----

+ 30 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-SingleKey.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:bean class="org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor"/>
+
+	<b:bean name="decoder"
+			class="org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParserTests$JwtDecoderFactoryBean">
+		<b:property name="key" value="classpath:org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-SingleKey.pub"/>
+	</b:bean>
+</b:beans>

+ 1 - 0
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

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-TwoKeys.jwks


+ 42 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-UnexpiredJwtClockSkew.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:bean name="clock" class="org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParserTests.ClockFactoryBean">
+		<b:property name="millis" value="4687181540000"/>
+	</b:bean>
+
+	<b:bean name="jwtValidator" class="org.springframework.security.oauth2.jwt.JwtTimestampValidator">
+		<b:constructor-arg value="#{T(java.time.Duration).ofHours(1)}"/>
+		<b:property name="clock" ref="clock"/>
+	</b:bean>
+
+	<b:bean name="rest" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.web.client.RestOperations"/>
+	</b:bean>
+
+	<b:bean name="decoder"
+			class="org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParserTests$JwtDecoderFactoryBean">
+		<b:property name="jwtValidator" ref="jwtValidator"/>
+		<b:property name="rest" ref="rest"/>
+	</b:bean>
+</b:beans>

+ 1 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-Unsigned.token

@@ -0,0 +1 @@
+eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJuYmYiOjE1MzAzMDA4MzgsImV4cCI6MjE0NjAwMzE5OSwiaWF0IjoxNTMwMzAwODM4LCJ0eXAiOiJKV1QifQ.

+ 1 - 0
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

+ 1 - 0
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

+ 1 - 0
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

+ 31 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-WebServer.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:c="http://www.springframework.org/schema/context"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
+		http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
+
+	<b:bean name="web" class="org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParserTests.MockWebServerFactoryBean"/>
+	<b:bean name="webProperties" class="org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParserTests.MockWebServerPropertiesFactoryBean">
+		<b:constructor-arg ref="web"/>
+	</b:bean>
+	<c:property-placeholder properties-ref="webProperties" local-override="true"/>
+</b:beans>

+ 1 - 0
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

+ 1 - 0
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

+ 85 - 0
docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc

@@ -166,6 +166,7 @@ The default value is true.
 * <<nsa-logout,logout>>
 * <<nsa-logout,logout>>
 * <<nsa-oauth2-client,oauth2-client>>
 * <<nsa-oauth2-client,oauth2-client>>
 * <<nsa-oauth2-login,oauth2-login>>
 * <<nsa-oauth2-login,oauth2-login>>
+* <<nsa-oauth2-resource-server,oauth2-resource-server>>
 * <<nsa-openid-login,openid-login>>
 * <<nsa-openid-login,openid-login>>
 * <<nsa-port-mappings,port-mappings>>
 * <<nsa-port-mappings,port-mappings>>
 * <<nsa-remember-me,remember-me>>
 * <<nsa-remember-me,remember-me>>
@@ -1160,6 +1161,90 @@ The URI used to retrieve the https://tools.ietf.org/html/rfc7517[JSON Web Key (J
 * **issuer-uri**
 * **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].
 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]]
+==== <oauth2-resource-server>
+Adds a `BearerTokenAuthenticationFilter`, `BearerTokenAuthenticationEntryPoint`, and `BearerTokenAccessDeniedHandler` to the configuration.
+In addition, either `<jwt>` or `<opaque-token>` must be specified.
+
+[[nsa-oauth2-resource-server-parents]]
+===== Parents Elements of <oauth2-resource-server>
+
+* <<nsa-http,http>>
+
+[[nsa-oauth2-resource-server-children]]
+===== Child Elements of <oauth2-resource-server>
+
+* <<nsa-jwt,jwt>>
+* <<nsa-opaque-token,opaque-token>>
+
+[[nsa-oauth2-resource-server-attributes]]
+===== <oauth2-resource-server> 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]]
+==== <jwt>
+Represents an OAuth 2.0 Resource Server that will authorize JWTs
+
+
+[[nsa-jwt-parents]]
+===== Parent Elements of <jwt>
+
+* <<nsa-oauth2-resource-server,oauth2-resource-server>>
+
+
+[[nsa-jwt-attributes]]
+===== <jwt> Attributes
+
+[[nsa-jwt-jwt-authentication-converter-ref]]
+* **jwt-authentication-converter-ref**
+Reference to a `Converter<Jwt, AbstractAuthenticationToken>`
+
+[[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]]
+==== <opaque-token>
+Represents an OAuth 2.0 Resource Server that will authorize opaque tokens
+
+[[nsa-opaque-token-parents]]
+===== Parent Elements of <opaque-token>
+
+* <<nsa-oauth2-resource-server,oauth2-resource-server>>
+
+[[nsa-opaque-token-attributes]]
+===== <opaque-token> 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]]
 [[nsa-http-basic]]
 ==== <http-basic>
 ==== <http-basic>

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно