mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-22 20:12:14 +00:00
Add AuthenticationEntryPoint for DPoP
Issue gh-16574 Closes gh-16900
This commit is contained in:
parent
21a85e3520
commit
9c073dbcde
@ -17,11 +17,14 @@
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.server.resource;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@ -29,18 +32,21 @@ import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.DPoPAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.DPoPAuthenticationToken;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
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.HttpStatusEntryPoint;
|
||||
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@ -102,7 +108,7 @@ final class DPoPAuthenticationConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
private AuthenticationFailureHandler getAuthenticationFailureHandler() {
|
||||
if (this.authenticationFailureHandler == null) {
|
||||
this.authenticationFailureHandler = new AuthenticationEntryPointFailureHandler(
|
||||
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
|
||||
new DPoPAuthenticationEntryPoint());
|
||||
}
|
||||
return this.authenticationFailureHandler;
|
||||
}
|
||||
@ -161,4 +167,47 @@ final class DPoPAuthenticationConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
|
||||
}
|
||||
|
||||
private static final class DPoPAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException authenticationException) {
|
||||
Map<String, String> parameters = new LinkedHashMap<>();
|
||||
if (authenticationException instanceof OAuth2AuthenticationException oauth2AuthenticationException) {
|
||||
OAuth2Error error = oauth2AuthenticationException.getError();
|
||||
parameters.put(OAuth2ParameterNames.ERROR, error.getErrorCode());
|
||||
if (StringUtils.hasText(error.getDescription())) {
|
||||
parameters.put(OAuth2ParameterNames.ERROR_DESCRIPTION, error.getDescription());
|
||||
}
|
||||
if (StringUtils.hasText(error.getUri())) {
|
||||
parameters.put(OAuth2ParameterNames.ERROR_URI, error.getUri());
|
||||
}
|
||||
}
|
||||
parameters.put("algs",
|
||||
JwsAlgorithms.RS256 + " " + JwsAlgorithms.RS384 + " " + JwsAlgorithms.RS512 + " "
|
||||
+ JwsAlgorithms.PS256 + " " + JwsAlgorithms.PS384 + " " + JwsAlgorithms.PS512 + " "
|
||||
+ JwsAlgorithms.ES256 + " " + JwsAlgorithms.ES384 + " " + JwsAlgorithms.ES512);
|
||||
String wwwAuthenticate = toWWWAuthenticateHeader(parameters);
|
||||
response.addHeader(HttpHeaders.WWW_AUTHENTICATE, wwwAuthenticate);
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||
}
|
||||
|
||||
private static String toWWWAuthenticateHeader(Map<String, String> parameters) {
|
||||
StringBuilder wwwAuthenticate = new StringBuilder();
|
||||
wwwAuthenticate.append(OAuth2AccessToken.TokenType.DPOP.getValue());
|
||||
if (!parameters.isEmpty()) {
|
||||
wwwAuthenticate.append(" ");
|
||||
int i = 0;
|
||||
for (Map.Entry<String, String> entry : parameters.entrySet()) {
|
||||
wwwAuthenticate.append(entry.getKey()).append("=\"").append(entry.getValue()).append("\"");
|
||||
if (i++ != parameters.size() - 1) {
|
||||
wwwAuthenticate.append(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
return wwwAuthenticate.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -70,6 +70,7 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -120,7 +121,9 @@ public class DPoPAuthenticationConfigurerTests {
|
||||
.header(HttpHeaders.AUTHORIZATION, "DPoP " + accessToken)
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
|
||||
.header("DPoP", dPoPProof))
|
||||
.andExpect(status().isUnauthorized());
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE,
|
||||
"DPoP error=\"invalid_request\", error_description=\"Found multiple Authorization headers.\", algs=\"RS256 RS384 RS512 PS256 PS384 PS512 ES256 ES384 ES512\""));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@ -134,7 +137,9 @@ public class DPoPAuthenticationConfigurerTests {
|
||||
this.mvc.perform(get("/resource1")
|
||||
.header(HttpHeaders.AUTHORIZATION, "DPoP " + accessToken + " m a l f o r m e d ")
|
||||
.header("DPoP", dPoPProof))
|
||||
.andExpect(status().isUnauthorized());
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE,
|
||||
"DPoP error=\"invalid_token\", error_description=\"DPoP access token is malformed.\", algs=\"RS256 RS384 RS512 PS256 PS384 PS512 ES256 ES384 ES512\""));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@ -149,7 +154,9 @@ public class DPoPAuthenticationConfigurerTests {
|
||||
.header(HttpHeaders.AUTHORIZATION, "DPoP " + accessToken)
|
||||
.header("DPoP", dPoPProof)
|
||||
.header("DPoP", dPoPProof))
|
||||
.andExpect(status().isUnauthorized());
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE,
|
||||
"DPoP error=\"invalid_request\", error_description=\"DPoP proof is missing or invalid.\", algs=\"RS256 RS384 RS512 PS256 PS384 PS512 ES256 ES384 ES512\""));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user