Add support for multiple issuers per host using the path component
Closes gh-1342
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,7 +16,10 @@
|
||||
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
@@ -28,6 +31,7 @@ import org.springframework.security.oauth2.server.authorization.context.Authoriz
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.web.util.UrlUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
@@ -42,10 +46,12 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
*/
|
||||
final class AuthorizationServerContextFilter extends OncePerRequestFilter {
|
||||
private final AuthorizationServerSettings authorizationServerSettings;
|
||||
private final IssuerResolver issuerResolver;
|
||||
|
||||
AuthorizationServerContextFilter(AuthorizationServerSettings authorizationServerSettings) {
|
||||
Assert.notNull(authorizationServerSettings, "authorizationServerSettings cannot be null");
|
||||
this.authorizationServerSettings = authorizationServerSettings;
|
||||
this.issuerResolver = new IssuerResolver(authorizationServerSettings);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -53,10 +59,9 @@ final class AuthorizationServerContextFilter extends OncePerRequestFilter {
|
||||
throws ServletException, IOException {
|
||||
|
||||
try {
|
||||
String issuer = this.issuerResolver.resolve(request);
|
||||
AuthorizationServerContext authorizationServerContext =
|
||||
new DefaultAuthorizationServerContext(
|
||||
() -> resolveIssuer(this.authorizationServerSettings, request),
|
||||
this.authorizationServerSettings);
|
||||
new DefaultAuthorizationServerContext(issuer, this.authorizationServerSettings);
|
||||
AuthorizationServerContextHolder.setContext(authorizationServerContext);
|
||||
filterChain.doFilter(request, response);
|
||||
} finally {
|
||||
@@ -64,35 +69,69 @@ final class AuthorizationServerContextFilter extends OncePerRequestFilter {
|
||||
}
|
||||
}
|
||||
|
||||
private static String resolveIssuer(AuthorizationServerSettings authorizationServerSettings, HttpServletRequest request) {
|
||||
return authorizationServerSettings.getIssuer() != null ?
|
||||
authorizationServerSettings.getIssuer() :
|
||||
getContextPath(request);
|
||||
}
|
||||
private static final class IssuerResolver {
|
||||
private final String issuer;
|
||||
private final Set<String> endpointUris;
|
||||
|
||||
private IssuerResolver(AuthorizationServerSettings authorizationServerSettings) {
|
||||
if (authorizationServerSettings.getIssuer() != null) {
|
||||
this.issuer = authorizationServerSettings.getIssuer();
|
||||
this.endpointUris = Collections.emptySet();
|
||||
} else {
|
||||
this.issuer = null;
|
||||
this.endpointUris = new HashSet<>();
|
||||
this.endpointUris.add("/.well-known/oauth-authorization-server");
|
||||
this.endpointUris.add("/.well-known/openid-configuration");
|
||||
for (Map.Entry<String, Object> setting : authorizationServerSettings.getSettings().entrySet()) {
|
||||
if (setting.getKey().endsWith("-endpoint")) {
|
||||
this.endpointUris.add((String) setting.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String resolve(HttpServletRequest request) {
|
||||
if (this.issuer != null) {
|
||||
return this.issuer;
|
||||
}
|
||||
|
||||
// Resolve Issuer Identifier dynamically from request
|
||||
String path = request.getRequestURI();
|
||||
if (!StringUtils.hasText(path)) {
|
||||
path = "";
|
||||
} else {
|
||||
for (String endpointUri : this.endpointUris) {
|
||||
if (path.contains(endpointUri)) {
|
||||
path = path.replace(endpointUri, "");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
return UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
|
||||
.replacePath(path)
|
||||
.replaceQuery(null)
|
||||
.fragment(null)
|
||||
.build()
|
||||
.toUriString();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private static String getContextPath(HttpServletRequest request) {
|
||||
// @formatter:off
|
||||
return UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
|
||||
.replacePath(request.getContextPath())
|
||||
.replaceQuery(null)
|
||||
.fragment(null)
|
||||
.build()
|
||||
.toUriString();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private static final class DefaultAuthorizationServerContext implements AuthorizationServerContext {
|
||||
private final Supplier<String> issuerSupplier;
|
||||
private final String issuer;
|
||||
private final AuthorizationServerSettings authorizationServerSettings;
|
||||
|
||||
private DefaultAuthorizationServerContext(Supplier<String> issuerSupplier, AuthorizationServerSettings authorizationServerSettings) {
|
||||
this.issuerSupplier = issuerSupplier;
|
||||
private DefaultAuthorizationServerContext(String issuer, AuthorizationServerSettings authorizationServerSettings) {
|
||||
this.issuer = issuer;
|
||||
this.authorizationServerSettings = authorizationServerSettings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIssuer() {
|
||||
return this.issuerSupplier.get();
|
||||
return this.issuer;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -51,6 +51,8 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern;
|
||||
|
||||
/**
|
||||
* Configurer for the OAuth 2.0 Authorization Endpoint.
|
||||
*
|
||||
@@ -209,13 +211,10 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
|
||||
@Override
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
String authorizationEndpointUri = withMultipleIssuerPattern(authorizationServerSettings.getAuthorizationEndpoint());
|
||||
this.requestMatcher = new OrRequestMatcher(
|
||||
new AntPathRequestMatcher(
|
||||
authorizationServerSettings.getAuthorizationEndpoint(),
|
||||
HttpMethod.GET.name()),
|
||||
new AntPathRequestMatcher(
|
||||
authorizationServerSettings.getAuthorizationEndpoint(),
|
||||
HttpMethod.POST.name()));
|
||||
new AntPathRequestMatcher(authorizationEndpointUri, HttpMethod.GET.name()),
|
||||
new AntPathRequestMatcher(authorizationEndpointUri, HttpMethod.POST.name()));
|
||||
|
||||
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
|
||||
if (!this.authenticationProviders.isEmpty()) {
|
||||
@@ -234,7 +233,7 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
|
||||
OAuth2AuthorizationEndpointFilter authorizationEndpointFilter =
|
||||
new OAuth2AuthorizationEndpointFilter(
|
||||
authenticationManager,
|
||||
authorizationServerSettings.getAuthorizationEndpoint());
|
||||
withMultipleIssuerPattern(authorizationServerSettings.getAuthorizationEndpoint()));
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.authorizationRequestConverters.isEmpty()) {
|
||||
authenticationConverters.addAll(0, this.authorizationRequestConverters);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -56,6 +56,8 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern;
|
||||
|
||||
/**
|
||||
* An {@link AbstractHttpConfigurer} for OAuth 2.0 Authorization Server support.
|
||||
*
|
||||
@@ -314,7 +316,7 @@ public final class OAuth2AuthorizationServerConfigurer
|
||||
requestMatchers.add(configurer.getRequestMatcher());
|
||||
});
|
||||
requestMatchers.add(new AntPathRequestMatcher(
|
||||
authorizationServerSettings.getJwkSetEndpoint(), HttpMethod.GET.name()));
|
||||
withMultipleIssuerPattern(authorizationServerSettings.getJwkSetEndpoint()), HttpMethod.GET.name()));
|
||||
this.endpointsMatcher = new OrRequestMatcher(requestMatchers);
|
||||
|
||||
ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling = httpSecurity.getConfigurer(ExceptionHandlingConfigurer.class);
|
||||
@@ -342,7 +344,7 @@ public final class OAuth2AuthorizationServerConfigurer
|
||||
JWKSource<com.nimbusds.jose.proc.SecurityContext> jwkSource = OAuth2ConfigurerUtils.getJwkSource(httpSecurity);
|
||||
if (jwkSource != null) {
|
||||
NimbusJwkSetEndpointFilter jwkSetEndpointFilter = new NimbusJwkSetEndpointFilter(
|
||||
jwkSource, authorizationServerSettings.getJwkSetEndpoint());
|
||||
jwkSource, withMultipleIssuerPattern(authorizationServerSettings.getJwkSetEndpoint()));
|
||||
httpSecurity.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -70,7 +70,7 @@ public final class OAuth2AuthorizationServerMetadataEndpointConfigurer extends A
|
||||
@Override
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
this.requestMatcher = new AntPathRequestMatcher(
|
||||
"/.well-known/oauth-authorization-server", HttpMethod.GET.name());
|
||||
"/.well-known/oauth-authorization-server/**", HttpMethod.GET.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -51,6 +51,8 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern;
|
||||
|
||||
/**
|
||||
* Configurer for OAuth 2.0 Client Authentication.
|
||||
*
|
||||
@@ -161,16 +163,16 @@ public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Co
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
this.requestMatcher = new OrRequestMatcher(
|
||||
new AntPathRequestMatcher(
|
||||
authorizationServerSettings.getTokenEndpoint(),
|
||||
withMultipleIssuerPattern(authorizationServerSettings.getTokenEndpoint()),
|
||||
HttpMethod.POST.name()),
|
||||
new AntPathRequestMatcher(
|
||||
authorizationServerSettings.getTokenIntrospectionEndpoint(),
|
||||
withMultipleIssuerPattern(authorizationServerSettings.getTokenIntrospectionEndpoint()),
|
||||
HttpMethod.POST.name()),
|
||||
new AntPathRequestMatcher(
|
||||
authorizationServerSettings.getTokenRevocationEndpoint(),
|
||||
withMultipleIssuerPattern(authorizationServerSettings.getTokenRevocationEndpoint()),
|
||||
HttpMethod.POST.name()),
|
||||
new AntPathRequestMatcher(
|
||||
authorizationServerSettings.getDeviceAuthorizationEndpoint(),
|
||||
withMultipleIssuerPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint()),
|
||||
HttpMethod.POST.name()));
|
||||
|
||||
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -43,6 +43,7 @@ import org.springframework.security.oauth2.server.authorization.token.OAuth2Refr
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -56,6 +57,13 @@ final class OAuth2ConfigurerUtils {
|
||||
private OAuth2ConfigurerUtils() {
|
||||
}
|
||||
|
||||
static String withMultipleIssuerPattern(String endpointUri) {
|
||||
Assert.hasText(endpointUri, "endpointUri cannot be empty");
|
||||
return endpointUri.startsWith("/") ?
|
||||
"/**" + endpointUri :
|
||||
"/**/" + endpointUri;
|
||||
}
|
||||
|
||||
static RegisteredClientRepository getRegisteredClientRepository(HttpSecurity httpSecurity) {
|
||||
RegisteredClientRepository registeredClientRepository = httpSecurity.getSharedObject(RegisteredClientRepository.class);
|
||||
if (registeredClientRepository == null) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -45,6 +45,8 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern;
|
||||
|
||||
/**
|
||||
* Configurer for the OAuth 2.0 Device Authorization Endpoint.
|
||||
*
|
||||
@@ -166,7 +168,7 @@ public final class OAuth2DeviceAuthorizationEndpointConfigurer extends AbstractO
|
||||
AuthorizationServerSettings authorizationServerSettings =
|
||||
OAuth2ConfigurerUtils.getAuthorizationServerSettings(builder);
|
||||
this.requestMatcher = new AntPathRequestMatcher(
|
||||
authorizationServerSettings.getDeviceAuthorizationEndpoint(), HttpMethod.POST.name());
|
||||
withMultipleIssuerPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint()), HttpMethod.POST.name());
|
||||
|
||||
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(builder);
|
||||
if (!this.authenticationProviders.isEmpty()) {
|
||||
@@ -184,7 +186,7 @@ public final class OAuth2DeviceAuthorizationEndpointConfigurer extends AbstractO
|
||||
|
||||
OAuth2DeviceAuthorizationEndpointFilter deviceAuthorizationEndpointFilter =
|
||||
new OAuth2DeviceAuthorizationEndpointFilter(
|
||||
authenticationManager, authorizationServerSettings.getDeviceAuthorizationEndpoint());
|
||||
authenticationManager, withMultipleIssuerPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint()));
|
||||
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.deviceAuthorizationRequestConverters.isEmpty()) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -50,6 +50,8 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern;
|
||||
|
||||
/**
|
||||
* Configurer for the OAuth 2.0 Device Verification Endpoint.
|
||||
*
|
||||
@@ -195,13 +197,10 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA
|
||||
public void init(HttpSecurity builder) {
|
||||
AuthorizationServerSettings authorizationServerSettings =
|
||||
OAuth2ConfigurerUtils.getAuthorizationServerSettings(builder);
|
||||
String deviceVerificationEndpointUri = withMultipleIssuerPattern(authorizationServerSettings.getDeviceVerificationEndpoint());
|
||||
this.requestMatcher = new OrRequestMatcher(
|
||||
new AntPathRequestMatcher(
|
||||
authorizationServerSettings.getDeviceVerificationEndpoint(),
|
||||
HttpMethod.GET.name()),
|
||||
new AntPathRequestMatcher(
|
||||
authorizationServerSettings.getDeviceVerificationEndpoint(),
|
||||
HttpMethod.POST.name()));
|
||||
new AntPathRequestMatcher(deviceVerificationEndpointUri, HttpMethod.GET.name()),
|
||||
new AntPathRequestMatcher(deviceVerificationEndpointUri, HttpMethod.POST.name()));
|
||||
|
||||
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(builder);
|
||||
if (!this.authenticationProviders.isEmpty()) {
|
||||
@@ -221,7 +220,7 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA
|
||||
OAuth2DeviceVerificationEndpointFilter deviceVerificationEndpointFilter =
|
||||
new OAuth2DeviceVerificationEndpointFilter(
|
||||
authenticationManager,
|
||||
authorizationServerSettings.getDeviceVerificationEndpoint());
|
||||
withMultipleIssuerPattern(authorizationServerSettings.getDeviceVerificationEndpoint()));
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.deviceVerificationRequestConverters.isEmpty()) {
|
||||
authenticationConverters.addAll(0, this.deviceVerificationRequestConverters);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -54,6 +54,8 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern;
|
||||
|
||||
/**
|
||||
* Configurer for the OAuth 2.0 Token Endpoint.
|
||||
*
|
||||
@@ -163,7 +165,7 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
this.requestMatcher = new AntPathRequestMatcher(
|
||||
authorizationServerSettings.getTokenEndpoint(), HttpMethod.POST.name());
|
||||
withMultipleIssuerPattern(authorizationServerSettings.getTokenEndpoint()), HttpMethod.POST.name());
|
||||
|
||||
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
|
||||
if (!this.authenticationProviders.isEmpty()) {
|
||||
@@ -182,7 +184,7 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
|
||||
OAuth2TokenEndpointFilter tokenEndpointFilter =
|
||||
new OAuth2TokenEndpointFilter(
|
||||
authenticationManager,
|
||||
authorizationServerSettings.getTokenEndpoint());
|
||||
withMultipleIssuerPattern(authorizationServerSettings.getTokenEndpoint()));
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.accessTokenRequestConverters.isEmpty()) {
|
||||
authenticationConverters.addAll(0, this.accessTokenRequestConverters);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -43,6 +43,8 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern;
|
||||
|
||||
/**
|
||||
* Configurer for the OAuth 2.0 Token Introspection Endpoint.
|
||||
*
|
||||
@@ -152,7 +154,7 @@ public final class OAuth2TokenIntrospectionEndpointConfigurer extends AbstractOA
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
this.requestMatcher = new AntPathRequestMatcher(
|
||||
authorizationServerSettings.getTokenIntrospectionEndpoint(), HttpMethod.POST.name());
|
||||
withMultipleIssuerPattern(authorizationServerSettings.getTokenIntrospectionEndpoint()), HttpMethod.POST.name());
|
||||
|
||||
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
|
||||
if (!this.authenticationProviders.isEmpty()) {
|
||||
@@ -170,7 +172,7 @@ public final class OAuth2TokenIntrospectionEndpointConfigurer extends AbstractOA
|
||||
|
||||
OAuth2TokenIntrospectionEndpointFilter introspectionEndpointFilter =
|
||||
new OAuth2TokenIntrospectionEndpointFilter(
|
||||
authenticationManager, authorizationServerSettings.getTokenIntrospectionEndpoint());
|
||||
authenticationManager, withMultipleIssuerPattern(authorizationServerSettings.getTokenIntrospectionEndpoint()));
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.introspectionRequestConverters.isEmpty()) {
|
||||
authenticationConverters.addAll(0, this.introspectionRequestConverters);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -42,6 +42,8 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern;
|
||||
|
||||
/**
|
||||
* Configurer for the OAuth 2.0 Token Revocation Endpoint.
|
||||
*
|
||||
@@ -151,7 +153,7 @@ public final class OAuth2TokenRevocationEndpointConfigurer extends AbstractOAuth
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
this.requestMatcher = new AntPathRequestMatcher(
|
||||
authorizationServerSettings.getTokenRevocationEndpoint(), HttpMethod.POST.name());
|
||||
withMultipleIssuerPattern(authorizationServerSettings.getTokenRevocationEndpoint()), HttpMethod.POST.name());
|
||||
|
||||
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
|
||||
if (!this.authenticationProviders.isEmpty()) {
|
||||
@@ -169,7 +171,7 @@ public final class OAuth2TokenRevocationEndpointConfigurer extends AbstractOAuth
|
||||
|
||||
OAuth2TokenRevocationEndpointFilter revocationEndpointFilter =
|
||||
new OAuth2TokenRevocationEndpointFilter(
|
||||
authenticationManager, authorizationServerSettings.getTokenRevocationEndpoint());
|
||||
authenticationManager, withMultipleIssuerPattern(authorizationServerSettings.getTokenRevocationEndpoint()));
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.revocationRequestConverters.isEmpty()) {
|
||||
authenticationConverters.addAll(0, this.revocationRequestConverters);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -46,6 +46,8 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern;
|
||||
|
||||
/**
|
||||
* Configurer for OpenID Connect 1.0 Dynamic Client Registration Endpoint.
|
||||
*
|
||||
@@ -160,7 +162,7 @@ public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAut
|
||||
@Override
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
String clientRegistrationEndpointUri = authorizationServerSettings.getOidcClientRegistrationEndpoint();
|
||||
String clientRegistrationEndpointUri = withMultipleIssuerPattern(authorizationServerSettings.getOidcClientRegistrationEndpoint());
|
||||
this.requestMatcher = new OrRequestMatcher(
|
||||
new AntPathRequestMatcher(clientRegistrationEndpointUri, HttpMethod.POST.name()),
|
||||
new AntPathRequestMatcher(clientRegistrationEndpointUri, HttpMethod.GET.name())
|
||||
@@ -183,7 +185,7 @@ public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAut
|
||||
OidcClientRegistrationEndpointFilter oidcClientRegistrationEndpointFilter =
|
||||
new OidcClientRegistrationEndpointFilter(
|
||||
authenticationManager,
|
||||
authorizationServerSettings.getOidcClientRegistrationEndpoint());
|
||||
withMultipleIssuerPattern(authorizationServerSettings.getOidcClientRegistrationEndpoint()));
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.clientRegistrationRequestConverters.isEmpty()) {
|
||||
authenticationConverters.addAll(0, this.clientRegistrationRequestConverters);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -44,6 +44,8 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern;
|
||||
|
||||
/**
|
||||
* Configurer for OpenID Connect 1.0 RP-Initiated Logout Endpoint.
|
||||
*
|
||||
@@ -151,7 +153,7 @@ public final class OidcLogoutEndpointConfigurer extends AbstractOAuth2Configurer
|
||||
@Override
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
String logoutEndpointUri = authorizationServerSettings.getOidcLogoutEndpoint();
|
||||
String logoutEndpointUri = withMultipleIssuerPattern(authorizationServerSettings.getOidcLogoutEndpoint());
|
||||
this.requestMatcher = new OrRequestMatcher(
|
||||
new AntPathRequestMatcher(logoutEndpointUri, HttpMethod.GET.name()),
|
||||
new AntPathRequestMatcher(logoutEndpointUri, HttpMethod.POST.name())
|
||||
@@ -174,7 +176,7 @@ public final class OidcLogoutEndpointConfigurer extends AbstractOAuth2Configurer
|
||||
OidcLogoutEndpointFilter oidcLogoutEndpointFilter =
|
||||
new OidcLogoutEndpointFilter(
|
||||
authenticationManager,
|
||||
authorizationServerSettings.getOidcLogoutEndpoint());
|
||||
withMultipleIssuerPattern(authorizationServerSettings.getOidcLogoutEndpoint()));
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.logoutRequestConverters.isEmpty()) {
|
||||
authenticationConverters.addAll(0, this.logoutRequestConverters);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -70,7 +70,7 @@ public final class OidcProviderConfigurationEndpointConfigurer extends AbstractO
|
||||
@Override
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
this.requestMatcher = new AntPathRequestMatcher(
|
||||
"/.well-known/openid-configuration", HttpMethod.GET.name());
|
||||
"/**/.well-known/openid-configuration", HttpMethod.GET.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -49,6 +49,8 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern;
|
||||
|
||||
/**
|
||||
* Configurer for OpenID Connect 1.0 UserInfo Endpoint.
|
||||
*
|
||||
@@ -185,7 +187,7 @@ public final class OidcUserInfoEndpointConfigurer extends AbstractOAuth2Configur
|
||||
@Override
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
String userInfoEndpointUri = authorizationServerSettings.getOidcUserInfoEndpoint();
|
||||
String userInfoEndpointUri = withMultipleIssuerPattern(authorizationServerSettings.getOidcUserInfoEndpoint());
|
||||
this.requestMatcher = new OrRequestMatcher(
|
||||
new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.GET.name()),
|
||||
new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.POST.name()));
|
||||
@@ -207,7 +209,7 @@ public final class OidcUserInfoEndpointConfigurer extends AbstractOAuth2Configur
|
||||
OidcUserInfoEndpointFilter oidcUserInfoEndpointFilter =
|
||||
new OidcUserInfoEndpointFilter(
|
||||
authenticationManager,
|
||||
authorizationServerSettings.getOidcUserInfoEndpoint());
|
||||
withMultipleIssuerPattern(authorizationServerSettings.getOidcUserInfoEndpoint()));
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.userInfoRequestConverters.isEmpty()) {
|
||||
authenticationConverters.addAll(0, this.userInfoRequestConverters);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -28,9 +28,22 @@ import org.springframework.security.oauth2.server.authorization.settings.Authori
|
||||
public interface AuthorizationServerContext {
|
||||
|
||||
/**
|
||||
* Returns the {@code URL} of the Authorization Server's issuer identifier.
|
||||
* Returns {@link AuthorizationServerSettings#getIssuer()} if available, otherwise,
|
||||
* resolves the issuer identifier from the <i>"current"</i> request.
|
||||
*
|
||||
* @return the {@code URL} of the Authorization Server's issuer identifier
|
||||
* <p>
|
||||
* The issuer identifier may contain a path component to support multiple issuers per host in a multi-tenant hosting configuration.
|
||||
*
|
||||
* <p>
|
||||
* For example:
|
||||
* <ul>
|
||||
* <li>{@code https://example.com/issuer1/oauth2/token} — resolves the issuer to {@code https://example.com/issuer1}</li>
|
||||
* <li>{@code https://example.com/issuer2/oauth2/token} — resolves the issuer to {@code https://example.com/issuer2}</li>
|
||||
* <li>{@code https://example.com/authz/issuer1/oauth2/token} — resolves the issuer to {@code https://example.com/authz/issuer1}</li>
|
||||
* <li>{@code https://example.com/authz/issuer2/oauth2/token} — resolves the issuer to {@code https://example.com/authz/issuer2}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return {@link AuthorizationServerSettings#getIssuer()} if available, otherwise, resolves the issuer identifier from the <i>"current"</i> request
|
||||
*/
|
||||
String getIssuer();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -57,7 +57,7 @@ public final class OidcProviderConfigurationEndpointFilter extends OncePerReques
|
||||
/**
|
||||
* The default endpoint {@code URI} for OpenID Provider Configuration requests.
|
||||
*/
|
||||
private static final String DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI = "/.well-known/openid-configuration";
|
||||
private static final String DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI = "/**/.well-known/openid-configuration";
|
||||
|
||||
private final RequestMatcher requestMatcher = new AntPathRequestMatcher(
|
||||
DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -55,7 +55,7 @@ public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OnceP
|
||||
/**
|
||||
* The default endpoint {@code URI} for OAuth 2.0 Authorization Server Metadata requests.
|
||||
*/
|
||||
private static final String DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI = "/.well-known/oauth-authorization-server";
|
||||
private static final String DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI = "/.well-known/oauth-authorization-server/**";
|
||||
|
||||
private final RequestMatcher requestMatcher = new AntPathRequestMatcher(
|
||||
DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -40,13 +40,13 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2DeviceAuthorizationResponseHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
|
||||
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceAuthorizationRequestAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ErrorAuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.security.web.util.UrlUtils;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -199,9 +199,8 @@ public final class OAuth2DeviceAuthorizationEndpointFilter extends OncePerReques
|
||||
OAuth2UserCode userCode = deviceAuthorizationRequestAuthentication.getUserCode();
|
||||
|
||||
// Generate the fully-qualified verification URI
|
||||
String issuerUri = AuthorizationServerContextHolder.getContext().getIssuer();
|
||||
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(issuerUri)
|
||||
.path(this.verificationUri);
|
||||
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
|
||||
.replacePath(this.verificationUri);
|
||||
String verificationUri = uriComponentsBuilder.build().toUriString();
|
||||
// @formatter:off
|
||||
String verificationUriComplete = uriComponentsBuilder
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2020-2024 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.oauth2.server.authorization.config.annotation.web.configurers;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link AuthorizationServerContextFilter}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
class AuthorizationServerContextFilterTests {
|
||||
private static final String SCHEME = "https";
|
||||
private static final String HOST = "example.com";
|
||||
private static final int PORT = 8443;
|
||||
private static final String DEFAULT_ISSUER = SCHEME + "://" + HOST + ":" + PORT;
|
||||
private AuthorizationServerContextFilter filter;
|
||||
|
||||
@Test
|
||||
public void doFilterWhenDefaultEndpointsThenIssuerResolved() throws Exception {
|
||||
AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build();
|
||||
this.filter = new AuthorizationServerContextFilter(authorizationServerSettings);
|
||||
|
||||
String issuerPath = "/issuer1";
|
||||
String issuerWithPath = DEFAULT_ISSUER.concat(issuerPath);
|
||||
Set<String> endpointUris = getEndpointUris(authorizationServerSettings);
|
||||
|
||||
for (String endpointUri : endpointUris) {
|
||||
assertResolvedIssuer(issuerPath.concat(endpointUri), issuerWithPath);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenCustomEndpointsThenIssuerResolved() throws Exception {
|
||||
AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder()
|
||||
.authorizationEndpoint("/oauth2/v1/authorize")
|
||||
.deviceAuthorizationEndpoint("/oauth2/v1/device_authorization")
|
||||
.deviceVerificationEndpoint("/oauth2/v1/device_verification")
|
||||
.tokenEndpoint("/oauth2/v1/token")
|
||||
.jwkSetEndpoint("/oauth2/v1/jwks")
|
||||
.tokenRevocationEndpoint("/oauth2/v1/revoke")
|
||||
.tokenIntrospectionEndpoint("/oauth2/v1/introspect")
|
||||
.oidcClientRegistrationEndpoint("/connect/v1/register")
|
||||
.oidcUserInfoEndpoint("/v1/userinfo")
|
||||
.oidcLogoutEndpoint("/connect/v1/logout")
|
||||
.build();
|
||||
this.filter = new AuthorizationServerContextFilter(authorizationServerSettings);
|
||||
|
||||
String issuerPath = "/issuer2";
|
||||
String issuerWithPath = DEFAULT_ISSUER.concat(issuerPath);
|
||||
Set<String> endpointUris = getEndpointUris(authorizationServerSettings);
|
||||
|
||||
for (String endpointUri : endpointUris) {
|
||||
assertResolvedIssuer(issuerPath.concat(endpointUri), issuerWithPath);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenIssuerHasMultiplePathsThenIssuerResolved() throws Exception {
|
||||
AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build();
|
||||
this.filter = new AuthorizationServerContextFilter(authorizationServerSettings);
|
||||
|
||||
String issuerPath = "/path1/path2/issuer3";
|
||||
String issuerWithPath = DEFAULT_ISSUER.concat(issuerPath);
|
||||
Set<String> endpointUris = getEndpointUris(authorizationServerSettings);
|
||||
|
||||
for (String endpointUri : endpointUris) {
|
||||
assertResolvedIssuer(issuerPath.concat(endpointUri), issuerWithPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertResolvedIssuer(String requestUri, String expectedIssuer) throws Exception {
|
||||
MockHttpServletRequest request = createRequest(requestUri);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
AtomicReference<String> resolvedIssuer = new AtomicReference<>();
|
||||
FilterChain filterChain = (req, resp) ->
|
||||
resolvedIssuer.set(AuthorizationServerContextHolder.getContext().getIssuer());
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
assertThat(resolvedIssuer.get()).isEqualTo(expectedIssuer);
|
||||
}
|
||||
|
||||
private static Set<String> getEndpointUris(AuthorizationServerSettings authorizationServerSettings) {
|
||||
Set<String> endpointUris = new HashSet<>();
|
||||
endpointUris.add("/.well-known/oauth-authorization-server");
|
||||
endpointUris.add("/.well-known/openid-configuration");
|
||||
for (Map.Entry<String, Object> setting : authorizationServerSettings.getSettings().entrySet()) {
|
||||
if (setting.getKey().endsWith("-endpoint")) {
|
||||
endpointUris.add((String) setting.getValue());
|
||||
}
|
||||
}
|
||||
return endpointUris;
|
||||
}
|
||||
|
||||
private static MockHttpServletRequest createRequest(String requestUri) {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setRequestURI(requestUri);
|
||||
request.setScheme(SCHEME);
|
||||
request.setServerName(HOST);
|
||||
request.setServerPort(PORT);
|
||||
return request;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -112,6 +112,14 @@ public class JwkSetTests {
|
||||
assertJwkSetRequestThenReturnKeys(authorizationServerSettings.getJwkSetEndpoint());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenJwkSetRequestIncludesIssuerPathThenReturnKeys() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigurationCustomEndpoints.class).autowire();
|
||||
|
||||
String issuer = "https://example.com:8443/issuer1";
|
||||
assertJwkSetRequestThenReturnKeys(issuer.concat(authorizationServerSettings.getJwkSetEndpoint()));
|
||||
}
|
||||
|
||||
private void assertJwkSetRequestThenReturnKeys(String jwkSetEndpointUri) throws Exception {
|
||||
this.mvc.perform(get(jwkSetEndpointUri))
|
||||
.andExpect(status().isOk())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -826,6 +826,46 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
assertThat(securityContext.getAuthentication()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenAuthorizationAndTokenRequestIncludesIssuerPathThenIssuerResolvedWithPath() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigurationWithTokenGenerator.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build();
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
String issuer = "https://example.com:8443/issuer1";
|
||||
|
||||
MvcResult mvcResult = this.mvc.perform(get(issuer.concat(DEFAULT_AUTHORIZATION_ENDPOINT_URI))
|
||||
.queryParams(getAuthorizationRequestParameters(registeredClient))
|
||||
.queryParam(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE)
|
||||
.queryParam(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256")
|
||||
.with(user("user")))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
|
||||
String authorizationCode = extractParameterFromRedirectUri(mvcResult.getResponse().getRedirectedUrl(), "code");
|
||||
OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE);
|
||||
|
||||
this.mvc.perform(post(issuer.concat(DEFAULT_TOKEN_ENDPOINT_URI))
|
||||
.params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization))
|
||||
.param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
|
||||
.param(PkceParameterNames.CODE_VERIFIER, S256_CODE_VERIFIER))
|
||||
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
|
||||
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.token_type").isNotEmpty())
|
||||
.andExpect(jsonPath("$.expires_in").isNotEmpty())
|
||||
.andExpect(jsonPath("$.refresh_token").doesNotExist())
|
||||
.andExpect(jsonPath("$.scope").isNotEmpty())
|
||||
.andReturn();
|
||||
|
||||
ArgumentCaptor<OAuth2TokenContext> tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class);
|
||||
verify(tokenGenerator).generate(tokenContextCaptor.capture());
|
||||
OAuth2TokenContext tokenContext = tokenContextCaptor.getValue();
|
||||
assertThat(tokenContext.getAuthorizationServerContext().getIssuer()).isEqualTo(issuer);
|
||||
}
|
||||
|
||||
private static MultiValueMap<String, String> getAuthorizationRequestParameters(RegisteredClient registeredClient) {
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2AuthorizationResponseType.CODE.getValue());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -65,7 +65,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class OAuth2AuthorizationServerMetadataTests {
|
||||
private static final String DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI = "/.well-known/oauth-authorization-server";
|
||||
private static final String ISSUER_URL = "https://example.com";
|
||||
private static final String ISSUER = "https://example.com";
|
||||
private static EmbeddedDatabase db;
|
||||
private static JWKSource<SecurityContext> jwkSource;
|
||||
|
||||
@@ -105,19 +105,37 @@ public class OAuth2AuthorizationServerMetadataTests {
|
||||
public void requestWhenAuthorizationServerMetadataRequestAndIssuerSetThenUsed() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
this.mvc.perform(get(ISSUER_URL.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI)))
|
||||
this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath("issuer").value(ISSUER_URL))
|
||||
.andExpect(jsonPath("issuer").value(ISSUER))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenAuthorizationServerMetadataRequestAndIssuerNotSetThenResolveFromRequest() throws Exception {
|
||||
public void requestWhenAuthorizationServerMetadataRequestIncludesIssuerPathThenMetadataResponseHasIssuerPath() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigurationWithIssuerNotSet.class).autowire();
|
||||
|
||||
this.mvc.perform(get("http://localhost".concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI)))
|
||||
String host = "https://example.com:8443";
|
||||
|
||||
String issuerPath = "/issuer1";
|
||||
String issuer = host.concat(issuerPath);
|
||||
this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI).concat(issuerPath)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath("issuer").value("http://localhost"))
|
||||
.andExpect(jsonPath("issuer").value(issuer))
|
||||
.andReturn();
|
||||
|
||||
issuerPath = "/path1/issuer2";
|
||||
issuer = host.concat(issuerPath);
|
||||
this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI).concat(issuerPath)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath("issuer").value(issuer))
|
||||
.andReturn();
|
||||
|
||||
issuerPath = "/path1/path2/issuer3";
|
||||
issuer = host.concat(issuerPath);
|
||||
this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI).concat(issuerPath)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath("issuer").value(issuer))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@@ -126,7 +144,7 @@ public class OAuth2AuthorizationServerMetadataTests {
|
||||
public void requestWhenAuthorizationServerMetadataRequestAndMetadataCustomizerSetThenReturnCustomMetadataResponse() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigurationWithMetadataCustomizer.class).autowire();
|
||||
|
||||
this.mvc.perform(get(ISSUER_URL.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI)))
|
||||
this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED,
|
||||
hasItems("scope1", "scope2")));
|
||||
@@ -156,7 +174,7 @@ public class OAuth2AuthorizationServerMetadataTests {
|
||||
|
||||
@Bean
|
||||
AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder().issuer(ISSUER_URL).build();
|
||||
return AuthorizationServerSettings.builder().issuer(ISSUER).build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -357,6 +357,30 @@ public class OAuth2ClientCredentialsGrantTests {
|
||||
verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(clientPrincipal));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenTokenRequestIncludesIssuerPathThenIssuerResolvedWithPath() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
String issuer = "https://example.com:8443/issuer1";
|
||||
|
||||
this.mvc.perform(post(issuer.concat(DEFAULT_TOKEN_ENDPOINT_URI))
|
||||
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.param(OAuth2ParameterNames.SCOPE, "scope1 scope2")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
|
||||
registeredClient.getClientId(), registeredClient.getClientSecret())))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.scope").value("scope1 scope2"));
|
||||
|
||||
ArgumentCaptor<JwtEncodingContext> jwtEncodingContextCaptor = ArgumentCaptor.forClass(JwtEncodingContext.class);
|
||||
verify(jwtCustomizer).customize(jwtEncodingContextCaptor.capture());
|
||||
JwtEncodingContext jwtEncodingContext = jwtEncodingContextCaptor.getValue();
|
||||
assertThat(jwtEncodingContext.getAuthorizationServerContext().getIssuer()).isEqualTo(issuer);
|
||||
}
|
||||
|
||||
private static String encodeBasicAuth(String clientId, String secret) throws Exception {
|
||||
clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name());
|
||||
secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -218,8 +218,10 @@ public class OAuth2DeviceCodeGrantTests {
|
||||
parameters.set(OAuth2ParameterNames.SCOPE,
|
||||
StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " "));
|
||||
|
||||
String issuer = "https://example.com:8443/issuer1";
|
||||
|
||||
// @formatter:off
|
||||
MvcResult mvcResult = this.mvc.perform(post(DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI)
|
||||
MvcResult mvcResult = this.mvc.perform(post(issuer.concat(DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI))
|
||||
.params(parameters)
|
||||
.headers(withClientAuth(registeredClient)))
|
||||
.andExpect(status().isOk())
|
||||
@@ -240,9 +242,9 @@ public class OAuth2DeviceCodeGrantTests {
|
||||
String userCode = deviceAuthorizationResponse.getUserCode().getTokenValue();
|
||||
assertThat(userCode).matches("[A-Z]{4}-[A-Z]{4}");
|
||||
assertThat(deviceAuthorizationResponse.getVerificationUri())
|
||||
.isEqualTo("http://localhost/oauth2/device_verification");
|
||||
.isEqualTo("https://example.com:8443/oauth2/device_verification");
|
||||
assertThat(deviceAuthorizationResponse.getVerificationUriComplete())
|
||||
.isEqualTo("http://localhost/oauth2/device_verification?user_code=" + userCode);
|
||||
.isEqualTo("https://example.com:8443/oauth2/device_verification?user_code=" + userCode);
|
||||
|
||||
String deviceCode = deviceAuthorizationResponse.getDeviceCode().getTokenValue();
|
||||
OAuth2Authorization authorization = this.authorizationService.findByToken(deviceCode, DEVICE_CODE_TOKEN_TYPE);
|
||||
@@ -311,8 +313,10 @@ public class OAuth2DeviceCodeGrantTests {
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.USER_CODE, USER_CODE);
|
||||
|
||||
String issuer = "https://example.com:8443/issuer1";
|
||||
|
||||
// @formatter:off
|
||||
MvcResult mvcResult = this.mvc.perform(get(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI)
|
||||
MvcResult mvcResult = this.mvc.perform(get(issuer.concat(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI))
|
||||
.queryParams(parameters)
|
||||
.with(user("user")))
|
||||
.andExpect(status().isOk())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -30,6 +30,7 @@ import java.util.function.Consumer;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -107,6 +108,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
@@ -167,6 +169,18 @@ public class OAuth2TokenIntrospectionTests {
|
||||
.build();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
reset(authenticationConverter);
|
||||
reset(authenticationConvertersConsumer);
|
||||
reset(authenticationProvider);
|
||||
reset(authenticationProvidersConsumer);
|
||||
reset(authenticationSuccessHandler);
|
||||
reset(authenticationFailureHandler);
|
||||
reset(accessTokenCustomizer);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
jdbcOperations.update("truncate table oauth2_authorization");
|
||||
@@ -395,6 +409,41 @@ public class OAuth2TokenIntrospectionTests {
|
||||
verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(tokenIntrospectionAuthentication));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenIntrospectionRequestIncludesIssuerPathThenActive() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigurationCustomTokenIntrospectionEndpoint.class).autowire();
|
||||
|
||||
RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2().build();
|
||||
this.registeredClientRepository.save(introspectRegisteredClient);
|
||||
|
||||
RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient().build();
|
||||
this.registeredClientRepository.save(authorizedRegisteredClient);
|
||||
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(authorizedRegisteredClient).build();
|
||||
this.authorizationService.save(authorization);
|
||||
|
||||
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
|
||||
|
||||
Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
introspectRegisteredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, introspectRegisteredClient.getClientSecret());
|
||||
OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication =
|
||||
new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
accessToken.getTokenValue(), clientPrincipal, null, null);
|
||||
|
||||
when(authenticationConverter.convert(any())).thenReturn(tokenIntrospectionAuthentication);
|
||||
when(authenticationProvider.supports(eq(OAuth2TokenIntrospectionAuthenticationToken.class))).thenReturn(true);
|
||||
when(authenticationProvider.authenticate(any())).thenReturn(tokenIntrospectionAuthentication);
|
||||
|
||||
String issuer = "https://example.com:8443/issuer1";
|
||||
|
||||
// @formatter:off
|
||||
this.mvc.perform(post(issuer.concat(authorizationServerSettings.getTokenIntrospectionEndpoint()))
|
||||
.params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN))
|
||||
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient)))
|
||||
.andExpect(status().isOk());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private static MultiValueMap<String, String> getTokenIntrospectionRequestParameters(OAuth2Token token,
|
||||
OAuth2TokenType tokenType) {
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -201,6 +201,35 @@ public class OAuth2TokenRevocationTests {
|
||||
assertThat(refreshToken.isInvalidated()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenRevokeAccessTokenAndRequestIncludesIssuerPathThenRevoked() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||
OAuth2AccessToken token = authorization.getAccessToken().getToken();
|
||||
OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN;
|
||||
this.authorizationService.save(authorization);
|
||||
|
||||
String issuer = "https://example.com:8443/issuer1";
|
||||
|
||||
// @formatter:off
|
||||
this.mvc.perform(post(issuer.concat(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI))
|
||||
.params(getTokenRevocationRequestParameters(token, tokenType))
|
||||
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
|
||||
registeredClient.getClientId(), registeredClient.getClientSecret())))
|
||||
.andExpect(status().isOk());
|
||||
// @formatter:on
|
||||
|
||||
OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId());
|
||||
OAuth2Authorization.Token<OAuth2AccessToken> accessToken = updatedAuthorization.getAccessToken();
|
||||
assertThat(accessToken.isInvalidated()).isTrue();
|
||||
OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken = updatedAuthorization.getRefreshToken();
|
||||
assertThat(refreshToken.isInvalidated()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenTokenRevocationEndpointCustomizedThenUsed() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigurationCustomTokenRevocationEndpoint.class).autowire();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -137,6 +137,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class OidcClientRegistrationTests {
|
||||
private static final String ISSUER = "https://example.com:8443/issuer1";
|
||||
private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token";
|
||||
private static final String DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI = "/connect/register";
|
||||
private static final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
|
||||
@@ -374,7 +375,7 @@ public class OidcClientRegistrationTests {
|
||||
|
||||
when(authenticationProvider.authenticate(any())).thenThrow(new OAuth2AuthenticationException("error"));
|
||||
|
||||
this.mvc.perform(get(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI)
|
||||
this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI))
|
||||
.param(OAuth2ParameterNames.CLIENT_ID, "invalid").with(jwt()));
|
||||
|
||||
verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), any());
|
||||
@@ -399,7 +400,7 @@ public class OidcClientRegistrationTests {
|
||||
|
||||
OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration);
|
||||
|
||||
this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
|
||||
this.mvc.perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI))
|
||||
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.param(OAuth2ParameterNames.SCOPE, "scope1")
|
||||
.with(httpBasic(clientRegistrationResponse.getClientId(), clientRegistrationResponse.getClientSecret())))
|
||||
@@ -436,7 +437,7 @@ public class OidcClientRegistrationTests {
|
||||
JwtClaimsSet jwtClaimsSet = JwtClaimsSet.builder()
|
||||
.issuer(clientRegistrationResponse.getClientId())
|
||||
.subject(clientRegistrationResponse.getClientId())
|
||||
.audience(Collections.singletonList(asUrl(this.authorizationServerSettings.getIssuer(), this.authorizationServerSettings.getTokenEndpoint())))
|
||||
.audience(Collections.singletonList(asUrl(ISSUER, this.authorizationServerSettings.getTokenEndpoint())))
|
||||
.issuedAt(issuedAt)
|
||||
.expiresAt(expiresAt)
|
||||
.build();
|
||||
@@ -447,7 +448,7 @@ public class OidcClientRegistrationTests {
|
||||
|
||||
Jwt jwtAssertion = jwtClientAssertionEncoder.encode(JwtEncoderParameters.from(jwsHeader, jwtClaimsSet));
|
||||
|
||||
this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
|
||||
this.mvc.perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI))
|
||||
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.param(OAuth2ParameterNames.SCOPE, "scope1")
|
||||
.param(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
|
||||
@@ -520,7 +521,7 @@ public class OidcClientRegistrationTests {
|
||||
// @formatter:on
|
||||
Jwt jwtAssertion = jwtClientAssertionEncoder.encode(JwtEncoderParameters.from(jwsHeader, jwtClaimsSet));
|
||||
|
||||
MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
|
||||
MvcResult mvcResult = this.mvc.perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI))
|
||||
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.param(OAuth2ParameterNames.SCOPE, clientRegistrationScope)
|
||||
.param(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
|
||||
@@ -539,7 +540,7 @@ public class OidcClientRegistrationTests {
|
||||
httpHeaders.setBearerAuth(accessToken.getTokenValue());
|
||||
|
||||
// Register the client
|
||||
mvcResult = this.mvc.perform(post(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI)
|
||||
mvcResult = this.mvc.perform(post(ISSUER.concat(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI))
|
||||
.headers(httpHeaders)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(getClientRegistrationRequestContent(clientRegistration)))
|
||||
@@ -557,7 +558,7 @@ public class OidcClientRegistrationTests {
|
||||
return JwtClaimsSet.builder()
|
||||
.issuer(registeredClient.getClientId())
|
||||
.subject(registeredClient.getClientId())
|
||||
.audience(Collections.singletonList(asUrl(this.authorizationServerSettings.getIssuer(), this.authorizationServerSettings.getTokenEndpoint())))
|
||||
.audience(Collections.singletonList(asUrl(ISSUER, this.authorizationServerSettings.getTokenEndpoint())))
|
||||
.issuedAt(issuedAt)
|
||||
.expiresAt(expiresAt);
|
||||
}
|
||||
@@ -734,7 +735,6 @@ public class OidcClientRegistrationTests {
|
||||
@Bean
|
||||
AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder()
|
||||
.issuer("https://auth-server:9000")
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -63,7 +63,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class OidcProviderConfigurationTests {
|
||||
private static final String DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI = "/.well-known/openid-configuration";
|
||||
private static final String ISSUER_URL = "https://example.com";
|
||||
private static final String ISSUER = "https://example.com";
|
||||
|
||||
public final SpringTestContext spring = new SpringTestContext();
|
||||
|
||||
@@ -77,9 +77,29 @@ public class OidcProviderConfigurationTests {
|
||||
public void requestWhenConfigurationRequestAndIssuerSetThenReturnDefaultConfigurationResponse() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
this.mvc.perform(get(ISSUER_URL.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)))
|
||||
this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers());
|
||||
.andExpectAll(defaultConfigurationMatchers(ISSUER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenConfigurationRequestIncludesIssuerPathThenConfigurationResponseHasIssuerPath() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigurationWithIssuerNotSet.class).autowire();
|
||||
|
||||
String issuer = "https://example.com:8443/issuer1";
|
||||
this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers(issuer));
|
||||
|
||||
issuer = "https://example.com:8443/path1/issuer2";
|
||||
this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers(issuer));
|
||||
|
||||
issuer = "https://example.com:8443/path1/path2/issuer3";
|
||||
this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers(issuer));
|
||||
}
|
||||
|
||||
// gh-632
|
||||
@@ -87,10 +107,10 @@ public class OidcProviderConfigurationTests {
|
||||
public void requestWhenConfigurationRequestAndUserAuthenticatedThenReturnConfigurationResponse() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
this.mvc.perform(get(ISSUER_URL.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))
|
||||
this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))
|
||||
.with(user("user")))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers());
|
||||
.andExpectAll(defaultConfigurationMatchers(ISSUER));
|
||||
}
|
||||
|
||||
// gh-616
|
||||
@@ -98,7 +118,7 @@ public class OidcProviderConfigurationTests {
|
||||
public void requestWhenConfigurationRequestAndConfigurationCustomizerSetThenReturnCustomConfigurationResponse() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigurationWithProviderConfigurationCustomizer.class).autowire();
|
||||
|
||||
this.mvc.perform(get(ISSUER_URL.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)))
|
||||
this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED,
|
||||
hasItems(OidcScopes.OPENID, OidcScopes.PROFILE, OidcScopes.EMAIL)));
|
||||
@@ -108,35 +128,35 @@ public class OidcProviderConfigurationTests {
|
||||
public void requestWhenConfigurationRequestAndClientRegistrationEnabledThenConfigurationResponseIncludesRegistrationEndpoint() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigurationWithClientRegistrationEnabled.class).autowire();
|
||||
|
||||
this.mvc.perform(get(ISSUER_URL.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)))
|
||||
this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers())
|
||||
.andExpect(jsonPath("$.registration_endpoint").value(ISSUER_URL.concat(this.authorizationServerSettings.getOidcClientRegistrationEndpoint())));
|
||||
.andExpectAll(defaultConfigurationMatchers(ISSUER))
|
||||
.andExpect(jsonPath("$.registration_endpoint").value(ISSUER.concat(this.authorizationServerSettings.getOidcClientRegistrationEndpoint())));
|
||||
}
|
||||
|
||||
private ResultMatcher[] defaultConfigurationMatchers() {
|
||||
private ResultMatcher[] defaultConfigurationMatchers(String issuer) {
|
||||
// @formatter:off
|
||||
return new ResultMatcher[] {
|
||||
jsonPath("issuer").value(ISSUER_URL),
|
||||
jsonPath("authorization_endpoint").value(ISSUER_URL.concat(this.authorizationServerSettings.getAuthorizationEndpoint())),
|
||||
jsonPath("token_endpoint").value(ISSUER_URL.concat(this.authorizationServerSettings.getTokenEndpoint())),
|
||||
jsonPath("issuer").value(issuer),
|
||||
jsonPath("authorization_endpoint").value(issuer.concat(this.authorizationServerSettings.getAuthorizationEndpoint())),
|
||||
jsonPath("token_endpoint").value(issuer.concat(this.authorizationServerSettings.getTokenEndpoint())),
|
||||
jsonPath("$.token_endpoint_auth_methods_supported[0]").value(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()),
|
||||
jsonPath("$.token_endpoint_auth_methods_supported[1]").value(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue()),
|
||||
jsonPath("$.token_endpoint_auth_methods_supported[2]").value(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()),
|
||||
jsonPath("$.token_endpoint_auth_methods_supported[3]").value(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()),
|
||||
jsonPath("jwks_uri").value(ISSUER_URL.concat(this.authorizationServerSettings.getJwkSetEndpoint())),
|
||||
jsonPath("userinfo_endpoint").value(ISSUER_URL.concat(this.authorizationServerSettings.getOidcUserInfoEndpoint())),
|
||||
jsonPath("end_session_endpoint").value(ISSUER_URL.concat(this.authorizationServerSettings.getOidcLogoutEndpoint())),
|
||||
jsonPath("jwks_uri").value(issuer.concat(this.authorizationServerSettings.getJwkSetEndpoint())),
|
||||
jsonPath("userinfo_endpoint").value(issuer.concat(this.authorizationServerSettings.getOidcUserInfoEndpoint())),
|
||||
jsonPath("end_session_endpoint").value(issuer.concat(this.authorizationServerSettings.getOidcLogoutEndpoint())),
|
||||
jsonPath("response_types_supported").value(OAuth2AuthorizationResponseType.CODE.getValue()),
|
||||
jsonPath("$.grant_types_supported[0]").value(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()),
|
||||
jsonPath("$.grant_types_supported[1]").value(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()),
|
||||
jsonPath("$.grant_types_supported[2]").value(AuthorizationGrantType.REFRESH_TOKEN.getValue()),
|
||||
jsonPath("revocation_endpoint").value(ISSUER_URL.concat(this.authorizationServerSettings.getTokenRevocationEndpoint())),
|
||||
jsonPath("revocation_endpoint").value(issuer.concat(this.authorizationServerSettings.getTokenRevocationEndpoint())),
|
||||
jsonPath("$.revocation_endpoint_auth_methods_supported[0]").value(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()),
|
||||
jsonPath("$.revocation_endpoint_auth_methods_supported[1]").value(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue()),
|
||||
jsonPath("$.revocation_endpoint_auth_methods_supported[2]").value(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()),
|
||||
jsonPath("$.revocation_endpoint_auth_methods_supported[3]").value(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()),
|
||||
jsonPath("introspection_endpoint").value(ISSUER_URL.concat(this.authorizationServerSettings.getTokenIntrospectionEndpoint())),
|
||||
jsonPath("introspection_endpoint").value(issuer.concat(this.authorizationServerSettings.getTokenIntrospectionEndpoint())),
|
||||
jsonPath("$.introspection_endpoint_auth_methods_supported[0]").value(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()),
|
||||
jsonPath("$.introspection_endpoint_auth_methods_supported[1]").value(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue()),
|
||||
jsonPath("$.introspection_endpoint_auth_methods_supported[2]").value(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()),
|
||||
@@ -218,7 +238,18 @@ public class OidcProviderConfigurationTests {
|
||||
@Bean
|
||||
AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder()
|
||||
.issuer(ISSUER_URL)
|
||||
.issuer(ISSUER)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class AuthorizationServerConfigurationWithIssuerNotSet extends AuthorizationServerConfiguration {
|
||||
|
||||
@Bean
|
||||
AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -306,7 +337,7 @@ public class OidcProviderConfigurationTests {
|
||||
|
||||
@Bean
|
||||
AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder().issuer(ISSUER_URL + "?param=value").build();
|
||||
return AuthorizationServerSettings.builder().issuer(ISSUER + "?param=value").build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,7 +346,7 @@ public class OidcProviderConfigurationTests {
|
||||
|
||||
@Bean
|
||||
AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder().issuer(ISSUER_URL + "#fragment").build();
|
||||
return AuthorizationServerSettings.builder().issuer(ISSUER + "#fragment").build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,7 +355,7 @@ public class OidcProviderConfigurationTests {
|
||||
|
||||
@Bean
|
||||
AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder().issuer(ISSUER_URL + "?param=value#fragment").build();
|
||||
return AuthorizationServerSettings.builder().issuer(ISSUER + "?param=value#fragment").build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +364,7 @@ public class OidcProviderConfigurationTests {
|
||||
|
||||
@Bean
|
||||
AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder().issuer(ISSUER_URL + "?").build();
|
||||
return AuthorizationServerSettings.builder().issuer(ISSUER + "?").build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +373,7 @@ public class OidcProviderConfigurationTests {
|
||||
|
||||
@Bean
|
||||
AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder().issuer(ISSUER_URL + "#").build();
|
||||
return AuthorizationServerSettings.builder().issuer(ISSUER + "#").build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -303,9 +303,11 @@ public class OidcTests {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build();
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
String issuer = "https://example.com:8443/issuer1";
|
||||
|
||||
// Login
|
||||
MultiValueMap<String, String> authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient);
|
||||
MvcResult mvcResult = this.mvc.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
|
||||
MvcResult mvcResult = this.mvc.perform(get(issuer.concat(DEFAULT_AUTHORIZATION_ENDPOINT_URI))
|
||||
.queryParams(authorizationRequestParameters)
|
||||
.with(user("user")))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
@@ -319,7 +321,7 @@ public class OidcTests {
|
||||
OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE);
|
||||
|
||||
// Get ID Token
|
||||
mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
|
||||
mvcResult = this.mvc.perform(post(issuer.concat(DEFAULT_TOKEN_ENDPOINT_URI))
|
||||
.params(getTokenRequestParameters(registeredClient, authorization))
|
||||
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
|
||||
registeredClient.getClientId(), registeredClient.getClientSecret())))
|
||||
@@ -334,7 +336,7 @@ public class OidcTests {
|
||||
String idToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN);
|
||||
|
||||
// Logout
|
||||
mvcResult = this.mvc.perform(post(DEFAULT_OIDC_LOGOUT_ENDPOINT_URI)
|
||||
mvcResult = this.mvc.perform(post(issuer.concat(DEFAULT_OIDC_LOGOUT_ENDPOINT_URI))
|
||||
.param("id_token_hint", idToken)
|
||||
.session(session))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,11 +24,12 @@ import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -193,6 +194,24 @@ public class OidcUserInfoTests {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenUserInfoRequestIncludesIssuerPathThenUserInfoResponse() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
OAuth2Authorization authorization = createAuthorization();
|
||||
this.authorizationService.save(authorization);
|
||||
|
||||
String issuer = "https://example.com:8443/issuer1";
|
||||
|
||||
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
|
||||
// @formatter:off
|
||||
this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI))
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue()))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(userInfoResponse());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenUserInfoEndpointCustomizedThenUsed() throws Exception {
|
||||
this.spring.register(CustomUserInfoConfiguration.class).autowire();
|
||||
@@ -512,7 +531,6 @@ public class OidcUserInfoTests {
|
||||
@Bean
|
||||
AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder()
|
||||
.issuer("https://auth-server:9000")
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,6 +21,7 @@ import java.time.temporal.ChronoUnit;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -404,6 +405,9 @@ public class OAuth2DeviceAuthorizationEndpointFilterTests {
|
||||
request.setRequestURI(AUTHORIZATION_URI);
|
||||
request.setServletPath(AUTHORIZATION_URI);
|
||||
request.setRemoteAddr(REMOTE_ADDRESS);
|
||||
request.setScheme("https");
|
||||
request.setServerName("provider.com");
|
||||
request.setServerPort(-1);
|
||||
return request;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -43,8 +43,6 @@ import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
@@ -94,7 +92,7 @@ public class AuthorizationServerConfig {
|
||||
*/
|
||||
DeviceClientAuthenticationConverter deviceClientAuthenticationConverter =
|
||||
new DeviceClientAuthenticationConverter(
|
||||
authorizationServerSettings.getDeviceAuthorizationEndpoint());
|
||||
"/**" + authorizationServerSettings.getDeviceAuthorizationEndpoint());
|
||||
DeviceClientAuthenticationProvider deviceClientAuthenticationProvider =
|
||||
new DeviceClientAuthenticationProvider(registeredClientRepository);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user