diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java index 3ded49507f..f43127de1e 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java @@ -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()); diff --git a/config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java index 47459c827f..e561334713 100644 --- a/config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java +++ b/config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java @@ -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()); diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolver.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolver.java index db2fd78187..3407356732 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolver.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolver.java @@ -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()))); } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolverTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolverTests.java index b67c6c3211..9132ab2bd6 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolverTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolverTests.java @@ -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);