/*
 * Copyright 2004-present the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.security.config.annotation.web.configurers.oauth2.server.resource;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

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 com.nimbusds.jose.util.JSONObjectUtils;
import jakarta.annotation.PreDestroy;
import jakarta.servlet.http.HttpServletRequest;
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.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.verification.VerificationMode;

import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
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.mock.web.MockHttpServletRequest;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextChangedListener;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
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.core.TestOAuth2AccessTokens;
import org.springframework.security.oauth2.jose.TestKeys;
import org.springframework.security.oauth2.jwt.BadJwtException;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.TestJwts;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
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.oauth2.server.resource.web.authentication.BearerTokenAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestOperations;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.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.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
 * Tests for {@link OAuth2ResourceServerConfigurer}
 *
 * @author Josh Cummings
 * @author Evgeniy Cheban
 */
@ExtendWith(SpringTestContextExtension.class)
public class OAuth2ResourceServerConfigurerTests {

	private static final String JWT_TOKEN = "token";

	private static final String JWT_SUBJECT = "mock-test-subject";

	private static final Map<String, Object> JWT_CLAIMS = Collections.singletonMap(JwtClaimNames.SUB, JWT_SUBJECT);

	private static final Jwt JWT = TestJwts.jwt().build();

	private static final String JWK_SET_URI = "https://mock.org";

	private static final JwtAuthenticationToken JWT_AUTHENTICATION_TOKEN = new JwtAuthenticationToken(JWT,
			Collections.emptyList());

	private static final String INTROSPECTION_URI = "https://idp.example.com";

	private static final String CLIENT_ID = "client-id";

	private static final String CLIENT_SECRET = "client-secret";

	private static final BearerTokenAuthentication INTROSPECTION_AUTHENTICATION_TOKEN = new BearerTokenAuthentication(
			new DefaultOAuth2AuthenticatedPrincipal(JWT_CLAIMS, Collections.emptyList()),
			TestOAuth2AccessTokens.noScopes(), Collections.emptyList());

	@Autowired(required = false)
	MockMvc mvc;

	@Autowired(required = false)
	MockWebServer web;

	public final SpringTestContext spring = new SpringTestContext(this);

	@Test
	public void getWhenUsingDefaultsWithValidBearerTokenThenAcceptsRequest() throws Exception {
		this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ValidNoScopes");
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(status().isOk())
				.andExpect(content().string("ok"));
		// @formatter:on
	}

