Conditionally resolve bearer token from request parameters
Before this commit, the DefaultBearerTokenResolver unconditionally resolved the request parameters to check whether multiple tokens are present in the request and reject those requests as invalid. This commit changes this behaviour to resolve the request parameters only if parameter token is supported for the specific request according to spec (RFC 6750). Closes gh-10326
This commit is contained in:
parent
88c64b3b7b
commit
6db58cbf8a
|
@ -360,7 +360,7 @@ public class OAuth2ResourceServerConfigurerTests {
|
|||
this.spring.register(JwkSetUriConfig.class).autowire();
|
||||
// engage csrf
|
||||
// @formatter:off
|
||||
this.mvc.perform(post("/").with(bearerToken("token").asParam()))
|
||||
this.mvc.perform(post("/").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken("token").asParam()))
|
||||
.andExpect(status().isForbidden())
|
||||
.andExpect(header().doesNotExist(HttpHeaders.WWW_AUTHENTICATE));
|
||||
// @formatter:on
|
||||
|
@ -370,7 +370,7 @@ public class OAuth2ResourceServerConfigurerTests {
|
|||
public void postWhenCsrfDisabledWithBearerTokenAsFormParameterThenIgnoresToken() throws Exception {
|
||||
this.spring.register(CsrfDisabledConfig.class).autowire();
|
||||
// @formatter:off
|
||||
this.mvc.perform(post("/").with(bearerToken("token").asParam()))
|
||||
this.mvc.perform(post("/").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken("token").asParam()))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer"));
|
||||
// @formatter:on
|
||||
|
@ -536,7 +536,7 @@ public class OAuth2ResourceServerConfigurerTests {
|
|||
mockRestOperations(jwks("Default"));
|
||||
String token = this.token("ValidNoScopes");
|
||||
// @formatter:off
|
||||
this.mvc.perform(post("/authenticated").with(bearerToken(token)))
|
||||
this.mvc.perform(post("/authenticated").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken(token)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string("test-subject"));
|
||||
// @formatter:on
|
||||
|
@ -558,7 +558,7 @@ public class OAuth2ResourceServerConfigurerTests {
|
|||
mockRestOperations(jwks("Default"));
|
||||
String token = this.token("Expired");
|
||||
// @formatter:off
|
||||
this.mvc.perform(post("/authenticated").with(bearerToken(token)))
|
||||
this.mvc.perform(post("/authenticated").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken(token)))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt"));
|
||||
// @formatter:on
|
||||
|
@ -626,7 +626,7 @@ public class OAuth2ResourceServerConfigurerTests {
|
|||
this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(JWT_SUBJECT));
|
||||
this.mvc.perform(post("/authenticated").param("access_token", JWT_TOKEN))
|
||||
this.mvc.perform(post("/authenticated").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).param("access_token", JWT_TOKEN))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(JWT_SUBJECT));
|
||||
// @formatter:on
|
||||
|
@ -659,6 +659,7 @@ public class OAuth2ResourceServerConfigurerTests {
|
|||
given(decoder.decode(anyString())).willReturn(JWT);
|
||||
// @formatter:off
|
||||
MockHttpServletRequestBuilder request = post("/authenticated")
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
.param("access_token", JWT_TOKEN)
|
||||
.with(bearerToken(JWT_TOKEN))
|
||||
.with(csrf());
|
||||
|
|
|
@ -261,6 +261,7 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
|
|||
public void postWhenBearerTokenAsFormParameterThenIgnoresToken() throws Exception {
|
||||
this.spring.configLocations(xml("JwkSetUri")).autowire();
|
||||
this.mvc.perform(post("/") // engage csrf
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
.param("access_token", "token")).andExpect(status().isForbidden())
|
||||
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer")); // different
|
||||
// from
|
||||
|
@ -451,7 +452,7 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
|
|||
// @formatter:off
|
||||
this.mvc.perform(get("/authenticated").header("Authorization", "Bearer token"))
|
||||
.andExpect(status().isNotFound());
|
||||
this.mvc.perform(post("/authenticated").param("access_token", "token"))
|
||||
this.mvc.perform(post("/authenticated").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).param("access_token", "token"))
|
||||
.andExpect(status().isNotFound());
|
||||
// @formatter:on
|
||||
}
|
||||
|
@ -477,6 +478,7 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
|
|||
this.spring.configLocations(xml("MockJwtDecoder"), xml("AllowBearerTokenInBody")).autowire();
|
||||
// @formatter:off
|
||||
MockHttpServletRequestBuilder request = post("/authenticated")
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
.param("access_token", "token")
|
||||
.header("Authorization", "Bearer token")
|
||||
.with(csrf());
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
|
@ -22,6 +22,7 @@ import java.util.regex.Pattern;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.server.resource.BearerTokenError;
|
||||
import org.springframework.security.oauth2.server.resource.BearerTokenErrors;
|
||||
|
@ -47,18 +48,19 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver {
|
|||
private String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION;
|
||||
|
||||
@Override
|
||||
public String resolve(HttpServletRequest request) {
|
||||
String authorizationHeaderToken = resolveFromAuthorizationHeader(request);
|
||||
String parameterToken = resolveFromRequestParameters(request);
|
||||
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) {
|
||||
BearerTokenError error = BearerTokenErrors
|
||||
final BearerTokenError error = BearerTokenErrors
|
||||
.invalidRequest("Found multiple bearer tokens in the request");
|
||||
throw new OAuth2AuthenticationException(error);
|
||||
}
|
||||
return authorizationHeaderToken;
|
||||
}
|
||||
if (parameterToken != null && isParameterTokenSupportedForRequest(request)) {
|
||||
if (parameterToken != null && isParameterTokenEnabledForRequest(request)) {
|
||||
return parameterToken;
|
||||
}
|
||||
return null;
|
||||
|
@ -124,8 +126,15 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver {
|
|||
throw new OAuth2AuthenticationException(error);
|
||||
}
|
||||
|
||||
private boolean isParameterTokenSupportedForRequest(HttpServletRequest request) {
|
||||
return ((this.allowFormEncodedBodyParameter && "POST".equals(request.getMethod()))
|
||||
private boolean isParameterTokenSupportedForRequest(final HttpServletRequest request) {
|
||||
return (("POST".equals(request.getMethod())
|
||||
&& MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType()))
|
||||
|| "GET".equals(request.getMethod()));
|
||||
}
|
||||
|
||||
private boolean isParameterTokenEnabledForRequest(final HttpServletRequest request) {
|
||||
return ((this.allowFormEncodedBodyParameter && "POST".equals(request.getMethod())
|
||||
&& MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType()))
|
||||
|| (this.allowUriQueryParameter && "GET".equals(request.getMethod())));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
|
@ -126,14 +126,38 @@ public class DefaultBearerTokenResolverTests {
|
|||
.withMessageContaining("Found multiple bearer tokens in the request");
|
||||
}
|
||||
|
||||
// gh-10326
|
||||
@Test
|
||||
public void resolveWhenRequestContainsTwoAccessTokenParametersThenAuthenticationExceptionIsThrown() {
|
||||
public void resolveWhenRequestContainsTwoAccessTokenQueryParametersThenAuthenticationExceptionIsThrown() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setMethod("GET");
|
||||
request.addParameter("access_token", "token1", "token2");
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.resolver.resolve(request))
|
||||
.withMessageContaining("Found multiple bearer tokens in the request");
|
||||
}
|
||||
|
||||
// gh-10326
|
||||
@Test
|
||||
public void resolveWhenRequestContainsTwoAccessTokenFormParametersThenAuthenticationExceptionIsThrown() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setMethod("POST");
|
||||
request.setContentType("application/x-www-form-urlencoded");
|
||||
request.addParameter("access_token", "token1", "token2");
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.resolver.resolve(request))
|
||||
.withMessageContaining("Found multiple bearer tokens in the request");
|
||||
}
|
||||
|
||||
// gh-10326
|
||||
@Test
|
||||
public void resolveWhenParameterIsPresentInMultipartRequestAndFormParameterSupportedThenTokenIsNotResolved() {
|
||||
this.resolver.setAllowFormEncodedBodyParameter(true);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setMethod("POST");
|
||||
request.setContentType("multipart/form-data");
|
||||
request.addParameter("access_token", TEST_TOKEN);
|
||||
assertThat(this.resolver.resolve(request)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWhenFormParameterIsPresentAndSupportedThenTokenIsResolved() {
|
||||
this.resolver.setAllowFormEncodedBodyParameter(true);
|
||||
|
|
Loading…
Reference in New Issue