Polish gh-17202
This commit is contained in:
-169
@@ -1,169 +0,0 @@
|
||||
/*
|
||||
* 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 jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationManagerResolver;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.DPoPAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.DPoPAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.resource.web.DPoPAuthenticationEntryPoint;
|
||||
import org.springframework.security.oauth2.server.resource.web.DPoPRequestMatcher;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
/**
|
||||
* An {@link AbstractHttpConfigurer} for OAuth 2.0 Demonstrating Proof of Possession
|
||||
* (DPoP) support.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Max Batischev
|
||||
* @since 6.5
|
||||
* @see DPoPAuthenticationProvider
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc9449">RFC 9449
|
||||
* OAuth 2.0 Demonstrating Proof of Possession (DPoP)</a>
|
||||
*/
|
||||
public final class DPoPAuthenticationConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
extends AbstractHttpConfigurer<DPoPAuthenticationConfigurer<B>, B> {
|
||||
|
||||
private RequestMatcher requestMatcher;
|
||||
|
||||
private AuthenticationConverter authenticationConverter;
|
||||
|
||||
private AuthenticationSuccessHandler authenticationSuccessHandler;
|
||||
|
||||
private AuthenticationFailureHandler authenticationFailureHandler;
|
||||
|
||||
@Override
|
||||
public void configure(B http) {
|
||||
AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
|
||||
http.authenticationProvider(new DPoPAuthenticationProvider(getTokenAuthenticationManager(http)));
|
||||
AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManager,
|
||||
getAuthenticationConverter());
|
||||
authenticationFilter.setRequestMatcher(getRequestMatcher());
|
||||
authenticationFilter.setSuccessHandler(getAuthenticationSuccessHandler());
|
||||
authenticationFilter.setFailureHandler(getAuthenticationFailureHandler());
|
||||
authenticationFilter.setSecurityContextRepository(new RequestAttributeSecurityContextRepository());
|
||||
authenticationFilter = postProcess(authenticationFilter);
|
||||
http.addFilter(authenticationFilter);
|
||||
}
|
||||
|
||||
private AuthenticationManager getTokenAuthenticationManager(B http) {
|
||||
OAuth2ResourceServerConfigurer<B> resourceServerConfigurer = http
|
||||
.getConfigurer(OAuth2ResourceServerConfigurer.class);
|
||||
final AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver = resourceServerConfigurer
|
||||
.getAuthenticationManagerResolver();
|
||||
if (authenticationManagerResolver == null) {
|
||||
return resourceServerConfigurer.getAuthenticationManager(http);
|
||||
}
|
||||
return (authentication) -> {
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
|
||||
AuthenticationManager authenticationManager = authenticationManagerResolver
|
||||
.resolve(servletRequestAttributes.getRequest());
|
||||
return authenticationManager.authenticate(authentication);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link RequestMatcher} to use.
|
||||
* @param requestMatcher
|
||||
* @since 7.0
|
||||
*/
|
||||
public DPoPAuthenticationConfigurer<B> requestMatcher(RequestMatcher requestMatcher) {
|
||||
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
|
||||
this.requestMatcher = requestMatcher;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationConverter} to use.
|
||||
* @param authenticationConverter
|
||||
* @since 7.0
|
||||
*/
|
||||
public DPoPAuthenticationConfigurer<B> authenticationConverter(AuthenticationConverter authenticationConverter) {
|
||||
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||
this.authenticationConverter = authenticationConverter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationFailureHandler} to use.
|
||||
* @param failureHandler
|
||||
* @since 7.0
|
||||
*/
|
||||
public DPoPAuthenticationConfigurer<B> failureHandler(AuthenticationFailureHandler failureHandler) {
|
||||
Assert.notNull(failureHandler, "failureHandler cannot be null");
|
||||
this.authenticationFailureHandler = failureHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationSuccessHandler} to use.
|
||||
* @param successHandler
|
||||
* @since 7.0
|
||||
*/
|
||||
public DPoPAuthenticationConfigurer<B> successHandler(AuthenticationSuccessHandler successHandler) {
|
||||
Assert.notNull(successHandler, "successHandler cannot be null");
|
||||
this.authenticationSuccessHandler = successHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
private RequestMatcher getRequestMatcher() {
|
||||
if (this.requestMatcher == null) {
|
||||
this.requestMatcher = new DPoPRequestMatcher();
|
||||
}
|
||||
return this.requestMatcher;
|
||||
}
|
||||
|
||||
private AuthenticationConverter getAuthenticationConverter() {
|
||||
if (this.authenticationConverter == null) {
|
||||
this.authenticationConverter = new DPoPAuthenticationConverter();
|
||||
}
|
||||
return this.authenticationConverter;
|
||||
}
|
||||
|
||||
private AuthenticationSuccessHandler getAuthenticationSuccessHandler() {
|
||||
if (this.authenticationSuccessHandler == null) {
|
||||
this.authenticationSuccessHandler = (request, response, authentication) -> {
|
||||
// No-op - will continue on filter chain
|
||||
};
|
||||
}
|
||||
return this.authenticationSuccessHandler;
|
||||
}
|
||||
|
||||
private AuthenticationFailureHandler getAuthenticationFailureHandler() {
|
||||
if (this.authenticationFailureHandler == null) {
|
||||
this.authenticationFailureHandler = new AuthenticationEntryPointFailureHandler(
|
||||
new DPoPAuthenticationEntryPoint());
|
||||
}
|
||||
return this.authenticationFailureHandler;
|
||||
}
|
||||
|
||||
}
|
||||
+182
-20
@@ -27,6 +27,7 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
@@ -40,11 +41,14 @@ import org.springframework.security.config.annotation.web.configurers.ExceptionH
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.FactorGrantedAuthority;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
import org.springframework.security.oauth2.server.resource.OAuth2ProtectedResourceMetadata;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.DPoPAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.DPoPAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider;
|
||||
@@ -53,16 +57,23 @@ import org.springframework.security.oauth2.server.resource.introspection.OpaqueT
|
||||
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.DPoPAuthenticationEntryPoint;
|
||||
import org.springframework.security.oauth2.server.resource.web.OAuth2ProtectedResourceMetadataFilter;
|
||||
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.oauth2.server.resource.web.authentication.DPoPAuthenticationConverter;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
|
||||
import org.springframework.security.web.access.DelegatingAccessDeniedHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
|
||||
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
|
||||
import org.springframework.security.web.csrf.CsrfException;
|
||||
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
|
||||
@@ -72,8 +83,12 @@ import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.accept.ContentNegotiationStrategy;
|
||||
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -146,6 +161,7 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy;
|
||||
* @author Josh Cummings
|
||||
* @author Evgeniy Cheban
|
||||
* @author Jerome Wacongne <ch4mp@c4-soft.com>
|
||||
* @author Joe Grandja
|
||||
* @author Max Batischev
|
||||
* @since 5.1
|
||||
* @see BearerTokenAuthenticationFilter
|
||||
@@ -169,8 +185,6 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
||||
|
||||
private final ApplicationContext context;
|
||||
|
||||
private DPoPAuthenticationConfigurer<H> dPoPAuthenticationConfigurer;
|
||||
|
||||
private AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;
|
||||
|
||||
private AuthenticationConverter authenticationConverter;
|
||||
@@ -179,6 +193,8 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
||||
|
||||
private OpaqueTokenConfigurer opaqueTokenConfigurer;
|
||||
|
||||
private DPoPConfigurer dPoPConfigurer;
|
||||
|
||||
private final ProtectedResourceMetadataConfigurer protectedResourceMetadataConfigurer = new ProtectedResourceMetadataConfigurer();
|
||||
|
||||
private AccessDeniedHandler accessDeniedHandler = new DelegatingAccessDeniedHandler(
|
||||
@@ -259,6 +275,21 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables DPoP-bound access token support.
|
||||
* @param dPoPCustomizer the {@link Customizer} to provide more options for the
|
||||
* {@link DPoPConfigurer}
|
||||
* @return the {@link OAuth2ResourceServerConfigurer} for further customizations
|
||||
* @since 7.1
|
||||
*/
|
||||
public OAuth2ResourceServerConfigurer<H> dPoP(Customizer<DPoPConfigurer> dPoPCustomizer) {
|
||||
if (this.dPoPConfigurer == null) {
|
||||
this.dPoPConfigurer = new DPoPConfigurer();
|
||||
}
|
||||
dPoPCustomizer.customize(this.dPoPConfigurer);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure OAuth 2.0 Protected Resource Metadata.
|
||||
* @param protectedResourceMetadataCustomizer the {@link Customizer} to provide more
|
||||
@@ -271,22 +302,6 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables DPoP support.
|
||||
* @param dpopAuthenticatioCustomizer the {@link Customizer} to provide more options
|
||||
* for the {@link DPoPAuthenticationConfigurer}
|
||||
* @return the {@link OAuth2ResourceServerConfigurer} for further customizations
|
||||
* @since 7.0
|
||||
*/
|
||||
public OAuth2ResourceServerConfigurer<H> dpop(
|
||||
Customizer<DPoPAuthenticationConfigurer<H>> dpopAuthenticatioCustomizer) {
|
||||
if (this.dPoPAuthenticationConfigurer == null) {
|
||||
this.dPoPAuthenticationConfigurer = new DPoPAuthenticationConfigurer<>();
|
||||
}
|
||||
dpopAuthenticatioCustomizer.customize(this.dPoPAuthenticationConfigurer);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(H http) {
|
||||
validateConfiguration();
|
||||
@@ -315,8 +330,8 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
||||
filter = postProcess(filter);
|
||||
http.addFilter(filter);
|
||||
|
||||
if (dPoPAuthenticationAvailable && this.dPoPAuthenticationConfigurer != null) {
|
||||
this.dPoPAuthenticationConfigurer.configure(http);
|
||||
if (dPoPAuthenticationAvailable && this.dPoPConfigurer != null) {
|
||||
this.dPoPConfigurer.configure(http);
|
||||
}
|
||||
|
||||
OAuth2ProtectedResourceMetadataFilter protectedResourceMetadataFilter = new OAuth2ProtectedResourceMetadataFilter();
|
||||
@@ -611,6 +626,153 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A configurer for OAuth 2.0 Demonstrating Proof of Possession (DPoP) support.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Max Batischev
|
||||
* @since 7.1
|
||||
* @see AuthenticationFilter
|
||||
* @see DPoPAuthenticationConverter
|
||||
* @see DPoPAuthenticationEntryPoint
|
||||
* @see DPoPAuthenticationProvider
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc9449">RFC
|
||||
* 9449 OAuth 2.0 Demonstrating Proof of Possession (DPoP)</a>
|
||||
*/
|
||||
public final class DPoPConfigurer {
|
||||
|
||||
private RequestMatcher requestMatcher;
|
||||
|
||||
private AuthenticationConverter authenticationConverter;
|
||||
|
||||
private AuthenticationSuccessHandler authenticationSuccessHandler;
|
||||
|
||||
private AuthenticationFailureHandler authenticationFailureHandler;
|
||||
|
||||
/**
|
||||
* Sets the {@link RequestMatcher} used when matching the
|
||||
* {@link HttpServletRequest} to a DPoP-protected resource request.
|
||||
* @param requestMatcher the {@link RequestMatcher} used when matching the
|
||||
* {@link HttpServletRequest} to a DPoP-protected resource request
|
||||
* @return the {@link DPoPConfigurer} for further configuration
|
||||
*/
|
||||
public DPoPConfigurer requestMatcher(RequestMatcher requestMatcher) {
|
||||
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
|
||||
this.requestMatcher = requestMatcher;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationConverter} used when attempting to extract a
|
||||
* DPoP-bound access token from {@link HttpServletRequest} to an instance of
|
||||
* {@link DPoPAuthenticationToken} used for authenticating the DPoP-protected
|
||||
* resource request. The default is {@link DPoPAuthenticationConverter}.
|
||||
* @param authenticationConverter the {@link AuthenticationConverter} used when
|
||||
* attempting to extract a DPoP-bound access token from {@link HttpServletRequest}
|
||||
* @return the {@link DPoPConfigurer} for further configuration
|
||||
*/
|
||||
public DPoPConfigurer authenticationConverter(AuthenticationConverter authenticationConverter) {
|
||||
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||
this.authenticationConverter = authenticationConverter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationSuccessHandler} used for handling an
|
||||
* authenticated DPoP-protected resource request.
|
||||
* @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler}
|
||||
* used for handling an authenticated DPoP-protected resource request
|
||||
* @return the {@link DPoPConfigurer} for further configuration
|
||||
*/
|
||||
public DPoPConfigurer authenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) {
|
||||
Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null");
|
||||
this.authenticationSuccessHandler = authenticationSuccessHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationFailureHandler} used for handling a failed
|
||||
* DPoP-protected resource request. The default is
|
||||
* {@link AuthenticationEntryPointFailureHandler} with
|
||||
* {@link DPoPAuthenticationEntryPoint}.
|
||||
* @param authenticationFailureHandler the {@link AuthenticationFailureHandler}
|
||||
* used for handling a failed DPoP-protected resource request
|
||||
* @return the {@link DPoPConfigurer} for further configuration
|
||||
*/
|
||||
public DPoPConfigurer authenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
|
||||
Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null");
|
||||
this.authenticationFailureHandler = authenticationFailureHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void configure(H http) {
|
||||
AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
|
||||
http.authenticationProvider(new DPoPAuthenticationProvider(getTokenAuthenticationManager(http)));
|
||||
AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManager,
|
||||
getAuthenticationConverter());
|
||||
authenticationFilter.setRequestMatcher(getRequestMatcher());
|
||||
authenticationFilter.setSuccessHandler(getAuthenticationSuccessHandler());
|
||||
authenticationFilter.setFailureHandler(getAuthenticationFailureHandler());
|
||||
authenticationFilter.setSecurityContextRepository(new RequestAttributeSecurityContextRepository());
|
||||
authenticationFilter = postProcess(authenticationFilter);
|
||||
http.addFilter(authenticationFilter);
|
||||
}
|
||||
|
||||
private AuthenticationManager getTokenAuthenticationManager(H http) {
|
||||
final AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver = getAuthenticationManagerResolver();
|
||||
if (authenticationManagerResolver == null) {
|
||||
return getAuthenticationManager(http);
|
||||
}
|
||||
return (authentication) -> {
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
|
||||
AuthenticationManager authenticationManager = authenticationManagerResolver
|
||||
.resolve(servletRequestAttributes.getRequest());
|
||||
return authenticationManager.authenticate(authentication);
|
||||
};
|
||||
}
|
||||
|
||||
private RequestMatcher getRequestMatcher() {
|
||||
if (this.requestMatcher == null) {
|
||||
this.requestMatcher = this::matchesDPoPRequest;
|
||||
}
|
||||
return this.requestMatcher;
|
||||
}
|
||||
|
||||
private AuthenticationConverter getAuthenticationConverter() {
|
||||
if (this.authenticationConverter == null) {
|
||||
this.authenticationConverter = new DPoPAuthenticationConverter();
|
||||
}
|
||||
return this.authenticationConverter;
|
||||
}
|
||||
|
||||
private AuthenticationSuccessHandler getAuthenticationSuccessHandler() {
|
||||
if (this.authenticationSuccessHandler == null) {
|
||||
this.authenticationSuccessHandler = (request, response, authentication) -> {
|
||||
// No-op - will continue on filter chain
|
||||
};
|
||||
}
|
||||
return this.authenticationSuccessHandler;
|
||||
}
|
||||
|
||||
private AuthenticationFailureHandler getAuthenticationFailureHandler() {
|
||||
if (this.authenticationFailureHandler == null) {
|
||||
this.authenticationFailureHandler = new AuthenticationEntryPointFailureHandler(
|
||||
new DPoPAuthenticationEntryPoint());
|
||||
}
|
||||
return this.authenticationFailureHandler;
|
||||
}
|
||||
|
||||
private boolean matchesDPoPRequest(HttpServletRequest request) {
|
||||
String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
if (!StringUtils.hasText(authorization)) {
|
||||
return false;
|
||||
}
|
||||
return StringUtils.startsWithIgnoreCase(authorization, OAuth2AccessToken.TokenType.DPOP.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final class ProtectedResourceMetadataConfigurer {
|
||||
|
||||
private Consumer<OAuth2ProtectedResourceMetadata.Builder> protectedResourceMetadataCustomizer;
|
||||
|
||||
+12
-12
@@ -16,17 +16,16 @@
|
||||
|
||||
package org.springframework.security.config.annotation.web
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.springframework.security.authentication.AuthenticationManagerResolver
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer
|
||||
import org.springframework.security.config.annotation.web.oauth2.resourceserver.DPoPDsl
|
||||
import org.springframework.security.config.annotation.web.oauth2.resourceserver.JwtDsl
|
||||
import org.springframework.security.config.annotation.web.oauth2.resourceserver.OpaqueTokenDsl
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer
|
||||
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver
|
||||
import org.springframework.security.web.AuthenticationEntryPoint
|
||||
import org.springframework.security.web.access.AccessDeniedHandler
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.DPoPAuthenticationConfigurer
|
||||
import org.springframework.security.config.annotation.web.oauth2.resourceserver.DPoPDsl
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [HttpSecurity] OAuth 2.0 resource server support using
|
||||
@@ -51,7 +50,7 @@ class OAuth2ResourceServerDsl {
|
||||
|
||||
private var jwt: ((OAuth2ResourceServerConfigurer<HttpSecurity>.JwtConfigurer) -> Unit)? = null
|
||||
private var opaqueToken: ((OAuth2ResourceServerConfigurer<HttpSecurity>.OpaqueTokenConfigurer) -> Unit)? = null
|
||||
private var dpop: ((DPoPAuthenticationConfigurer<HttpSecurity>) -> Unit)? = null
|
||||
private var dPoP: ((OAuth2ResourceServerConfigurer<HttpSecurity>.DPoPConfigurer) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* Enables JWT-encoded bearer token support.
|
||||
@@ -114,7 +113,7 @@ class OAuth2ResourceServerDsl {
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables DPoP support.
|
||||
* Enables DPoP-bound access token support.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
@@ -127,7 +126,8 @@ class OAuth2ResourceServerDsl {
|
||||
* fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
* http {
|
||||
* oauth2ResourceServer {
|
||||
* dpop { }
|
||||
* jwt { }
|
||||
* dPoP { }
|
||||
* }
|
||||
* }
|
||||
* return http.build()
|
||||
@@ -135,12 +135,12 @@ class OAuth2ResourceServerDsl {
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param dpopConfig custom configurations to configure DPoP support
|
||||
* @param dPoPConfig custom configurations to configure DPoP-bound access token support
|
||||
* @see [DPoPDsl]
|
||||
* @since 7.0
|
||||
* @since 7.1
|
||||
*/
|
||||
fun dpop(dpopConfig: DPoPDsl.() -> Unit) {
|
||||
this.dpop = DPoPDsl().apply(dpopConfig).get()
|
||||
fun dPoP(dPoPConfig: DPoPDsl.() -> Unit) {
|
||||
this.dPoP = DPoPDsl().apply(dPoPConfig).get()
|
||||
}
|
||||
|
||||
internal fun get(): (OAuth2ResourceServerConfigurer<HttpSecurity>) -> Unit {
|
||||
@@ -151,7 +151,7 @@ class OAuth2ResourceServerDsl {
|
||||
authenticationManagerResolver?.also { oauth2ResourceServer.authenticationManagerResolver(authenticationManagerResolver) }
|
||||
jwt?.also { oauth2ResourceServer.jwt(jwt) }
|
||||
opaqueToken?.also { oauth2ResourceServer.opaqueToken(opaqueToken) }
|
||||
dpop?.also { oauth2ResourceServer.dpop(dpop) }
|
||||
dPoP?.also { oauth2ResourceServer.dPoP(dPoP) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+25
-16
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,35 +16,44 @@
|
||||
|
||||
package org.springframework.security.config.annotation.web.oauth2.resourceserver
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.DPoPAuthenticationConfigurer
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer
|
||||
import org.springframework.security.oauth2.server.resource.authentication.DPoPAuthenticationToken
|
||||
import org.springframework.security.oauth2.server.resource.web.DPoPAuthenticationEntryPoint
|
||||
import org.springframework.security.oauth2.server.resource.web.authentication.DPoPAuthenticationConverter
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter
|
||||
import org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure DPoP support using idiomatic Kotlin code.
|
||||
* A Kotlin DSL to configure DPoP-bound access token support using idiomatic Kotlin code.
|
||||
*
|
||||
* @author Max Batischev
|
||||
* @property requestMatcher the [RequestMatcher] to use.
|
||||
* @property authenticationConverter the [AuthenticationConverter] to use.
|
||||
* @property successHandler the [AuthenticationSuccessHandler] to use.
|
||||
* @property failureHandler the [AuthenticationFailureHandler] to use.
|
||||
* @since 7.0
|
||||
* @property requestMatcher the [RequestMatcher] used when matching the [HttpServletRequest] to a DPoP-protected resource request.
|
||||
* @property authenticationConverter the [AuthenticationConverter] used when attempting to extract a DPoP-bound access token
|
||||
* from [HttpServletRequest] to an instance of [DPoPAuthenticationToken] used for authenticating the DPoP-protected resource request.
|
||||
* The default is [DPoPAuthenticationConverter].
|
||||
* @property authenticationSuccessHandler the [AuthenticationSuccessHandler] used for handling an authenticated DPoP-protected resource request.
|
||||
* @property authenticationFailureHandler the [AuthenticationFailureHandler] used for handling a failed DPoP-protected resource request.
|
||||
* The default is [AuthenticationEntryPointFailureHandler] with [DPoPAuthenticationEntryPoint].
|
||||
* @since 7.1
|
||||
*/
|
||||
@OAuth2ResourceServerSecurityMarker
|
||||
class DPoPDsl {
|
||||
var requestMatcher: RequestMatcher? = null
|
||||
var authenticationConverter: AuthenticationConverter? = null
|
||||
var successHandler: AuthenticationSuccessHandler? = null
|
||||
var failureHandler: AuthenticationFailureHandler? = null
|
||||
var authenticationSuccessHandler: AuthenticationSuccessHandler? = null
|
||||
var authenticationFailureHandler: AuthenticationFailureHandler? = null
|
||||
|
||||
internal fun get(): (DPoPAuthenticationConfigurer<HttpSecurity>) -> Unit {
|
||||
return { dpop ->
|
||||
requestMatcher?.also { dpop.requestMatcher(requestMatcher) }
|
||||
authenticationConverter?.also { dpop.authenticationConverter(authenticationConverter) }
|
||||
successHandler?.also { dpop.successHandler(successHandler) }
|
||||
failureHandler?.also { dpop.failureHandler(failureHandler) }
|
||||
internal fun get(): (OAuth2ResourceServerConfigurer<HttpSecurity>.DPoPConfigurer) -> Unit {
|
||||
return { dPoP ->
|
||||
requestMatcher?.also { dPoP.requestMatcher(requestMatcher) }
|
||||
authenticationConverter?.also { dPoP.authenticationConverter(authenticationConverter) }
|
||||
authenticationSuccessHandler?.also { dPoP.authenticationSuccessHandler(authenticationSuccessHandler) }
|
||||
authenticationFailureHandler?.also { dPoP.authenticationFailureHandler(authenticationFailureHandler) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -686,19 +686,6 @@ opaque-token.attlist &=
|
||||
## Reference to an OpaqueTokenAuthenticationConverter responsible for converting successful introspection result into an Authentication.
|
||||
attribute authentication-converter-ref {xsd:token}?
|
||||
|
||||
dpop =
|
||||
## Configuration DpoP
|
||||
element dpop {dpop.attlist}
|
||||
dpop.attlist &=
|
||||
## DPoP Request Matcher
|
||||
attribute dpop-request-matcher-ref {xsd:token}?
|
||||
dpop.attlist &=
|
||||
attribute dpop-authentication-converter-ref {xsd:token}?
|
||||
dpop.attlist &=
|
||||
attribute dpop-success-handler-ref {xsd:token}?
|
||||
dpop.attlist &=
|
||||
attribute dpop-failure-handler-ref {xsd:token}?
|
||||
|
||||
saml2-login =
|
||||
## Configures authentication support for SAML 2.0 Login
|
||||
element saml2-login {saml2-login.attlist}
|
||||
|
||||
@@ -2074,26 +2074,6 @@
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:attributeGroup>
|
||||
<xs:element name="dpop">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Configuration DpoP
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:attributeGroup ref="security:dpop.attlist"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:attributeGroup name="dpop.attlist">
|
||||
<xs:attribute name="dpop-request-matcher-ref" type="xs:token">
|
||||
<xs:annotation>
|
||||
<xs:documentation>DPoP Request Matcher
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="dpop-authentication-converter-ref" type="xs:token"/>
|
||||
<xs:attribute name="dpop-success-handler-ref" type="xs:token"/>
|
||||
<xs:attribute name="dpop-failure-handler-ref" type="xs:token"/>
|
||||
</xs:attributeGroup>
|
||||
|
||||
<xs:attributeGroup name="saml2-login.attlist">
|
||||
<xs:attribute name="relying-party-registration-repository-ref" type="xs:token">
|
||||
|
||||
+6
-72
@@ -37,8 +37,6 @@ import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -53,7 +51,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.jose.TestJwks;
|
||||
import org.springframework.security.oauth2.jose.TestKeys;
|
||||
@@ -65,28 +62,24 @@ import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
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.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
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.status;
|
||||
|
||||
/**
|
||||
* Tests for {@link DPoPAuthenticationConfigurer}.
|
||||
* Integration tests for OAuth 2.0 Demonstrating Proof of Possession (DPoP) support.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class DPoPAuthenticationConfigurerTests {
|
||||
public class DPoPAuthenticationTests {
|
||||
|
||||
private static final RSAPublicKey PROVIDER_RSA_PUBLIC_KEY = TestKeys.DEFAULT_PUBLIC_KEY;
|
||||
|
||||
@@ -183,22 +176,6 @@ public class DPoPAuthenticationConfigurerTests {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenCustomSuccessHandlerIsPresentThenAccessed() throws Exception {
|
||||
this.spring.register(SecurityConfigWithCustomSuccessHandler.class, ResourceEndpoints.class).autowire();
|
||||
Set<String> scope = Collections.singleton("resource1.read");
|
||||
String accessToken = generateAccessToken(scope, CLIENT_EC_KEY);
|
||||
String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken);
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/resource1")
|
||||
.header(HttpHeaders.AUTHORIZATION, "DPoP " + accessToken)
|
||||
.header("DPoP", dPoPProof))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string("resource1"));
|
||||
// @formatter:on
|
||||
verify(SecurityConfigWithCustomSuccessHandler.successHandler).onAuthenticationSuccess(any(), any(), any());
|
||||
}
|
||||
|
||||
private static String generateAccessToken(Set<String> scope, JWK jwk) {
|
||||
Map<String, Object> jktClaim = null;
|
||||
if (jwk != null) {
|
||||
@@ -268,11 +245,10 @@ public class DPoPAuthenticationConfigurerTests {
|
||||
.requestMatchers("/resource2").hasAnyAuthority("SCOPE_resource2.read", "SCOPE_resource2.write")
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer((oauth2ResourceServer) ->
|
||||
oauth2ResourceServer
|
||||
.jwt(Customizer.withDefaults())
|
||||
.dpop(Customizer.withDefaults())
|
||||
);
|
||||
.oauth2ResourceServer((oauth2) -> oauth2
|
||||
.jwt(Customizer.withDefaults())
|
||||
.dPoP(Customizer.withDefaults()));
|
||||
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
@@ -284,48 +260,6 @@ public class DPoPAuthenticationConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
static class SecurityConfigWithCustomSuccessHandler {
|
||||
|
||||
static final CustomSuccessHandler successHandler = spy(CustomSuccessHandler.class);
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeHttpRequests((authorize) ->
|
||||
authorize
|
||||
.requestMatchers("/resource1").hasAnyAuthority("SCOPE_resource1.read", "SCOPE_resource1.write")
|
||||
.requestMatchers("/resource2").hasAnyAuthority("SCOPE_resource2.read", "SCOPE_resource2.write")
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer((oauth2ResourceServer) ->
|
||||
oauth2ResourceServer
|
||||
.jwt(Customizer.withDefaults())
|
||||
.dpop((dpop) -> dpop.successHandler(successHandler))
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
NimbusJwtDecoder jwtDecoder() {
|
||||
return NimbusJwtDecoder.withPublicKey(PROVIDER_RSA_PUBLIC_KEY).build();
|
||||
}
|
||||
|
||||
static class CustomSuccessHandler implements AuthenticationSuccessHandler {
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||
Authentication authentication) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class ResourceEndpoints {
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* 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.
|
||||
@@ -57,7 +57,7 @@ import java.time.temporal.ChronoUnit
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Tests for [DPoPDsl]
|
||||
* Integration tests for OAuth 2.0 Demonstrating Proof of Possession (DPoP) support [DPoPDsl].
|
||||
*
|
||||
* @author Max Batischev
|
||||
*/
|
||||
@@ -202,7 +202,7 @@ class DPoPDslTests {
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
jwt { }
|
||||
dpop { }
|
||||
dPoP { }
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
|
||||
Reference in New Issue
Block a user