	@Test
	public void getWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
		this.spring
			.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class,
					SecurityContextChangedListenerConfig.class)
			.autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ValidNoScopes");
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(status().isOk())
				.andExpect(content().string("ok"));
		// @formatter:on
		verifyBean(SecurityContextHolderStrategy.class, atLeastOnce()).getContext();
	}

	@Test
	public void getWhenSecurityContextHolderStrategyThenUses() throws Exception {
		this.spring
			.register(RestOperationsConfig.class, DefaultConfig.class, SecurityContextChangedListenerConfig.class,
					BasicController.class)
			.autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ValidNoScopes");
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(status().isOk())
				.andExpect(content().string("ok"));
		// @formatter:on
		verifyBean(SecurityContextChangedListener.class, atLeastOnce()).securityContextChanged(any());
	}

	@Test
	public void getWhenUsingDefaultsInLambdaWithValidBearerTokenThenAcceptsRequest() throws Exception {
		this.spring.register(RestOperationsConfig.class, DefaultInLambdaConfig.class, BasicController.class).autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ValidNoScopes");
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(status().isOk())
				.andExpect(content().string("ok"));
		// @formatter:on
	}

	@Test
	public void getWhenUsingJwkSetUriThenAcceptsRequest() throws Exception {
		this.spring.register(WebServerConfig.class, JwkSetUriConfig.class, BasicController.class).autowire();
		mockWebServer(jwks("Default"));
		String token = this.token("ValidNoScopes");
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(status().isOk())
				.andExpect(content().string("ok"));
		// @formatter:on
	}

	@Test
	public void getWhenUsingJwkSetUriInLambdaThenAcceptsRequest() throws Exception {
		this.spring.register(WebServerConfig.class, JwkSetUriInLambdaConfig.class, BasicController.class).autowire();
		mockWebServer(jwks("Default"));
		String token = this.token("ValidNoScopes");
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(status().isOk())
				.andExpect(content().string("ok"));
		// @formatter:on
	}

	@Test
	public void getWhenUsingDefaultsWithExpiredBearerTokenThenInvalidToken() throws Exception {
		this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("Expired");
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(status().isUnauthorized())
				.andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt"));
		// @formatter:on
	}

	@Test
	public void getWhenUsingDefaultsWithBadJwkEndpointThen500() throws Exception {
		this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire();
		mockRestOperations("malformed");
		String token = this.token("ValidNoScopes");
		// @formatter:off
		assertThatExceptionOfType(AuthenticationServiceException.class)
				.isThrownBy(() -> this.mvc.perform(get("/").with(bearerToken(token))));
		// @formatter:on
	}

	@Test
	public void getWhenUsingDefaultsWithUnavailableJwkEndpointThen500() throws Exception {
		this.spring.register(WebServerConfig.class, JwkSetUriConfig.class).autowire();
		this.web.shutdown();
		String token = this.token("ValidNoScopes");
		// @formatter:off
		assertThatExceptionOfType(AuthenticationServiceException.class)
				.isThrownBy(() -> this.mvc.perform(get("/").with(bearerToken(token))));
		// @formatter:on
	}

	@Test
	public void getWhenUsingDefaultsWithMalformedBearerTokenThenInvalidToken() throws Exception {
		this.spring.register(JwkSetUriConfig.class).autowire();
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken("an\"invalid\"token")))
				.andExpect(status().isUnauthorized())
				.andExpect(invalidTokenHeader("Bearer token is malformed"));
		// @formatter:on
	}

	@Test
	public void getWhenUsingDefaultsWithMalformedPayloadThenInvalidToken() throws Exception {
		this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("MalformedPayload");
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(status().isUnauthorized())
				.andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt: Malformed payload"));
		// @formatter:on
	}

	@Test
	public void getWhenUsingDefaultsWithUnsignedBearerTokenThenInvalidToken() throws Exception {
		this.spring.register(JwkSetUriConfig.class).autowire();
		String token = this.token("Unsigned");
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(status().isUnauthorized())
				.andExpect(invalidTokenHeader("Unsupported algorithm of none"));
		// @formatter:on
	}

	@Test
	public void getWhenUsingDefaultsWithBearerTokenBeforeNotBeforeThenInvalidToken() throws Exception {
		this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire();
		this.mockJwksRestOperations(jwks("Default"));
		String token = this.token("TooEarly");
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(status().isUnauthorized())
				.andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt"));
		// @formatter:on
	}

	@Test
	public void getWhenUsingDefaultsWithBearerTokenInTwoPlacesThenInvalidRequest() throws Exception {
		this.spring.register(JwkSetUriConfig.class).autowire();
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken("token")).with(bearerToken("token").asParam()))
				.andExpect(status().isBadRequest())
				.andExpect(invalidRequestHeader("Found multiple bearer tokens in the request"));
		// @formatter:on
	}

	@Test
	public void getWhenUsingDefaultsWithBearerTokenInTwoParametersThenInvalidRequest() throws Exception {
		this.spring.register(JwkSetUriConfig.class).autowire();
		MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
		params.add("access_token", "token1");
		params.add("access_token", "token2");
		// @formatter:off
		this.mvc.perform(get("/").params(params))
				.andExpect(status().isBadRequest())
				.andExpect(invalidRequestHeader("Found multiple bearer tokens in the request"));
		// @formatter:on
	}

	@Test
	public void postWhenUsingDefaultsWithBearerTokenAsFormParameterThenIgnoresToken() throws Exception {
		this.spring.register(JwkSetUriConfig.class).autowire();
		// engage csrf
		// @formatter:off
		this.mvc.perform(post("/").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken("token").asParam()))
				.andExpect(status().isForbidden())
				.andExpect(header().doesNotExist(HttpHeaders.WWW_AUTHENTICATE));
		// @formatter:on
	}

	@Test
	public void postWhenCsrfDisabledWithBearerTokenAsFormParameterThenIgnoresToken() throws Exception {
		this.spring.register(CsrfDisabledConfig.class).autowire();
		// @formatter:off
		this.mvc.perform(post("/").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken("token").asParam()))
				.andExpect(status().isUnauthorized())
				.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer resource_metadata=\"http://localhost/.well-known/oauth-protected-resource\""));
		// @formatter:on
	}

	// gh-8031
	@Test
	public void getWhenAnonymousDisabledThenAllows() throws Exception {
		this.spring.register(RestOperationsConfig.class, AnonymousDisabledConfig.class).autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = token("ValidNoScopes");
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken(token)))
				.andExpect(status().isNotFound());
		// @formatter:on
	}

	@Test
	public void getWhenUsingDefaultsWithNoBearerTokenThenUnauthorized() throws Exception {
		this.spring.register(JwkSetUriConfig.class).autowire();
		// @formatter:off
		this.mvc.perform(get("/"))
				.andExpect(status().isUnauthorized())
				.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer resource_metadata=\"http://localhost/.well-known/oauth-protected-resource\""));
		// @formatter:on
	}

	@Test
	public void getWhenUsingDefaultsWithSufficientlyScopedBearerTokenThenAcceptsRequest() throws Exception {
		this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ValidMessageReadScope");
		// @formatter:off
		this.mvc.perform(get("/requires-read-scope").with(bearerToken(token)))
				.andExpect(status().isOk())
				.andExpect(content().string("[SCOPE_message:read]"));
		// @formatter:on
	}

	@Test
	public void getWhenUsingDefaultsWithInsufficientScopeThenInsufficientScopeError() throws Exception {
		this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ValidNoScopes");
		// @formatter:off
		this.mvc.perform(get("/requires-read-scope").with(bearerToken(token)))
				.andExpect(status().isForbidden())
				.andExpect(insufficientScopeHeader());
		// @formatter:on
	}

	@Test
	public void getWhenUsingDefaultsWithInsufficientScpThenInsufficientScopeError() throws Exception {
		this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ValidMessageWriteScp");
		// @formatter:off
		this.mvc.perform(get("/requires-read-scope").with(bearerToken(token)))
				.andExpect(status().isForbidden())
				.andExpect(insufficientScopeHeader());
		// @formatter:on
	}

	@Test
	public void getWhenUsingDefaultsAndAuthorizationServerHasNoMatchingKeyThenInvalidToken() throws Exception {
		this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire();
		mockJwksRestOperations(jwks("Empty"));
		String token = this.token("ValidNoScopes");
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(status().isUnauthorized())
				.andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt"));
		// @formatter:on
	}

	@Test
	public void getWhenUsingDefaultsAndAuthorizationServerHasMultipleMatchingKeysThenOk() throws Exception {
		this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire();
		mockJwksRestOperations(jwks("TwoKeys"));
		String token = this.token("ValidNoScopes");
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken(token)))
				.andExpect(status().isOk())
				.andExpect(content().string("test-subject"));
		// @formatter:on
	}

	@Test
	public void getWhenUsingDefaultsAndKeyMatchesByKidThenOk() throws Exception {
		this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire();
		mockJwksRestOperations(jwks("TwoKeys"));
		String token = this.token("Kid");
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken(token)))
				.andExpect(status().isOk())
				.andExpect(content().string("test-subject"));
		// @formatter:on
	}

	@Test
	public void getWhenUsingMethodSecurityWithValidBearerTokenThenAcceptsRequest() throws Exception {
		this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ValidMessageReadScope");
		// @formatter:off
		this.mvc.perform(get("/ms-requires-read-scope").with(bearerToken(token)))
				.andExpect(status().isOk())
				.andExpect(content().string("[SCOPE_message:read]"));
		// @formatter:on
	}

	@Test
	public void getWhenUsingMethodSecurityWithValidBearerTokenHavingScpAttributeThenAcceptsRequest() throws Exception {
		this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ValidMessageReadScp");
		// @formatter:off
		this.mvc.perform(get("/ms-requires-read-scope").with(bearerToken(token)))
				.andExpect(status().isOk())
				.andExpect(content().string("[SCOPE_message:read]"));
		// @formatter:on
	}

	@Test
	public void getWhenUsingMethodSecurityWithInsufficientScopeThenInsufficientScopeError() throws Exception {
		this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ValidNoScopes");
		// @formatter:off
		this.mvc.perform(get("/ms-requires-read-scope").with(bearerToken(token)))
				.andExpect(status().isForbidden())
				.andExpect(insufficientScopeHeader());
		// @formatter:on
	}

	@Test
	public void getWhenUsingMethodSecurityWithInsufficientScpThenInsufficientScopeError() throws Exception {
		this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ValidMessageWriteScp");
		// @formatter:off
		this.mvc.perform(get("/ms-requires-read-scope").with(bearerToken(token)))
				.andExpect(status().isForbidden())
				.andExpect(insufficientScopeHeader());
		// @formatter:on
	}

	@Test
	public void getWhenUsingMethodSecurityWithDenyAllThenInsufficientScopeError() throws Exception {
		this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ValidMessageReadScope");
		// @formatter:off
		this.mvc.perform(get("/ms-deny").with(bearerToken(token)))
				.andExpect(status().isForbidden())
				.andExpect(insufficientScopeHeader());
		// @formatter:on
	}

	@Test
	public void postWhenUsingDefaultsWithValidBearerTokenAndNoCsrfTokenThenOk() throws Exception {
		this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ValidNoScopes");
		// @formatter:off
		this.mvc.perform(post("/authenticated").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken(token)))
				.andExpect(status().isOk())
				.andExpect(content().string("test-subject"));
		// @formatter:on
	}

	@Test
	public void postWhenUsingDefaultsWithNoBearerTokenThenCsrfDenies() throws Exception {
		this.spring.register(JwkSetUriConfig.class).autowire();
		// @formatter:off
		this.mvc.perform(post("/authenticated"))
				.andExpect(status().isForbidden())
				.andExpect(header().doesNotExist(HttpHeaders.WWW_AUTHENTICATE));
		// @formatter:on
	}

	@Test
	public void postWhenUsingDefaultsWithExpiredBearerTokenAndNoCsrfThenInvalidToken() throws Exception {
		this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("Expired");
		// @formatter:off
		this.mvc.perform(post("/authenticated").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken(token)))
				.andExpect(status().isUnauthorized())
				.andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt"));
		// @formatter:on
	}

	@Test
	public void requestWhenDefaultConfiguredThenSessionIsNotCreated() throws Exception {
		this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ValidNoScopes");
		// @formatter:off
		MvcResult result = this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(status().isOk())
				.andReturn();
		// @formatter:on
		assertThat(result.getRequest().getSession(false)).isNull();
	}

	@Test
	public void requestWhenIntrospectionConfiguredThenSessionIsNotCreated() throws Exception {
		this.spring.register(RestOperationsConfig.class, OpaqueTokenConfig.class, BasicController.class).autowire();
		mockJsonRestOperations(json("Active"));
		// @formatter:off
		MvcResult result = this.mvc.perform(get("/authenticated").with(bearerToken("token")))
				.andExpect(status().isOk())
				.andExpect(content().string("test-subject"))
				.andReturn();
		// @formatter:on
		assertThat(result.getRequest().getSession(false)).isNull();
	}

	@Test
	public void requestWhenUsingDefaultsAndNoBearerTokenThenSessionIsCreated() throws Exception {
		this.spring.register(JwkSetUriConfig.class, BasicController.class).autowire();
		// @formatter:off
		MvcResult result = this.mvc.perform(get("/"))
				.andExpect(status().isUnauthorized())
				.andReturn();
		// @formatter:on
		assertThat(result.getRequest().getSession(false)).isNotNull();
	}

	@Test
	public void requestWhenSessionManagementConfiguredThenUserConfigurationOverrides() throws Exception {
		this.spring.register(RestOperationsConfig.class, AlwaysSessionCreationConfig.class, BasicController.class)
			.autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ValidNoScopes");
		// @formatter:off
		MvcResult result = this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(status().isOk())
				.andReturn();
		// @formatter:on
		assertThat(result.getRequest().getSession(false)).isNotNull();
	}

	@Test
	public void requestWhenBearerTokenResolverAllowsRequestBodyThenEitherHeaderOrRequestBodyIsAccepted()
			throws Exception {
		this.spring.register(AllowBearerTokenInRequestBodyConfig.class, JwtDecoderConfig.class, BasicController.class)
			.autowire();
		JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
		given(decoder.decode(anyString())).willReturn(JWT);
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN)))
				.andExpect(status().isOk())
				.andExpect(content().string(JWT_SUBJECT));
		this.mvc.perform(post("/authenticated").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).param("access_token", JWT_TOKEN))
				.andExpect(status().isOk())
				.andExpect(content().string(JWT_SUBJECT));
		// @formatter:on
	}

	@Test
	public void requestWhenBearerTokenResolverAllowsQueryParameterThenEitherHeaderOrQueryParameterIsAccepted()
			throws Exception {
		this.spring
			.register(AllowBearerTokenAsQueryParameterConfig.class, JwtDecoderConfig.class, BasicController.class)
			.autowire();
		JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
		given(decoder.decode(anyString())).willReturn(JWT);
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN)))
				.andExpect(status().isOk())
				.andExpect(content().string(JWT_SUBJECT));
		this.mvc.perform(get("/authenticated").param("access_token", JWT_TOKEN))
				.andExpect(status().isOk())
				.andExpect(content().string(JWT_SUBJECT));
		// @formatter:on
	}

	@Test
	public void requestWhenBearerTokenResolverAllowsRequestBodyAndRequestContainsTwoTokensThenInvalidRequest()
			throws Exception {
		this.spring.register(AllowBearerTokenInRequestBodyConfig.class, JwtDecoderConfig.class, BasicController.class)
			.autowire();
		JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
		given(decoder.decode(anyString())).willReturn(JWT);
		// @formatter:off
		MockHttpServletRequestBuilder request = post("/authenticated")
				.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
				.param("access_token", JWT_TOKEN)
				.with(bearerToken(JWT_TOKEN))
				.with(csrf());
		this.mvc.perform(request)
				.andExpect(status().isBadRequest())
				.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("invalid_request")));
		// @formatter:on
	}

	@Test
	public void requestWhenBearerTokenResolverAllowsQueryParameterAndRequestContainsTwoTokensThenInvalidRequest()
			throws Exception {
		this.spring
			.register(AllowBearerTokenAsQueryParameterConfig.class, JwtDecoderConfig.class, BasicController.class)
			.autowire();
		JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
		given(decoder.decode(anyString())).willReturn(JWT);
		// @formatter:off
		MockHttpServletRequestBuilder request = get("/authenticated")
				.with(bearerToken(JWT_TOKEN))
				.param("access_token", JWT_TOKEN);
		this.mvc.perform(request)
				.andExpect(status().isBadRequest())
				.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("invalid_request")));
		// @formatter:on
	}

	@Test
	public void getBearerTokenResolverWhenDuplicateResolverBeansAndAnotherOnTheDslThenTheDslOneIsUsed() {
		BearerTokenResolver resolverBean = mock(BearerTokenResolver.class);
		BearerTokenResolver resolver = mock(BearerTokenResolver.class);
		GenericWebApplicationContext context = new GenericWebApplicationContext();
		context.registerBean("resolverOne", BearerTokenResolver.class, () -> resolverBean);
		context.registerBean("resolverTwo", BearerTokenResolver.class, () -> resolverBean);
		this.spring.context(context).autowire();
		OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
		oauth2.bearerTokenResolver(resolver);
		assertThat(oauth2.getBearerTokenResolver()).isEqualTo(resolver);
	}

	@Test
	public void getBearerTokenResolverWhenDuplicateResolverBeansThenWiringException() {
		assertThatExceptionOfType(BeanCreationException.class)
			.isThrownBy(() -> this.spring.register(MultipleBearerTokenResolverBeansConfig.class, JwtDecoderConfig.class)
				.autowire())
			.withRootCauseInstanceOf(NoUniqueBeanDefinitionException.class);
	}

	@Test
	public void getBearerTokenResolverWhenResolverBeanAndAnotherOnTheDslThenTheDslOneIsUsed() {
		BearerTokenResolver resolver = mock(BearerTokenResolver.class);
		BearerTokenResolver resolverBean = mock(BearerTokenResolver.class);
		GenericWebApplicationContext context = new GenericWebApplicationContext();
		context.registerBean(BearerTokenResolver.class, () -> resolverBean);
		this.spring.context(context).autowire();
		OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
		oauth2.bearerTokenResolver(resolver);
		assertThat(oauth2.getBearerTokenResolver()).isEqualTo(resolver);
	}

	@Test
	public void requestWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception {
		this.spring.register(CustomAuthenticationDetailsSource.class, JwtDecoderConfig.class, BasicController.class)
			.autowire();
		JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
		given(decoder.decode(anyString())).willReturn(JWT);
		this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN)))
			.andExpect(status().isOk())
			.andExpect(content().string(JWT_SUBJECT));
		verifyBean(AuthenticationDetailsSource.class).buildDetails(any());
	}

	@Test
	public void requestWhenCustomJwtDecoderWiredOnDslThenUsed() throws Exception {
		this.spring.register(CustomJwtDecoderOnDsl.class, BasicController.class).autowire();
		CustomJwtDecoderOnDsl config = this.spring.getContext().getBean(CustomJwtDecoderOnDsl.class);
		JwtDecoder decoder = config.decoder();
		given(decoder.decode(anyString())).willReturn(JWT);
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN)))
				.andExpect(status().isOk())
				.andExpect(content().string(JWT_SUBJECT));
		// @formatter:on
	}

	@Test
	public void requestWhenCustomJwtDecoderInLambdaOnDslThenUsed() throws Exception {
		this.spring.register(CustomJwtDecoderInLambdaOnDsl.class, BasicController.class).autowire();
		CustomJwtDecoderInLambdaOnDsl config = this.spring.getContext().getBean(CustomJwtDecoderInLambdaOnDsl.class);
		JwtDecoder decoder = config.decoder();
		given(decoder.decode(anyString())).willReturn(JWT);
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN)))
				.andExpect(status().isOk())
				.andExpect(content().string(JWT_SUBJECT));
		// @formatter:on
	}

	@Test
	public void requestWhenCustomJwtDecoderExposedAsBeanThenUsed() throws Exception {
		this.spring.register(CustomJwtDecoderAsBean.class, BasicController.class).autowire();
		JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
		given(decoder.decode(anyString())).willReturn(JWT);
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN)))
				.andExpect(status().isOk())
				.andExpect(content().string(JWT_SUBJECT));
		// @formatter:on
	}

	@Test
	public void getJwtDecoderWhenConfiguredWithDecoderAndJwkSetUriThenLastOneWins() {
		ApplicationContext context = mock(ApplicationContext.class);
		JwtDecoder decoder = mock(JwtDecoder.class);
		new OAuth2ResourceServerConfigurer<HttpSecurity>(context).jwt((jwt) -> {
			jwt.jwkSetUri(JWK_SET_URI);
			jwt.decoder(decoder);
			assertThat(jwt.getJwtDecoder()).isEqualTo(decoder);
		});
		new OAuth2ResourceServerConfigurer<HttpSecurity>(context).jwt((jwt) -> {
			jwt.decoder(decoder).jwkSetUri(JWK_SET_URI);
			assertThat(jwt.getJwtDecoder()).isInstanceOf(NimbusJwtDecoder.class);
		});
	}

	@Test
	public void getJwtDecoderWhenConflictingJwtDecodersThenTheDslWiredOneTakesPrecedence() {
		JwtDecoder decoderBean = mock(JwtDecoder.class);
		JwtDecoder decoder = mock(JwtDecoder.class);
		ApplicationContext context = mock(ApplicationContext.class);
		given(context.getBean(JwtDecoder.class)).willReturn(decoderBean);
		new OAuth2ResourceServerConfigurer<HttpSecurity>(context).jwt((jwt) -> {
			jwt.decoder(decoder);
			assertThat(jwt.getJwtDecoder()).isEqualTo(decoder);
		});
	}

	@Test
	public void getJwtDecoderWhenContextHasBeanAndUserConfiguresJwkSetUriThenJwkSetUriTakesPrecedence() {
		JwtDecoder decoder = mock(JwtDecoder.class);
		ApplicationContext context = mock(ApplicationContext.class);
		given(context.getBean(JwtDecoder.class)).willReturn(decoder);
		new OAuth2ResourceServerConfigurer<HttpSecurity>(context).jwt((jwt) -> {
			jwt.jwkSetUri(JWK_SET_URI);
			assertThat(jwt.getJwtDecoder()).isNotEqualTo(decoder);
			assertThat(jwt.getJwtDecoder()).isInstanceOf(NimbusJwtDecoder.class);
		});
	}

	@Test
	public void getJwtDecoderWhenTwoJwtDecoderBeansAndAnotherWiredOnDslThenDslWiredOneTakesPrecedence() {
		JwtDecoder decoderBean = mock(JwtDecoder.class);
		JwtDecoder decoder = mock(JwtDecoder.class);
		GenericWebApplicationContext context = new GenericWebApplicationContext();
		context.registerBean("decoderOne", JwtDecoder.class, () -> decoderBean);
		context.registerBean("decoderTwo", JwtDecoder.class, () -> decoderBean);
		this.spring.context(context).autowire();
		new OAuth2ResourceServerConfigurer<HttpSecurity>(context).jwt((jwt) -> {
			jwt.decoder(decoder);
			assertThat(jwt.getJwtDecoder()).isEqualTo(decoder);
		});
	}

	@Test
	public void getJwtDecoderWhenTwoJwtDecoderBeansThenThrowsException() {
		JwtDecoder decoder = mock(JwtDecoder.class);
		GenericWebApplicationContext context = new GenericWebApplicationContext();
		context.registerBean("decoderOne", JwtDecoder.class, () -> decoder);
		context.registerBean("decoderTwo", JwtDecoder.class, () -> decoder);
		this.spring.context(context).autowire();
		new OAuth2ResourceServerConfigurer<HttpSecurity>(context)
			.jwt((jwt) -> assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
				.isThrownBy(jwt::getJwtDecoder));
	}

	@Test
	public void requestWhenRealmNameConfiguredThenUsesOnUnauthenticated() throws Exception {
		this.spring.register(RealmNameConfiguredOnEntryPoint.class, JwtDecoderConfig.class).autowire();
		JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
		given(decoder.decode(anyString())).willThrow(BadJwtException.class);
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken("invalid_token")))
				.andExpect(status().isUnauthorized())
				.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer realm=\"myRealm\"")));
		// @formatter:on
	}

	@Test
	public void requestWhenRealmNameConfiguredThenUsesOnAccessDenied() throws Exception {
		this.spring.register(RealmNameConfiguredOnAccessDeniedHandler.class, JwtDecoderConfig.class).autowire();
		JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
		given(decoder.decode(anyString())).willReturn(JWT);
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken("insufficiently_scoped")))
				.andExpect(status().isForbidden())
				.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer realm=\"myRealm\"")));
		// @formatter:on
	}

	@Test
	public void authenticationEntryPointWhenGivenNullThenThrowsException() {
		ApplicationContext context = mock(ApplicationContext.class);
		OAuth2ResourceServerConfigurer configurer = new OAuth2ResourceServerConfigurer(context);
		assertThatIllegalArgumentException().isThrownBy(() -> configurer.authenticationEntryPoint(null));
	}

	@Test
	public void accessDeniedHandlerWhenGivenNullThenThrowsException() {
		ApplicationContext context = mock(ApplicationContext.class);
		OAuth2ResourceServerConfigurer configurer = new OAuth2ResourceServerConfigurer(context);
		assertThatIllegalArgumentException().isThrownBy(() -> configurer.accessDeniedHandler(null));
	}

	@Test
	public void requestWhenCustomJwtValidatorFailsThenCorrespondingErrorMessage() throws Exception {
		this.spring.register(RestOperationsConfig.class, CustomJwtValidatorConfig.class).autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ValidNoScopes");
		OAuth2TokenValidator<Jwt> jwtValidator = this.spring.getContext()
			.getBean(CustomJwtValidatorConfig.class)
			.getJwtValidator();
		OAuth2Error error = new OAuth2Error("custom-error", "custom-description", "custom-uri");
		given(jwtValidator.validate(any(Jwt.class))).willReturn(OAuth2TokenValidatorResult.failure(error));
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(status().isUnauthorized())
				.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("custom-description")));
		// @formatter:on
	}

	@Test
	public void requestWhenClockSkewSetThenTimestampWindowRelaxedAccordingly() throws Exception {
		this.spring.register(RestOperationsConfig.class, UnexpiredJwtClockSkewConfig.class, BasicController.class)
			.autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ExpiresAt4687177990");
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(status().isOk());
		// @formatter:on
	}

	@Test
	public void requestWhenClockSkewSetButJwtStillTooLateThenReportsExpired() throws Exception {
		this.spring.register(RestOperationsConfig.class, ExpiredJwtClockSkewConfig.class, BasicController.class)
			.autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ExpiresAt4687177990");
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(status().isUnauthorized())
				.andExpect(invalidTokenHeader("Jwt expired at"));
		// @formatter:on
	}

	@Test
	public void requestWhenJwtAuthenticationConverterConfiguredOnDslThenIsUsed() throws Exception {
		this.spring
			.register(JwtDecoderConfig.class, JwtAuthenticationConverterConfiguredOnDsl.class, BasicController.class)
			.autowire();
		Converter<Jwt, JwtAuthenticationToken> jwtAuthenticationConverter = this.spring.getContext()
			.getBean(JwtAuthenticationConverterConfiguredOnDsl.class)
			.getJwtAuthenticationConverter();
		given(jwtAuthenticationConverter.convert(JWT)).willReturn(JWT_AUTHENTICATION_TOKEN);
		JwtDecoder jwtDecoder = this.spring.getContext().getBean(JwtDecoder.class);
		given(jwtDecoder.decode(anyString())).willReturn(JWT);
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken(JWT_TOKEN)))
				.andExpect(status().isOk());
		// @formatter:on
		verify(jwtAuthenticationConverter).convert(JWT);
	}

	@Test
	public void requestWhenJwtAuthenticationConverterCustomizedAuthoritiesThenThoseAuthoritiesArePropagated()
			throws Exception {
		this.spring.register(JwtDecoderConfig.class, CustomAuthorityMappingConfig.class, BasicController.class)
			.autowire();
		JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
		given(decoder.decode(JWT_TOKEN)).willReturn(JWT);
		// @formatter:off
		this.mvc.perform(get("/requires-read-scope").with(bearerToken(JWT_TOKEN)))
				.andExpect(status().isOk());
		// @formatter:on
	}

	@Test
	public void requestWhenUsingPublicKeyAndValidTokenThenAuthenticates() throws Exception {
		this.spring.register(SingleKeyConfig.class, BasicController.class).autowire();
		String token = this.token("ValidNoScopes");
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(status().isOk());
		// @formatter:on
	}

	@Test
	public void requestWhenUsingPublicKeyAndSignatureFailsThenReturnsInvalidToken() throws Exception {
		this.spring.register(SingleKeyConfig.class).autowire();
		String token = this.token("WrongSignature");
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(invalidTokenHeader("signature"));
		// @formatter:on
	}

	@Test
	public void requestWhenUsingPublicKeyAlgorithmDoesNotMatchThenReturnsInvalidToken() throws Exception {
		this.spring.register(SingleKeyConfig.class).autowire();
		String token = this.token("WrongAlgorithm");
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken(token)))
				.andExpect(invalidTokenHeader("algorithm"));
		// @formatter:on
	}

	// gh-7793
	@Test
	public void requestWhenUsingCustomAuthenticationEventPublisherThenUses() throws Exception {
		this.spring.register(CustomAuthenticationEventPublisher.class).autowire();
		given(bean(JwtDecoder.class).decode(anyString())).willThrow(new BadJwtException("problem"));
		this.mvc.perform(get("/").with(bearerToken("token")));
		verifyBean(AuthenticationEventPublisher.class)
			.publishAuthenticationFailure(any(OAuth2AuthenticationException.class), any(Authentication.class));
	}

	@Test
	public void getWhenCustomJwtAuthenticationManagerThenUsed() throws Exception {
		this.spring.register(JwtAuthenticationManagerConfig.class, BasicController.class).autowire();
		given(bean(AuthenticationProvider.class).authenticate(any(Authentication.class)))
			.willReturn(JWT_AUTHENTICATION_TOKEN);
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken("token")))
				.andExpect(status().isOk())
				.andExpect(content().string("mock-test-subject"));
		// @formatter:on
		verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class));
	}

	@Test
	public void getWhenDefaultAndCustomJwtAuthenticationManagerThenCustomUsed() throws Exception {
		this.spring.register(DefaultAndJwtAuthenticationManagerConfig.class, BasicController.class).autowire();
		DefaultAndJwtAuthenticationManagerConfig config = this.spring.getContext()
			.getBean(DefaultAndJwtAuthenticationManagerConfig.class);
		AuthenticationManager defaultAuthenticationManager = config.defaultAuthenticationManager();
		AuthenticationManager jwtAuthenticationManager = config.jwtAuthenticationManager();
		given(defaultAuthenticationManager.authenticate(any()))
			.willThrow(new RuntimeException("should not interact with default auth manager"));
		given(jwtAuthenticationManager.authenticate(any())).willReturn(JWT_AUTHENTICATION_TOKEN);
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken("token")))
				.andExpect(status().isOk())
				.andExpect(content().string("mock-test-subject"));
		// @formatter:on
		verify(jwtAuthenticationManager).authenticate(any(Authentication.class));
	}

	@Test
	public void getWhenIntrospectingThenOk() throws Exception {
		this.spring.register(RestOperationsConfig.class, OpaqueTokenConfig.class, BasicController.class).autowire();
		mockJsonRestOperations(json("Active"));
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken("token")))
				.andExpect(status().isOk())
				.andExpect(content().string("test-subject"));
		// @formatter:on
	}

	@Test
	public void getWhenOpaqueTokenInLambdaAndIntrospectingThenOk() throws Exception {
		this.spring.register(RestOperationsConfig.class, OpaqueTokenInLambdaConfig.class, BasicController.class)
			.autowire();
		mockJsonRestOperations(json("Active"));
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken("token")))
				.andExpect(status().isOk())
				.andExpect(content().string("test-subject"));
		// @formatter:on
	}

	@Test
	public void getWhenIntrospectionFailsThenUnauthorized() throws Exception {
		this.spring.register(RestOperationsConfig.class, OpaqueTokenConfig.class).autowire();
		mockJsonRestOperations(json("Inactive"));
		// @formatter:off
		this.mvc.perform(get("/").with(bearerToken("token")))
				.andExpect(status().isUnauthorized())
				.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("Provided token isn't active")));
		// @formatter:on
	}

	@Test
	public void getWhenIntrospectionLacksScopeThenForbidden() throws Exception {
		this.spring.register(RestOperationsConfig.class, OpaqueTokenConfig.class).autowire();
		mockJsonRestOperations(json("ActiveNoScopes"));
		// @formatter:off
		this.mvc.perform(get("/requires-read-scope").with(bearerToken("token")))
				.andExpect(status().isForbidden())
				.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("scope")));
		// @formatter:on
	}

	@Test
	public void getWhenCustomIntrospectionAuthenticationManagerThenUsed() throws Exception {
		this.spring.register(OpaqueTokenAuthenticationManagerConfig.class, BasicController.class).autowire();
		given(bean(AuthenticationProvider.class).authenticate(any(Authentication.class)))
			.willReturn(INTROSPECTION_AUTHENTICATION_TOKEN);
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken("token")))
				.andExpect(status().isOk())
				.andExpect(content().string("mock-test-subject"));
		// @formatter:on
		verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class));
	}

	@Test
	public void getWhenDefaultAndCustomIntrospectionAuthenticationManagerThenCustomUsed() throws Exception {
		this.spring.register(DefaultAndOpaqueTokenAuthenticationManagerConfig.class, BasicController.class).autowire();
		DefaultAndOpaqueTokenAuthenticationManagerConfig config = this.spring.getContext()
			.getBean(DefaultAndOpaqueTokenAuthenticationManagerConfig.class);
		AuthenticationManager defaultAuthenticationManager = config.defaultAuthenticationManager();
		AuthenticationManager opaqueTokenAuthenticationManager = config.opaqueTokenAuthenticationManager();
		given(defaultAuthenticationManager.authenticate(any()))
			.willThrow(new RuntimeException("should not interact with default auth manager"));
		given(opaqueTokenAuthenticationManager.authenticate(any())).willReturn(INTROSPECTION_AUTHENTICATION_TOKEN);
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken("token")))
				.andExpect(status().isOk())
				.andExpect(content().string("mock-test-subject"));
		// @formatter:on
		verify(opaqueTokenAuthenticationManager).authenticate(any(Authentication.class));
	}

	@Test
	public void getWhenCustomIntrospectionAuthenticationManagerInLambdaThenUsed() throws Exception {
		this.spring.register(OpaqueTokenAuthenticationManagerInLambdaConfig.class, BasicController.class).autowire();
		given(bean(AuthenticationProvider.class).authenticate(any(Authentication.class)))
			.willReturn(INTROSPECTION_AUTHENTICATION_TOKEN);
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken("token")))
				.andExpect(status().isOk())
				.andExpect(content().string("mock-test-subject"));
		// @formatter:on
		verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class));
	}

	@Test
	public void configureWhenOnlyIntrospectionUrlThenException() {
		assertThatExceptionOfType(BeanCreationException.class)
			.isThrownBy(() -> this.spring.register(OpaqueTokenHalfConfiguredConfig.class).autowire());
	}

	@Test
	public void getIntrospectionClientWhenConfiguredWithClientAndIntrospectionUriThenLastOneWins() {
		ApplicationContext context = mock(ApplicationContext.class);
		OpaqueTokenIntrospector client = mock(OpaqueTokenIntrospector.class);
		new OAuth2ResourceServerConfigurer<HttpSecurity>(context).opaqueToken((opaqueToken) -> {
			opaqueToken.introspectionUri(INTROSPECTION_URI);
			opaqueToken.introspectionClientCredentials(CLIENT_ID, CLIENT_SECRET);
			opaqueToken.introspector(client);
			assertThat(opaqueToken.getIntrospector()).isEqualTo(client);
		});
		new OAuth2ResourceServerConfigurer<HttpSecurity>(context).opaqueToken((opaqueToken) -> {
			opaqueToken.introspector(client);
			opaqueToken.introspectionUri(INTROSPECTION_URI);
			opaqueToken.introspectionClientCredentials(CLIENT_ID, CLIENT_SECRET);
			assertThat(opaqueToken.getIntrospector()).isNotSameAs(client);
		});
	}

	@Test
	public void getIntrospectionClientWhenDslAndBeanWiredThenDslTakesPrecedence() {
		GenericApplicationContext context = new GenericApplicationContext();
		registerMockBean(context, "introspectionClientOne", OpaqueTokenIntrospector.class);
		registerMockBean(context, "introspectionClientTwo", OpaqueTokenIntrospector.class);
		new OAuth2ResourceServerConfigurer<HttpSecurity>(context).opaqueToken((opaqueToken) -> {
			opaqueToken.introspectionUri(INTROSPECTION_URI);
			opaqueToken.introspectionClientCredentials(CLIENT_ID, CLIENT_SECRET);
			assertThat(opaqueToken.getIntrospector()).isNotNull();
		});
	}

	@Test
	public void requestWhenBasicAndResourceServerEntryPointsThenMatchedByRequest() throws Exception {
		this.spring.register(BasicAndResourceServerConfig.class, JwtDecoderConfig.class).autowire();
		JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
		given(decoder.decode(anyString())).willThrow(BadJwtException.class);
		// @formatter:off
		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("Basic")));
		this.mvc.perform(get("/authenticated").with(bearerToken("invalid_token")))
				.andExpect(status().isUnauthorized())
				.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer")));
		// @formatter:on
	}

	@Test
	public void requestWhenFormLoginAndResourceServerEntryPointsThenSessionCreatedByRequest() throws Exception {
		this.spring.register(FormAndResourceServerConfig.class, JwtDecoderConfig.class).autowire();
		JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
		given(decoder.decode(anyString())).willThrow(BadJwtException.class);
		// @formatter:off
		MvcResult result = this.mvc.perform(get("/authenticated")
				.header("Accept", "text/html"))
				.andExpect(status().isFound())
				.andExpect(redirectedUrl("/login"))
				.andReturn();
		// @formatter:on
		assertThat(result.getRequest().getSession(false)).isNotNull();
		// @formatter:off
		result = this.mvc.perform(get("/authenticated").with(bearerToken("token")))
				.andExpect(status().isUnauthorized())
				.andReturn();
		// @formatter:on
		assertThat(result.getRequest().getSession(false)).isNull();
	}

	@Test
	public void unauthenticatedRequestWhenFormOAuth2LoginAndResourceServerThenNegotiates() throws Exception {
		this.spring.register(OAuth2LoginAndResourceServerConfig.class, JwtDecoderConfig.class).autowire();
		this.mvc.perform(get("/any").header("X-Requested-With", "XMLHttpRequest")).andExpect(status().isUnauthorized());
		this.mvc.perform(get("/any").header("Accept", "application/json")).andExpect(status().isUnauthorized());
		this.mvc.perform(get("/any").header("Accept", "text/html")).andExpect(status().is3xxRedirection());
		this.mvc.perform(get("/any").header("Accept", "image/jpg")).andExpect(status().is3xxRedirection());
	}

	@Test
	public void requestWhenDefaultAndResourceServerAccessDeniedHandlersThenMatchedByRequest() throws Exception {
		this.spring
			.register(ExceptionHandlingAndResourceServerWithAccessDeniedHandlerConfig.class, JwtDecoderConfig.class)
			.autowire();
		JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
		given(decoder.decode(anyString())).willReturn(JWT);
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(httpBasic("basic-user", "basic-password")))
				.andExpect(status().isForbidden())
				.andExpect(header().doesNotExist(HttpHeaders.WWW_AUTHENTICATE));
		this.mvc.perform(get("/authenticated").with(bearerToken("insufficiently_scoped")))
				.andExpect(status().isForbidden())
				.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer")));
		// @formatter:on
	}

	@Test
	public void getWhenAlsoUsingHttpBasicThenCorrectProviderEngages() throws Exception {
		this.spring.register(RestOperationsConfig.class, BasicAndResourceServerConfig.class, BasicController.class)
			.autowire();
		mockJwksRestOperations(jwks("Default"));
		String token = this.token("ValidNoScopes");
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken(token)))
				.andExpect(status().isOk())
				.andExpect(content().string("test-subject"));
		this.mvc.perform(get("/authenticated").with(httpBasic("basic-user", "basic-password")))
				.andExpect(status().isOk())
				.andExpect(content().string("basic-user"));
		// @formatter:on
	}

	@Test
	public void getAuthenticationManagerWhenConfiguredAuthenticationManagerThenTakesPrecedence() {
		ApplicationContext context = mock(ApplicationContext.class);
		OAuth2ResourceServerConfigurer<HttpSecurity> oauth2ResourceServer = new OAuth2ResourceServerConfigurer<>(
				context);
		AuthenticationManager authenticationManager = mock(AuthenticationManager.class);
		oauth2ResourceServer
			.jwt((jwt) -> jwt.authenticationManager(authenticationManager).decoder(mock(JwtDecoder.class)));
		assertThat(oauth2ResourceServer.getAuthenticationManager(null)).isSameAs(authenticationManager);
		oauth2ResourceServer = new OAuth2ResourceServerConfigurer<>(context);
		oauth2ResourceServer.opaqueToken((opaqueToken) -> opaqueToken.authenticationManager(authenticationManager)
			.introspector(mock(OpaqueTokenIntrospector.class)));
		assertThat(oauth2ResourceServer.getAuthenticationManager(null)).isSameAs(authenticationManager);
	}

	@Test
	public void getWhenMultipleIssuersThenUsesIssuerClaimToDifferentiate() throws Exception {
		this.spring.register(WebServerConfig.class, MultipleIssuersConfig.class, BasicController.class).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);
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken(jwtOne)))
				.andExpect(status().isOk())
				.andExpect(content().string("test-subject"));
		// @formatter:on
		mockWebServer(String.format(metadata, issuerTwo, issuerTwo));
		mockWebServer(jwkSet);
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken(jwtTwo)))
				.andExpect(status().isOk())
				.andExpect(content().string("test-subject"));
		// @formatter:on
		mockWebServer(String.format(metadata, issuerThree, issuerThree));
		mockWebServer(jwkSet);
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken(jwtThree)))
				.andExpect(status().isUnauthorized())
				.andExpect(invalidTokenHeader("Invalid issuer"));
		// @formatter:on
	}

	@Test
	public void configuredWhenMissingJwtAuthenticationProviderThenWiringException() {
		assertThatExceptionOfType(BeanCreationException.class)
			.isThrownBy(() -> this.spring.register(JwtlessConfig.class).autowire())
			.withMessageContaining("neither was found");
	}

	@Test
	public void configureWhenMissingJwkSetUriThenWiringException() {
		assertThatExceptionOfType(BeanCreationException.class)
			.isThrownBy(() -> this.spring.register(JwtHalfConfiguredConfig.class).autowire())
			.withMessageContaining("No qualifying bean of type");
	}

	@Test
	public void configureWhenUsingBothJwtAndOpaqueThenWiringException() {
		assertThatExceptionOfType(BeanCreationException.class)
			.isThrownBy(() -> this.spring.register(OpaqueAndJwtConfig.class).autowire())
			.withMessageContaining("Spring Security only supports JWTs or Opaque Tokens");
	}

	@Test
	public void configureWhenUsingBothAuthenticationManagerResolverAndOpaqueThenWiringException() {
		assertThatExceptionOfType(BeanCreationException.class)
			.isThrownBy(() -> this.spring.register(AuthenticationManagerResolverPlusOtherConfig.class).autowire())
			.withMessageContaining("authenticationManagerResolver");
	}

	@Test
	public void getJwtAuthenticationConverterWhenNoConverterSpecifiedThenTheDefaultIsUsed() {
		ApplicationContext context = this.spring.context(new GenericWebApplicationContext()).getContext();
		new OAuth2ResourceServerConfigurer<HttpSecurity>(context)
			.jwt((jwt) -> assertThat(jwt.getJwtAuthenticationConverter())
				.isInstanceOf(JwtAuthenticationConverter.class));
	}

	@Test
	public void getJwtAuthenticationConverterWhenConverterBeanSpecified() {
		JwtAuthenticationConverter converterBean = new JwtAuthenticationConverter();
		GenericWebApplicationContext context = new GenericWebApplicationContext();
		context.registerBean(JwtAuthenticationConverter.class, () -> converterBean);
		this.spring.context(context).autowire();
		new OAuth2ResourceServerConfigurer<HttpSecurity>(context)
			.jwt((jwt) -> assertThat(jwt.getJwtAuthenticationConverter()).isEqualTo(converterBean));
	}

	@Test
	public void getJwtAuthenticationConverterWhenConverterBeanAndAnotherOnTheDslThenTheDslOneIsUsed() {
		JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
		JwtAuthenticationConverter converterBean = new JwtAuthenticationConverter();
		GenericWebApplicationContext context = new GenericWebApplicationContext();
		context.registerBean(JwtAuthenticationConverter.class, () -> converterBean);
		this.spring.context(context).autowire();
		new OAuth2ResourceServerConfigurer<HttpSecurity>(context).jwt((jwt) -> {
			jwt.jwtAuthenticationConverter(converter);
			assertThat(jwt.getJwtAuthenticationConverter()).isEqualTo(converter);
		});
	}

	@Test
	public void getJwtAuthenticationConverterWhenDuplicateConverterBeansAndAnotherOnTheDslThenTheDslOneIsUsed() {
		JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
		JwtAuthenticationConverter converterBean = new JwtAuthenticationConverter();
		GenericWebApplicationContext context = new GenericWebApplicationContext();
		context.registerBean("converterOne", JwtAuthenticationConverter.class, () -> converterBean);
		context.registerBean("converterTwo", JwtAuthenticationConverter.class, () -> converterBean);
		this.spring.context(context).autowire();
		new OAuth2ResourceServerConfigurer<HttpSecurity>(context).jwt((jwt) -> {
			jwt.jwtAuthenticationConverter(converter);
			assertThat(jwt.getJwtAuthenticationConverter()).isEqualTo(converter);
		});
	}

	@Test
	public void getJwtAuthenticationConverterWhenDuplicateConverterBeansThenThrowsException() {
		JwtAuthenticationConverter converterBean = new JwtAuthenticationConverter();
		GenericWebApplicationContext context = new GenericWebApplicationContext();
		context.registerBean("converterOne", JwtAuthenticationConverter.class, () -> converterBean);
		context.registerBean("converterTwo", JwtAuthenticationConverter.class, () -> converterBean);
		this.spring.context(context).autowire();
		new OAuth2ResourceServerConfigurer<HttpSecurity>(context)
			.jwt((jwt) -> assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
				.isThrownBy(jwt::getJwtAuthenticationConverter));
	}

	@Test
	public void getWhenCustomAuthenticationConverterThenUsed() throws Exception {
		this.spring
			.register(RestOperationsConfig.class, OpaqueTokenAuthenticationConverterConfig.class, BasicController.class)
			.autowire();
		OpaqueTokenAuthenticationConverter authenticationConverter = bean(OpaqueTokenAuthenticationConverter.class);
		given(authenticationConverter.convert(anyString(), any(OAuth2AuthenticatedPrincipal.class)))
			.willReturn(new TestingAuthenticationToken("jdoe", null, Collections.emptyList()));
		mockJsonRestOperations(json("Active"));
		// @formatter:off
		this.mvc.perform(get("/authenticated").with(bearerToken("token")))
				.andExpect(status().isOk())
				.andExpect(content().string("jdoe"));
		// @formatter:on
		verify(authenticationConverter).convert(any(), any());
	}

	@Test
	public void getAuthenticationConverterWhenDuplicateConverterBeansAndAnotherOnTheDslThenTheDslOneIsUsed() {
		AuthenticationConverter converter = mock(AuthenticationConverter.class);
		AuthenticationConverter converterBean = mock(AuthenticationConverter.class);
		GenericWebApplicationContext context = new GenericWebApplicationContext();
		context.registerBean("converterOne", AuthenticationConverter.class, () -> converterBean);
		context.registerBean("converterTwo", AuthenticationConverter.class, () -> converterBean);
		this.spring.context(context).autowire();
		OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
		oauth2.authenticationConverter(converter);
		assertThat(oauth2.getAuthenticationConverter()).isEqualTo(converter);
	}

	@Test
	public void getAuthenticationConverterWhenConverterBeanAndAnotherOnTheDslThenTheDslOneIsUsed() {
		AuthenticationConverter converter = mock(AuthenticationConverter.class);
		AuthenticationConverter converterBean = mock(AuthenticationConverter.class);
		GenericWebApplicationContext context = new GenericWebApplicationContext();
		context.registerBean(AuthenticationConverter.class, () -> converterBean);
		this.spring.context(context).autowire();
		OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
		oauth2.authenticationConverter(converter);
		assertThat(oauth2.getAuthenticationConverter()).isEqualTo(converter);
	}

	@Test
	public void getAuthenticationConverterWhenDuplicateConverterBeansThenWiringException() {
		assertThatExceptionOfType(BeanCreationException.class)
			.isThrownBy(
					() -> this.spring.register(MultipleAuthenticationConverterBeansConfig.class, JwtDecoderConfig.class)
						.autowire())
			.withRootCauseInstanceOf(NoUniqueBeanDefinitionException.class);
	}

	@Test
	public void getAuthenticationConverterWhenNoConverterSpecifiedThenTheDefaultIsUsed() {
		ApplicationContext context = this.spring.context(new GenericWebApplicationContext()).getContext();
		OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
		assertThat(oauth2.getAuthenticationConverter()).isInstanceOf(BearerTokenAuthenticationConverter.class);
	}

	private static <T> void registerMockBean(GenericApplicationContext context, String name, Class<T> clazz) {
		context.registerBean(name, clazz, () -> mock(clazz));
	}

	private static BearerTokenRequestPostProcessor bearerToken(String token) {
		return new BearerTokenRequestPostProcessor(token);
	}

	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 StringContains(", " + "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""),
						new StringEndsWith(
								", " + "resource_metadata=\"http://localhost/.well-known/oauth-protected-resource\"")));
	}

	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 StringContains(", " + "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""),
						new StringEndsWith(
								", " + "resource_metadata=\"http://localhost/.well-known/oauth-protected-resource\"")));
	}

	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(JwtClaimNames.ISS, issuer);
		claims.put(JwtClaimNames.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);
		given(rest.exchange(any(RequestEntity.class), eq(String.class))).willReturn(entity);
	}

	private void mockJwksRestOperations(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);
		given(rest.exchange(any(RequestEntity.class), eq(String.class))).willReturn(entity);
	}

	private void mockJsonRestOperations(String response) {
		try {
			RestOperations rest = this.spring.getContext().getBean(RestOperations.class);
			HttpHeaders headers = new HttpHeaders();
			headers.setContentType(MediaType.APPLICATION_JSON);
			ResponseEntity<Map<String, Object>> entity = new ResponseEntity<>(JSONObjectUtils.parse(response), headers,
					HttpStatus.OK);
			given(rest.exchange(any(RequestEntity.class), eq(new ParameterizedTypeReference<Map<String, Object>>() {
			}))).willReturn(entity);
		}
		catch (Exception ex) {
			throw new IllegalArgumentException(ex);
		}
	}

	private <T> T bean(Class<T> beanClass) {
		return this.spring.getContext().getBean(beanClass);
	}

	private <T> T verifyBean(Class<T> beanClass) {
		return verify(this.spring.getContext().getBean(beanClass));
	}

	private <T> T verifyBean(Class<T> beanClass, VerificationMode mode) {
		return verify(this.spring.getContext().getBean(beanClass), mode);
	}

	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());
		}
	}

	@Configuration
	@EnableWebSecurity
	@EnableWebMvc
	static class DefaultConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read")
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.jwt(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	@EnableWebMvc
	static class DefaultInLambdaConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((authorize) -> authorize
						.requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read")
						.anyRequest().authenticated()
				)
				.oauth2ResourceServer((oauth2) -> oauth2
						.jwt(withDefaults())
				);
			return http.build();
			// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	@EnableWebMvc
	static class JwkSetUriConfig {

		@Value("${mockwebserver.url:https://example.org}")
		String jwkSetUri;

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			DefaultBearerTokenResolver defaultBearerTokenResolver = new DefaultBearerTokenResolver();
			defaultBearerTokenResolver.setAllowUriQueryParameter(true);
			http
				.authorizeHttpRequests((requests) -> requests
					.requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read")
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.bearerTokenResolver(defaultBearerTokenResolver)
					.jwt((jwt) -> jwt.jwkSetUri(this.jwkSetUri)));
			return http.build();
			// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	@EnableWebMvc
	static class JwkSetUriInLambdaConfig {

		@Value("${mockwebserver.url:https://example.org}")
		String jwkSetUri;

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((authorize) -> authorize
						.requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read")
						.anyRequest().authenticated()
				)
				.oauth2ResourceServer((oauth2) -> oauth2
						.jwt((jwt) -> jwt
								.jwkSetUri(this.jwkSetUri)
						)
				);
			return http.build();
			// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	@EnableWebMvc
	static class CsrfDisabledConfig {

		@Value("${mockwebserver.url:https://example.org}")
		String jwkSetUri;

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read")
					.anyRequest().authenticated())
				.csrf((csrf) -> csrf.disable())
				.oauth2ResourceServer((server) -> server
					.jwt((jwt) -> jwt.jwkSetUri(this.jwkSetUri)));
			return http.build();
			// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	static class AnonymousDisabledConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.anonymous((anonymous) -> anonymous.disable())
				.oauth2ResourceServer((server) -> server
					.jwt(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	@EnableGlobalMethodSecurity(prePostEnabled = true)
	static class MethodSecurityConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.jwt(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	static class JwtlessConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.oauth2ResourceServer(withDefaults());
			return http.build();
			// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	static class RealmNameConfiguredOnEntryPoint {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.authenticationEntryPoint(authenticationEntryPoint())
					.jwt(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

		AuthenticationEntryPoint authenticationEntryPoint() {
			BearerTokenAuthenticationEntryPoint entryPoint = new BearerTokenAuthenticationEntryPoint();
			entryPoint.setRealmName("myRealm");
			return entryPoint;
		}

	}

	@Configuration
	@EnableWebSecurity
	static class RealmNameConfiguredOnAccessDeniedHandler {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().denyAll())
				.oauth2ResourceServer((server) -> server
					.accessDeniedHandler(accessDeniedHandler())
					.jwt(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

		AccessDeniedHandler accessDeniedHandler() {
			BearerTokenAccessDeniedHandler accessDeniedHandler = new BearerTokenAccessDeniedHandler();
			accessDeniedHandler.setRealmName("myRealm");
			return accessDeniedHandler;
		}

	}

	@Configuration
	@EnableWebSecurity
	static class ExceptionHandlingAndResourceServerWithAccessDeniedHandlerConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().denyAll())
				.exceptionHandling((handling) -> handling
					.defaultAccessDeniedHandlerFor(new AccessDeniedHandlerImpl(), (request) -> false))
				.httpBasic(withDefaults())
				.oauth2ResourceServer((server) -> server
					.jwt(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

		@Bean
		UserDetailsService userDetailsService() {
			return new InMemoryUserDetailsManager(
			// @formatter:off
					org.springframework.security.core.userdetails.User.withDefaultPasswordEncoder()
							.username("basic-user")
							.password("basic-password")
							.roles("USER")
							.build());
					// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	static class JwtAuthenticationConverterConfiguredOnDsl {

		private final Converter<Jwt, JwtAuthenticationToken> jwtAuthenticationConverter = mock(Converter.class);

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.jwt((jwt) -> jwt
						.jwtAuthenticationConverter(getJwtAuthenticationConverter())));
			return http.build();
			// @formatter:on
		}

		Converter<Jwt, JwtAuthenticationToken> getJwtAuthenticationConverter() {
			return this.jwtAuthenticationConverter;
		}

	}

	@Configuration
	@EnableWebSecurity
	@EnableWebMvc
	static class CustomAuthorityMappingConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.requestMatchers("/requires-read-scope").hasAuthority("message:read"))
				.oauth2ResourceServer((server) -> server
					.jwt((jwt) -> jwt
						.jwtAuthenticationConverter(getJwtAuthenticationConverter())));
			return http.build();
			// @formatter:on
		}

		Converter<Jwt, AbstractAuthenticationToken> getJwtAuthenticationConverter() {
			JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
			converter.setJwtGrantedAuthoritiesConverter(
					(jwt) -> Collections.singletonList(new SimpleGrantedAuthority("message:read")));
			return converter;
		}

	}

	@Configuration
	@EnableWebSecurity
	static class BasicAndResourceServerConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.httpBasic(withDefaults())
				.oauth2ResourceServer((server) -> server
					.jwt(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

		@Bean
		UserDetailsService userDetailsService() {
			return new InMemoryUserDetailsManager(
			// @formatter:off
					org.springframework.security.core.userdetails.User.withDefaultPasswordEncoder()
							.username("basic-user")
							.password("basic-password")
							.roles("USER")
							.build());
					// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	static class FormAndResourceServerConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.formLogin(withDefaults())
				.oauth2ResourceServer((server) -> server
					.jwt(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	static class OAuth2LoginAndResourceServerConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((authz) -> authz
					.anyRequest().authenticated()
				)
				.oauth2Login(withDefaults())
				.oauth2ResourceServer((oauth2) -> oauth2.jwt(withDefaults()));
			return http.build();
			// @formatter:on
		}

		@Bean
		ClientRegistrationRepository clients() {
			ClientRegistration registration = TestClientRegistrations.clientRegistration().build();
			return new InMemoryClientRegistrationRepository(registration);
		}

	}

	@Configuration
	@EnableWebSecurity
	static class JwtHalfConfiguredConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.jwt(Customizer.withDefaults()));
			return http.build(); // missing key configuration, e.g. jwkSetUri
			// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	static class AlwaysSessionCreationConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.sessionManagement((management) -> management
					.sessionCreationPolicy(SessionCreationPolicy.ALWAYS))
				.oauth2ResourceServer((server) -> server
					.jwt(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	static class AllowBearerTokenInRequestBodyConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.bearerTokenResolver(allowRequestBody())
					.jwt(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

		private BearerTokenResolver allowRequestBody() {
			DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver();
			resolver.setAllowFormEncodedBodyParameter(true);
			return resolver;
		}

	}

	@Configuration
	@EnableWebSecurity
	static class AllowBearerTokenAsQueryParameterConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.jwt(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

		@Bean
		BearerTokenResolver allowQueryParameter() {
			DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver();
			resolver.setAllowUriQueryParameter(true);
			return resolver;
		}

	}

	@Configuration
	@EnableWebSecurity
	static class MultipleBearerTokenResolverBeansConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.jwt(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

		@Bean
		BearerTokenResolver resolverOne() {
			DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver();
			resolver.setAllowUriQueryParameter(true);
			return resolver;
		}

		@Bean
		BearerTokenResolver resolverTwo() {
			DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver();
			resolver.setAllowFormEncodedBodyParameter(true);
			return resolver;
		}

	}

	@Configuration
	@EnableWebSecurity
	static class CustomAuthenticationDetailsSource {

		AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = mock(
				AuthenticationDetailsSource.class);

		@Bean
		SecurityFilterChain web(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((authorize) -> authorize
					.anyRequest().authenticated()
				)
				.oauth2ResourceServer((oauth2) -> oauth2
					.jwt(withDefaults())
					.withObjectPostProcessor(new ObjectPostProcessor<BearerTokenAuthenticationFilter>() {
						@Override
						public BearerTokenAuthenticationFilter postProcess(BearerTokenAuthenticationFilter object) {
							object.setAuthenticationDetailsSource(CustomAuthenticationDetailsSource.this.authenticationDetailsSource);
							return object;
						}
					})
				);
			return http.build();
		}

		@Bean
		AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource() {
			return this.authenticationDetailsSource;
		}
	}

	@Configuration
	@EnableWebSecurity
	static class CustomJwtDecoderOnDsl {

		JwtDecoder decoder = mock(JwtDecoder.class);

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.jwt((jwt) -> jwt.decoder(decoder())));
			return http.build();
			// @formatter:on
		}

		JwtDecoder decoder() {
			return this.decoder;
		}

	}

	@Configuration
	@EnableWebSecurity
	static class CustomJwtDecoderInLambdaOnDsl {

		JwtDecoder decoder = mock(JwtDecoder.class);

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((authorize) -> authorize
						.anyRequest().authenticated()
				)
				.oauth2ResourceServer((oauth2) -> oauth2
						.jwt((jwt) -> jwt
								.decoder(decoder())
						)
				);
			return http.build();
			// @formatter:on
		}

		JwtDecoder decoder() {
			return this.decoder;
		}

	}

	@Configuration
	@EnableWebSecurity
	static class CustomJwtDecoderAsBean {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.jwt(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

		@Bean
		JwtDecoder decoder() {
			return mock(JwtDecoder.class);
		}

	}

	@Configuration
	@EnableWebSecurity
	static class JwtAuthenticationManagerConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.jwt((jwt) -> jwt
						.authenticationManager(authenticationProvider()::authenticate)));
			return http.build();
			// @formatter:on
		}

		@Bean
		AuthenticationProvider authenticationProvider() {
			return mock(AuthenticationProvider.class);
		}

	}

	@Configuration
	@EnableWebSecurity
	static class DefaultAndJwtAuthenticationManagerConfig {

		AuthenticationManager defaultAuthenticationManager = mock(AuthenticationManager.class);

		AuthenticationManager jwtAuthenticationManager = mock(AuthenticationManager.class);

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
					.authenticationManager(this.defaultAuthenticationManager)
					.authorizeHttpRequests((authz) -> authz
							.anyRequest().authenticated()
					)
					.oauth2ResourceServer((oauth2) -> oauth2
							.jwt((jwt) -> jwt
									.authenticationManager(this.jwtAuthenticationManager)
							)
					);
			return http.build();
			// @formatter:on
		}

		AuthenticationManager defaultAuthenticationManager() {
			return this.defaultAuthenticationManager;
		}

		AuthenticationManager jwtAuthenticationManager() {
			return this.jwtAuthenticationManager;
		}

	}

	@Configuration
	@EnableWebSecurity
	static class CustomJwtValidatorConfig {

		@Autowired
		NimbusJwtDecoder jwtDecoder;

		private final OAuth2TokenValidator<Jwt> jwtValidator = mock(OAuth2TokenValidator.class);

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			this.jwtDecoder.setJwtValidator(this.jwtValidator);
			// @formatter:off
			http
				.oauth2ResourceServer((server) -> server
					.jwt(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

		OAuth2TokenValidator<Jwt> getJwtValidator() {
			return this.jwtValidator;
		}

	}

	@Configuration
	@EnableWebSecurity
	static class UnexpiredJwtClockSkewConfig {

		@Autowired
		NimbusJwtDecoder jwtDecoder;

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			Clock nearlyAnHourFromTokenExpiry = Clock.fixed(Instant.ofEpochMilli(4687181540000L),
					ZoneId.systemDefault());
			JwtTimestampValidator jwtValidator = new JwtTimestampValidator(Duration.ofHours(1));
			jwtValidator.setClock(nearlyAnHourFromTokenExpiry);
			this.jwtDecoder.setJwtValidator(jwtValidator);
			// @formatter:off
			http
				.oauth2ResourceServer((server) -> server
					.jwt(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	static class ExpiredJwtClockSkewConfig {

		@Autowired
		NimbusJwtDecoder jwtDecoder;

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			Clock justOverOneHourAfterExpiry = Clock.fixed(Instant.ofEpochMilli(4687181595000L),
					ZoneId.systemDefault());
			JwtTimestampValidator jwtValidator = new JwtTimestampValidator(Duration.ofHours(1));
			jwtValidator.setClock(justOverOneHourAfterExpiry);
			this.jwtDecoder.setJwtValidator(jwtValidator);
			// @formatter:off
			http
				.oauth2ResourceServer((server) -> server
					.jwt(Customizer.withDefaults()));
			return http.build();
		}
	}
	@Configuration
	@EnableWebSecurity
	static class SingleKeyConfig {
		byte[] spec = Base64.getDecoder().decode(
				"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoXJ8OyOv/eRnce4akdan" +
				"R4KYRfnC2zLV4uYNQpcFn6oHL0dj7D6kxQmsXoYgJV8ZVDn71KGmuLvolxsDncc2" +
				"UrhyMBY6DVQVgMSVYaPCTgW76iYEKGgzTEw5IBRQL9w3SRJWd3VJTZZQjkXef48O" +
				"cz06PGF3lhbz4t5UEZtdF4rIe7u+977QwHuh7yRPBQ3sII+cVoOUMgaXB9SHcGF2" +
				"iZCtPzL/IffDUcfhLQteGebhW8A6eUHgpD5A1PQ+JCw/G7UOzZAjjDjtNM2eqm8j" +
				"+Ms/gqnm4MiCZ4E+9pDN77CAAPVN7kuX6ejs9KBXpk01z48i9fORYk9u7rAkh1Hu" +
				"QwIDAQAB");

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.jwt(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

		@Bean
		JwtDecoder decoder() throws Exception {
			RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
				.generatePublic(new X509EncodedKeySpec(this.spec));
			return NimbusJwtDecoder.withPublicKey(publicKey).build();
		}

	}

	@Configuration
	@EnableWebSecurity
	static class CustomAuthenticationEventPublisher {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.jwt(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

		@Bean
		JwtDecoder jwtDecoder() {
			return mock(JwtDecoder.class);
		}

		@Bean
		AuthenticationEventPublisher authenticationEventPublisher() {
			return mock(AuthenticationEventPublisher.class);
		}

	}

	@Configuration
	@EnableWebSecurity
	@EnableWebMvc
	static class OpaqueTokenConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read")
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.opaqueToken(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	@EnableWebMvc
	static class OpaqueTokenInLambdaConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((authorize) -> authorize
						.requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read")
						.anyRequest().authenticated()
				)
				.oauth2ResourceServer((oauth2) -> oauth2
						.opaqueToken(withDefaults())
				);
			return http.build();
			// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	static class OpaqueTokenAuthenticationManagerConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.opaqueToken((opaqueToken) -> opaqueToken
						.authenticationManager(authenticationProvider()::authenticate)));
			return http.build();
			// @formatter:on
		}

		@Bean
		AuthenticationProvider authenticationProvider() {
			return mock(AuthenticationProvider.class);
		}

	}

	@Configuration
	@EnableWebSecurity
	static class OpaqueTokenAuthenticationManagerInLambdaConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((authorize) -> authorize
						.anyRequest().authenticated()
				)
				.oauth2ResourceServer((oauth2) -> oauth2
						.opaqueToken((opaqueToken) -> opaqueToken
								.authenticationManager(authenticationProvider()::authenticate)
						)
				);
			return http.build();
			// @formatter:on
		}

		@Bean
		AuthenticationProvider authenticationProvider() {
			return mock(AuthenticationProvider.class);
		}

	}

	@Configuration
	@EnableWebSecurity
	static class DefaultAndOpaqueTokenAuthenticationManagerConfig {

		AuthenticationManager defaultAuthenticationManager = mock(AuthenticationManager.class);

		AuthenticationManager opaqueTokenAuthenticationManager = mock(AuthenticationManager.class);

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
					.authenticationManager(this.defaultAuthenticationManager)
					.authorizeHttpRequests((authz) -> authz
							.anyRequest().authenticated()
					)
					.oauth2ResourceServer((oauth2) -> oauth2
							.opaqueToken((opaque) -> opaque
									.authenticationManager(this.opaqueTokenAuthenticationManager)
							)
					);
			return http.build();
			// @formatter:on
		}

		AuthenticationManager defaultAuthenticationManager() {
			return this.defaultAuthenticationManager;
		}

		AuthenticationManager opaqueTokenAuthenticationManager() {
			return this.opaqueTokenAuthenticationManager;
		}

	}

	@Configuration
	@EnableWebSecurity
	static class OpaqueAndJwtConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.oauth2ResourceServer((server) -> server
					.jwt(Customizer.withDefaults())
					.opaqueToken(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	static class OpaqueTokenHalfConfiguredConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.opaqueToken((opaqueToken) -> opaqueToken
						.introspectionUri("https://idp.example.com")));
			return http.build(); // missing credentials
			// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	static class MultipleAuthenticationConverterBeansConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.jwt(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

		@Bean
		AuthenticationConverter authenticationConverterOne() {
			DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver();
			resolver.setAllowUriQueryParameter(true);
			BearerTokenAuthenticationConverter authenticationConverter = new BearerTokenAuthenticationConverter();
			authenticationConverter.setBearerTokenResolver(resolver);
			return authenticationConverter;
		}

		@Bean
		AuthenticationConverter authenticationConverterTwo() {
			DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver();
			resolver.setAllowUriQueryParameter(true);
			BearerTokenAuthenticationConverter authenticationConverter = new BearerTokenAuthenticationConverter();
			authenticationConverter.setBearerTokenResolver(resolver);
			return authenticationConverter;
		}

	}

	@Configuration
	@EnableWebSecurity
	static class MultipleIssuersConfig {

		@Autowired
		MockWebServer web;

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			String issuerOne = this.web.url("/issuerOne").toString();
			String issuerTwo = this.web.url("/issuerTwo").toString();
			JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = JwtIssuerAuthenticationManagerResolver
				.fromTrustedIssuers(issuerOne, issuerTwo);
			// @formatter:off
			http
				.oauth2ResourceServer((server) -> server
					.authenticationManagerResolver(authenticationManagerResolver))
				.anonymous(AbstractHttpConfigurer::disable);
			return http.build();
			// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	static class AuthenticationManagerResolverPlusOtherConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.authenticationManagerResolver(mock(AuthenticationManagerResolver.class))
					.opaqueToken(Customizer.withDefaults()));
			return http.build();
			// @formatter:on
		}

	}

	@Configuration
	@EnableWebSecurity
	@EnableWebMvc
	static class OpaqueTokenAuthenticationConverterConfig {

		@Bean
		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			// @formatter:off
			http
				.authorizeHttpRequests((requests) -> requests
					.requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read")
					.anyRequest().authenticated())
				.oauth2ResourceServer((server) -> server
					.opaqueToken((opaqueToken) -> opaqueToken
						.authenticationConverter(authenticationConverter())));
			return http.build();
			// @formatter:on
		}

		@Bean
		OpaqueTokenAuthenticationConverter authenticationConverter() {
			return mock(OpaqueTokenAuthenticationConverter.class);
		}

	}

	@Configuration
	static class JwtDecoderConfig {

		@Bean
		JwtDecoder jwtDecoder() {
			return mock(JwtDecoder.class);
		}

	}

	@RestController
	static class BasicController {

		@GetMapping("/")
		String get() {
			return "ok";
		}

		@PostMapping("/post")
		String post() {
			return "post";
		}

		@RequestMapping(value = "/authenticated", method = { RequestMethod.GET, RequestMethod.POST })
		String authenticated(Authentication authentication) {
			return authentication.getName();
		}

		@GetMapping("/requires-read-scope")
		String requiresReadScope(JwtAuthenticationToken token) {
			return token.getAuthorities()
				.stream()
				.filter((ga) -> ga.getAuthority().startsWith("SCOPE_"))
				.map(GrantedAuthority::getAuthority)
				.collect(Collectors.toList())
				.toString();
		}

		@GetMapping("/ms-requires-read-scope")
		@PreAuthorize("hasAuthority('SCOPE_message:read')")
		String msRequiresReadScope(JwtAuthenticationToken token) {
			return requiresReadScope(token);
		}

		@GetMapping("/ms-deny")
		@PreAuthorize("denyAll")
		String deny() {
			return "hmm, that's odd";
		}

	}

	@Configuration
	static class WebServerConfig implements BeanPostProcessor, EnvironmentAware {

		private final MockWebServer server = new MockWebServer();

		@PreDestroy
		void shutdown() throws IOException {
			this.server.shutdown();
		}

		@Override
		public void setEnvironment(Environment environment) {
			if (environment instanceof ConfigurableEnvironment) {
				((ConfigurableEnvironment) environment).getPropertySources()
					.addFirst(new MockWebServerPropertySource());
			}
		}

		@Bean
		MockWebServer web() {
			return this.server;
		}

		private class MockWebServerPropertySource extends PropertySource {

			MockWebServerPropertySource() {
				super("mockwebserver");
			}

			@Override
			public Object getProperty(String name) {
				if ("mockwebserver.url".equals(name)) {
					return WebServerConfig.this.server.url("/.well-known/jwks.json").toString();
				}
				else {
					return null;
				}
			}

		}

	}

	@Configuration
	static class RestOperationsConfig {

		RestOperations rest = mock(RestOperations.class);

		@Bean
		RestOperations rest() {
			return this.rest;
		}

		@Bean
		NimbusJwtDecoder jwtDecoder() {
			return NimbusJwtDecoder.withJwkSetUri("https://example.org/.well-known/jwks.json")
				.restOperations(this.rest)
				.build();
		}

		@Bean
		OpaqueTokenIntrospector tokenIntrospectionClient() {
			return new SpringOpaqueTokenIntrospector("https://example.org/introspect", this.rest);
		}

	}

	private static class BearerTokenRequestPostProcessor implements RequestPostProcessor {

		private boolean asRequestParameter;

		private String token;

		BearerTokenRequestPostProcessor(String token) {
			this.token = token;
		}

		BearerTokenRequestPostProcessor asParam() {
			this.asRequestParameter = true;
			return this;
		}

		@Override
		public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
			if (this.asRequestParameter) {
				request.setParameter("access_token", this.token);
			}
			else {
				request.addHeader("Authorization", "Bearer " + this.token);
			}
			return request;
		}

	}

}
