mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-30 16:52:13 +00:00
Merge branch '6.3.x' into 6.4.x
Closes gh-16901
This commit is contained in:
commit
db34de59bc
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -1560,12 +1560,15 @@ public class OAuth2ResourceServerConfigurerTests {
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
DefaultBearerTokenResolver defaultBearerTokenResolver = new DefaultBearerTokenResolver();
|
||||
defaultBearerTokenResolver.setAllowUriQueryParameter(true);
|
||||
http
|
||||
.authorizeRequests()
|
||||
.requestMatchers("/requires-read-scope").access("hasAuthority('SCOPE_message:read')")
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.oauth2ResourceServer()
|
||||
.bearerTokenResolver(defaultBearerTokenResolver)
|
||||
.jwt()
|
||||
.jwkSetUri(this.jwkSetUri);
|
||||
return http.build();
|
||||
|
@ -25,10 +25,15 @@
|
||||
|
||||
<c:property-placeholder local-override="true"/>
|
||||
|
||||
<b:bean id="bearerTokenResolver"
|
||||
class="org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver">
|
||||
<b:property name="allowUriQueryParameter" value="true"/>
|
||||
</b:bean>
|
||||
|
||||
<http>
|
||||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
<intercept-url pattern="/requires-read-scope" access="hasAuthority('SCOPE_message:read')"/>
|
||||
<oauth2-resource-server>
|
||||
<oauth2-resource-server bearer-token-resolver-ref="bearerTokenResolver">
|
||||
<jwt jwk-set-uri="${jwk-set-uri:https://idp.example.org}"/>
|
||||
</oauth2-resource-server>
|
||||
</http>
|
||||
|
@ -52,26 +52,77 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver {
|
||||
|
||||
@Override
|
||||
public String resolve(final HttpServletRequest request) {
|
||||
final String authorizationHeaderToken = resolveFromAuthorizationHeader(request);
|
||||
final String parameterToken = isParameterTokenSupportedForRequest(request)
|
||||
? resolveFromRequestParameters(request) : null;
|
||||
if (authorizationHeaderToken != null) {
|
||||
if (parameterToken != null) {
|
||||
// @formatter:off
|
||||
return resolveToken(
|
||||
resolveFromAuthorizationHeader(request),
|
||||
resolveAccessTokenFromQueryString(request),
|
||||
resolveAccessTokenFromBody(request)
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private static String resolveToken(String... accessTokens) {
|
||||
if (accessTokens == null || accessTokens.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String accessToken = null;
|
||||
for (String token : accessTokens) {
|
||||
if (accessToken == null) {
|
||||
accessToken = token;
|
||||
}
|
||||
else if (token != null) {
|
||||
BearerTokenError error = BearerTokenErrors
|
||||
.invalidRequest("Found multiple bearer tokens in the request");
|
||||
throw new OAuth2AuthenticationException(error);
|
||||
}
|
||||
return authorizationHeaderToken;
|
||||
}
|
||||
if (parameterToken != null && isParameterTokenEnabledForRequest(request)) {
|
||||
if (!StringUtils.hasText(parameterToken)) {
|
||||
BearerTokenError error = BearerTokenErrors
|
||||
.invalidRequest("The requested token parameter is an empty string");
|
||||
throw new OAuth2AuthenticationException(error);
|
||||
}
|
||||
return parameterToken;
|
||||
|
||||
if (accessToken != null && accessToken.isBlank()) {
|
||||
BearerTokenError error = BearerTokenErrors
|
||||
.invalidRequest("The requested token parameter is an empty string");
|
||||
throw new OAuth2AuthenticationException(error);
|
||||
}
|
||||
return null;
|
||||
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
private String resolveFromAuthorizationHeader(HttpServletRequest request) {
|
||||
String authorization = request.getHeader(this.bearerTokenHeaderName);
|
||||
if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Matcher matcher = authorizationPattern.matcher(authorization);
|
||||
if (!matcher.matches()) {
|
||||
BearerTokenError error = BearerTokenErrors.invalidToken("Bearer token is malformed");
|
||||
throw new OAuth2AuthenticationException(error);
|
||||
}
|
||||
|
||||
return matcher.group("token");
|
||||
}
|
||||
|
||||
private String resolveAccessTokenFromQueryString(HttpServletRequest request) {
|
||||
if (!this.allowUriQueryParameter || !HttpMethod.GET.name().equals(request.getMethod())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return resolveToken(request.getParameterValues(ACCESS_TOKEN_PARAMETER_NAME));
|
||||
}
|
||||
|
||||
private String resolveAccessTokenFromBody(HttpServletRequest request) {
|
||||
if (!this.allowFormEncodedBodyParameter
|
||||
|| !MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType())
|
||||
|| HttpMethod.GET.name().equals(request.getMethod())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String queryString = request.getQueryString();
|
||||
if (queryString != null && queryString.contains(ACCESS_TOKEN_PARAMETER_NAME)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return resolveToken(request.getParameterValues(ACCESS_TOKEN_PARAMETER_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -109,50 +160,4 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver {
|
||||
this.bearerTokenHeaderName = bearerTokenHeaderName;
|
||||
}
|
||||
|
||||
private String resolveFromAuthorizationHeader(HttpServletRequest request) {
|
||||
String authorization = request.getHeader(this.bearerTokenHeaderName);
|
||||
if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) {
|
||||
return null;
|
||||
}
|
||||
Matcher matcher = authorizationPattern.matcher(authorization);
|
||||
if (!matcher.matches()) {
|
||||
BearerTokenError error = BearerTokenErrors.invalidToken("Bearer token is malformed");
|
||||
throw new OAuth2AuthenticationException(error);
|
||||
}
|
||||
return matcher.group("token");
|
||||
}
|
||||
|
||||
private static String resolveFromRequestParameters(HttpServletRequest request) {
|
||||
String[] values = request.getParameterValues(ACCESS_TOKEN_PARAMETER_NAME);
|
||||
if (values == null || values.length == 0) {
|
||||
return null;
|
||||
}
|
||||
if (values.length == 1) {
|
||||
return values[0];
|
||||
}
|
||||
BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request");
|
||||
throw new OAuth2AuthenticationException(error);
|
||||
}
|
||||
|
||||
private boolean isParameterTokenSupportedForRequest(final HttpServletRequest request) {
|
||||
return isFormEncodedRequest(request) || isGetRequest(request);
|
||||
}
|
||||
|
||||
private static boolean isGetRequest(HttpServletRequest request) {
|
||||
return HttpMethod.GET.name().equals(request.getMethod());
|
||||
}
|
||||
|
||||
private static boolean isFormEncodedRequest(HttpServletRequest request) {
|
||||
return MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType());
|
||||
}
|
||||
|
||||
private static boolean hasAccessTokenInQueryString(HttpServletRequest request) {
|
||||
return (request.getQueryString() != null) && request.getQueryString().contains(ACCESS_TOKEN_PARAMETER_NAME);
|
||||
}
|
||||
|
||||
private boolean isParameterTokenEnabledForRequest(HttpServletRequest request) {
|
||||
return ((this.allowFormEncodedBodyParameter && isFormEncodedRequest(request) && !isGetRequest(request)
|
||||
&& !hasAccessTokenInQueryString(request)) || (this.allowUriQueryParameter && isGetRequest(request)));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -77,18 +77,18 @@ public class ServerBearerTokenAuthenticationConverter implements ServerAuthentic
|
||||
}
|
||||
return authorizationHeaderToken;
|
||||
}
|
||||
if (parameterToken != null && isParameterTokenSupportedForRequest(request)) {
|
||||
if (!StringUtils.hasText(parameterToken)) {
|
||||
BearerTokenError error = BearerTokenErrors
|
||||
.invalidRequest("The requested token parameter is an empty string");
|
||||
throw new OAuth2AuthenticationException(error);
|
||||
}
|
||||
return parameterToken;
|
||||
if (parameterToken != null && !StringUtils.hasText(parameterToken)) {
|
||||
BearerTokenError error = BearerTokenErrors
|
||||
.invalidRequest("The requested token parameter is an empty string");
|
||||
throw new OAuth2AuthenticationException(error);
|
||||
}
|
||||
return null;
|
||||
return parameterToken;
|
||||
}
|
||||
|
||||
private static String resolveAccessTokenFromRequest(ServerHttpRequest request) {
|
||||
private String resolveAccessTokenFromRequest(ServerHttpRequest request) {
|
||||
if (!isParameterTokenSupportedForRequest(request)) {
|
||||
return null;
|
||||
}
|
||||
List<String> parameterTokens = request.getQueryParams().get("access_token");
|
||||
if (CollectionUtils.isEmpty(parameterTokens)) {
|
||||
return null;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -110,6 +110,7 @@ public class DefaultBearerTokenResolverTests {
|
||||
|
||||
@Test
|
||||
public void resolveWhenValidHeaderIsPresentTogetherWithFormParameterThenAuthenticationExceptionIsThrown() {
|
||||
this.resolver.setAllowFormEncodedBodyParameter(true);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.addHeader("Authorization", "Bearer " + TEST_TOKEN);
|
||||
request.setMethod("POST");
|
||||
@ -121,6 +122,7 @@ public class DefaultBearerTokenResolverTests {
|
||||
|
||||
@Test
|
||||
public void resolveWhenValidHeaderIsPresentTogetherWithQueryParameterThenAuthenticationExceptionIsThrown() {
|
||||
this.resolver.setAllowUriQueryParameter(true);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.addHeader("Authorization", "Bearer " + TEST_TOKEN);
|
||||
request.setMethod("GET");
|
||||
@ -133,6 +135,7 @@ public class DefaultBearerTokenResolverTests {
|
||||
// gh-10326
|
||||
@Test
|
||||
public void resolveWhenRequestContainsTwoAccessTokenQueryParametersThenAuthenticationExceptionIsThrown() {
|
||||
this.resolver.setAllowUriQueryParameter(true);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setMethod("GET");
|
||||
request.addParameter("access_token", "token1", "token2");
|
||||
@ -143,6 +146,7 @@ public class DefaultBearerTokenResolverTests {
|
||||
// gh-10326
|
||||
@Test
|
||||
public void resolveWhenRequestContainsTwoAccessTokenFormParametersThenAuthenticationExceptionIsThrown() {
|
||||
this.resolver.setAllowFormEncodedBodyParameter(true);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setMethod("POST");
|
||||
request.setContentType("application/x-www-form-urlencoded");
|
||||
@ -233,6 +237,19 @@ public class DefaultBearerTokenResolverTests {
|
||||
assertThat(this.resolver.resolve(request)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWhenPostAndQueryParameterIsSupportedAndFormParameterIsPresentThenTokenIsNotResolved() {
|
||||
this.resolver.setAllowUriQueryParameter(true);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setMethod("POST");
|
||||
request.setContentType("application/x-www-form-urlencoded");
|
||||
request.setQueryString("access_token=" + TEST_TOKEN);
|
||||
request.addParameter("access_token", TEST_TOKEN);
|
||||
|
||||
assertThat(this.resolver.resolve(request)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWhenFormParameterIsPresentAndNotSupportedThenTokenIsNotResolved() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
@ -261,6 +278,25 @@ public class DefaultBearerTokenResolverTests {
|
||||
assertThat(this.resolver.resolve(request)).isNull();
|
||||
}
|
||||
|
||||
// gh-16038
|
||||
@Test
|
||||
public void resolveWhenRequestContainsTwoAccessTokenFormParametersAndSupportIsDisabledThenTokenIsNotResolved() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setMethod("POST");
|
||||
request.setContentType("application/x-www-form-urlencoded");
|
||||
request.addParameter("access_token", "token1", "token2");
|
||||
assertThat(this.resolver.resolve(request)).isNull();
|
||||
}
|
||||
|
||||
// gh-16038
|
||||
@Test
|
||||
public void resolveWhenRequestContainsTwoAccessTokenQueryParametersAndSupportIsDisabledThenTokenIsNotResolved() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setMethod("GET");
|
||||
request.addParameter("access_token", "token1", "token2");
|
||||
assertThat(this.resolver.resolve(request)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWhenQueryParameterIsPresentAndEmptyStringThenTokenIsNotResolved() {
|
||||
this.resolver.setAllowUriQueryParameter(true);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -157,6 +157,7 @@ public class ServerBearerTokenAuthenticationConverterTests {
|
||||
@Test
|
||||
public void resolveWhenValidHeaderIsPresentTogetherWithQueryParameterThenAuthenticationExceptionIsThrown() {
|
||||
// @formatter:off
|
||||
this.converter.setAllowUriQueryParameter(true);
|
||||
MockServerHttpRequest.BaseBuilder<?> request = MockServerHttpRequest.get("/")
|
||||
.queryParam("access_token", TEST_TOKEN)
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + TEST_TOKEN);
|
||||
@ -205,6 +206,7 @@ public class ServerBearerTokenAuthenticationConverterTests {
|
||||
|
||||
@Test
|
||||
void resolveWhenQueryParameterHasMultipleAccessTokensThenOAuth2AuthenticationException() {
|
||||
this.converter.setAllowUriQueryParameter(true);
|
||||
MockServerHttpRequest.BaseBuilder<?> request = MockServerHttpRequest.get("/")
|
||||
.queryParam("access_token", TEST_TOKEN, TEST_TOKEN);
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> convertToToken(request))
|
||||
@ -217,6 +219,14 @@ public class ServerBearerTokenAuthenticationConverterTests {
|
||||
|
||||
}
|
||||
|
||||
// gh-16038
|
||||
@Test
|
||||
void resolveWhenRequestContainsTwoAccessTokenQueryParametersAndSupportIsDisabledThenTokenIsNotResolved() {
|
||||
MockServerHttpRequest.BaseBuilder<?> request = MockServerHttpRequest.get("/")
|
||||
.queryParam("access_token", TEST_TOKEN, TEST_TOKEN);
|
||||
assertThat(convertToToken(request)).isNull();
|
||||
}
|
||||
|
||||
private BearerTokenAuthenticationToken convertToToken(MockServerHttpRequest.BaseBuilder<?> request) {
|
||||
return convertToToken(request.build());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user