From 6803ac4005857afb8cd6036c465ed77568a9cd8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonah=20Kl=C3=B6ckner?= Date: Tue, 17 Sep 2024 11:17:32 +0200 Subject: [PATCH 1/6] Add support for access token in body parameter as per rfc 6750 Sec. 2.2 --- ...verBearerTokenAuthenticationConverter.java | 117 +++++++++++++---- ...arerTokenAuthenticationConverterTests.java | 119 +++++++++++++++++- 2 files changed, 206 insertions(+), 30 deletions(-) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java index d30cd8e05af..ad93a724e23 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2024 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,14 +16,20 @@ package org.springframework.security.oauth2.server.resource.web.server.authentication; +import static org.springframework.security.oauth2.server.resource.BearerTokenErrors.invalidRequest; + import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; @@ -47,16 +53,20 @@ */ public class ServerBearerTokenAuthenticationConverter implements ServerAuthenticationConverter { + public static final String ACCESS_TOKEN_NAME = "access_token"; + public static final String MULTIPLE_BEARER_TOKENS_ERROR_MSG = "Found multiple bearer tokens in the request"; private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?[a-zA-Z0-9-._~+/]+=*)$", Pattern.CASE_INSENSITIVE); private boolean allowUriQueryParameter = false; + private boolean allowFormEncodedBodyParameter = false; + private String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION; @Override public Mono convert(ServerWebExchange exchange) { - return Mono.fromCallable(() -> token(exchange.getRequest())).map((token) -> { + return Mono.defer(() -> token(exchange)).map(token -> { if (token.isEmpty()) { BearerTokenError error = invalidTokenError(); throw new OAuth2AuthenticationException(error); @@ -65,38 +75,45 @@ public Mono convert(ServerWebExchange exchange) { }); } - private String token(ServerHttpRequest request) { - String authorizationHeaderToken = resolveFromAuthorizationHeader(request.getHeaders()); - String parameterToken = resolveAccessTokenFromRequest(request); - - if (authorizationHeaderToken != null) { - if (parameterToken != null) { - BearerTokenError error = BearerTokenErrors - .invalidRequest("Found multiple bearer tokens in the request"); - throw new OAuth2AuthenticationException(error); - } - return authorizationHeaderToken; - } - if (parameterToken != null && isParameterTokenSupportedForRequest(request)) { - return parameterToken; - } - return null; + private Mono token(ServerWebExchange exchange) { + final var request = exchange.getRequest(); + + return Flux.merge(resolveFromAuthorizationHeader(request.getHeaders()).map(s -> Tuples.of(s, TokenSource.HEADER)), + resolveAccessTokenFromRequest(request).map(s -> Tuples.of(s, TokenSource.QUERY_PARAMETER)), + resolveAccessTokenFromBody(exchange).map(s -> Tuples.of(s, TokenSource.BODY_PARAMETER))) + .collectList() + .mapNotNull(tokenTuples -> switch (tokenTuples.size()) { + case 0 -> null; + case 1 -> getTokenIfSupported(tokenTuples.get(0), request); + default -> { + BearerTokenError error = invalidRequest(MULTIPLE_BEARER_TOKENS_ERROR_MSG); + throw new OAuth2AuthenticationException(error); + } + }); } - private static String resolveAccessTokenFromRequest(ServerHttpRequest request) { - List parameterTokens = request.getQueryParams().get("access_token"); + private static Mono resolveAccessTokenFromRequest(ServerHttpRequest request) { + List parameterTokens = request.getQueryParams().get(ACCESS_TOKEN_NAME); if (CollectionUtils.isEmpty(parameterTokens)) { - return null; + return Mono.empty(); } if (parameterTokens.size() == 1) { - return parameterTokens.get(0); + return Mono.just(parameterTokens.get(0)); } - BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request"); + BearerTokenError error = invalidRequest(MULTIPLE_BEARER_TOKENS_ERROR_MSG); throw new OAuth2AuthenticationException(error); } + private String getTokenIfSupported(Tuple2 tokenTuple, ServerHttpRequest request) { + return switch (tokenTuple.getT2()) { + case HEADER -> tokenTuple.getT1(); + case QUERY_PARAMETER -> isParameterTokenSupportedForRequest(request) ? tokenTuple.getT1() : null; + case BODY_PARAMETER -> isBodyParameterTokenSupportedForRequest(request) ? tokenTuple.getT1() : null; + }; + } + /** * Set if transport of access token using URI query parameter is supported. Defaults * to {@code false}. @@ -122,25 +139,73 @@ public void setBearerTokenHeaderName(String bearerTokenHeaderName) { this.bearerTokenHeaderName = bearerTokenHeaderName; } - private String resolveFromAuthorizationHeader(HttpHeaders headers) { + /** + * Set if transport of access token using form-encoded body parameter is supported. + * Defaults to {@code false}. + * @param allowFormEncodedBodyParameter if the form-encoded body parameter is + * supported + */ + public void setAllowFormEncodedBodyParameter(boolean allowFormEncodedBodyParameter) { + this.allowFormEncodedBodyParameter = allowFormEncodedBodyParameter; + } + + private Mono resolveFromAuthorizationHeader(HttpHeaders headers) { String authorization = headers.getFirst(this.bearerTokenHeaderName); if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) { - return null; + return Mono.empty(); } Matcher matcher = authorizationPattern.matcher(authorization); if (!matcher.matches()) { BearerTokenError error = invalidTokenError(); throw new OAuth2AuthenticationException(error); } - return matcher.group("token"); + return Mono.just(matcher.group("token")); } private static BearerTokenError invalidTokenError() { return BearerTokenErrors.invalidToken("Bearer token is malformed"); } + private Mono resolveAccessTokenFromBody(ServerWebExchange exchange) { + if (!allowFormEncodedBodyParameter) { + return Mono.empty(); + } + + final var request = exchange.getRequest(); + + if (request.getMethod() == HttpMethod.POST && + MediaType.APPLICATION_FORM_URLENCODED.equalsTypeAndSubtype(request.getHeaders().getContentType())) { + + return exchange.getFormData().mapNotNull(formData -> { + if (formData.isEmpty()) { + return null; + } + if (formData.size() > 1) { + var error = invalidRequest("The HTTP request entity-body is not single-part"); + throw new OAuth2AuthenticationException(error); + } + final var tokens = formData.get(ACCESS_TOKEN_NAME); + if (tokens == null) { + return null; + } + if (tokens.size() > 1) { + var error = invalidRequest(MULTIPLE_BEARER_TOKENS_ERROR_MSG); + throw new OAuth2AuthenticationException(error); + } + return formData.getFirst(ACCESS_TOKEN_NAME); + }); + } + return Mono.empty(); + } + + private boolean isBodyParameterTokenSupportedForRequest(ServerHttpRequest request) { + return this.allowFormEncodedBodyParameter && HttpMethod.POST == request.getMethod(); + } + private boolean isParameterTokenSupportedForRequest(ServerHttpRequest request) { return this.allowUriQueryParameter && HttpMethod.GET.equals(request.getMethod()); } + private enum TokenSource {HEADER, QUERY_PARAMETER, BODY_PARAMETER} + } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverterTests.java index 6d9c7a5b98f..24b7c9b4d08 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverterTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverterTests.java @@ -16,11 +16,16 @@ package org.springframework.security.oauth2.server.resource.web.server.authentication; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; +import static org.springframework.mock.http.server.reactive.MockServerHttpRequest.post; + import java.util.Base64; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; @@ -30,9 +35,6 @@ import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes; import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - /** * @author Rob Winch * @since 5.1 @@ -217,6 +219,115 @@ void resolveWhenQueryParameterHasMultipleAccessTokensThenOAuth2AuthenticationExc } + @Test + void resolveWhenBodyParameterIsPresentThenTokenIsResolved() { + this.converter.setAllowFormEncodedBodyParameter(true); + var request = post("/").contentType(APPLICATION_FORM_URLENCODED) + .body("access_token=" + TEST_TOKEN); + + assertThat(convertToToken(request).getToken()).isEqualTo(TEST_TOKEN); + } + + + @Test + void resolveWhenBodyParameterIsPresentButNotAllowedThenTokenIsNotResolved() { + this.converter.setAllowFormEncodedBodyParameter(false); + var request = post("/").contentType(APPLICATION_FORM_URLENCODED) + .body("access_token=" + TEST_TOKEN); + + assertThat(convertToToken(request)).isNull(); + } + + @Test + void resolveWhenBodyParameterHasMultipleAccessTokensThenOAuth2AuthenticationException() { + this.converter.setAllowFormEncodedBodyParameter(true); + var request = post("/").contentType(APPLICATION_FORM_URLENCODED) + .body("access_token=" + TEST_TOKEN + "&access_token=" + TEST_TOKEN); + + assertThatExceptionOfType(OAuth2AuthenticationException.class) + .isThrownBy(() -> convertToToken(request)) + .satisfies(ex -> { + BearerTokenError error = (BearerTokenError) ex.getError(); + assertThat(error.getDescription()).isEqualTo("Found multiple bearer tokens in the request"); + assertThat(error.getErrorCode()).isEqualTo(BearerTokenErrorCodes.INVALID_REQUEST); + assertThat(error.getUri()).isEqualTo("https://tools.ietf.org/html/rfc6750#section-3.1"); + assertThat(error.getHttpStatus()).isEqualTo(HttpStatus.BAD_REQUEST); + }); + } + + @Test + void resolveWhenBodyParameterIsNotSinglePartThenOAuth2AuthenticationException() { + this.converter.setAllowFormEncodedBodyParameter(true); + var request = post("/").contentType(APPLICATION_FORM_URLENCODED) + .body("access_token=" + TEST_TOKEN + "&other_param=value"); + + assertThatExceptionOfType(OAuth2AuthenticationException.class) + .isThrownBy(() -> convertToToken(request)) + .satisfies(ex -> { + BearerTokenError error = (BearerTokenError) ex.getError(); + assertThat(error.getDescription()).isEqualTo("The HTTP request entity-body is not single-part"); + assertThat(error.getErrorCode()).isEqualTo(BearerTokenErrorCodes.INVALID_REQUEST); + assertThat(error.getUri()).isEqualTo("https://tools.ietf.org/html/rfc6750#section-3.1"); + assertThat(error.getHttpStatus()).isEqualTo(HttpStatus.BAD_REQUEST); + }); + } + + @Test + void resolveWhenNoBodyParameterThenTokenIsNotResolved() { + this.converter.setAllowFormEncodedBodyParameter(true); + var request = post("/").contentType(APPLICATION_FORM_URLENCODED); + + assertThat(convertToToken(request)).isNull(); + } + + @Test + void resolveWhenWrongBodyParameterThenTokenIsNotResolved() { + this.converter.setAllowFormEncodedBodyParameter(true); + var request = post("/").contentType(APPLICATION_FORM_URLENCODED) + .body("other_param=value"); + + assertThat(convertToToken(request)).isNull(); + } + + @Test + void resolveWhenValidHeaderIsPresentTogetherWithBodyParameterThenAuthenticationExceptionIsThrown() { + this.converter.setAllowFormEncodedBodyParameter(true); + var request = post("/").header(AUTHORIZATION, "Bearer " + TEST_TOKEN) + .contentType(APPLICATION_FORM_URLENCODED) + .body("access_token=" + TEST_TOKEN); + + assertThatExceptionOfType(OAuth2AuthenticationException.class) + .isThrownBy(() -> convertToToken(request)) + .withMessageContaining("Found multiple bearer tokens in the request"); + } + + @Test + void resolveWhenValidQueryParameterIsPresentTogetherWithBodyParameterThenAuthenticationExceptionIsThrown() { + this.converter.setAllowUriQueryParameter(true); + this.converter.setAllowFormEncodedBodyParameter(true); + var request = post("/").queryParam("access_token", TEST_TOKEN) + .contentType(APPLICATION_FORM_URLENCODED) + .body("access_token=" + TEST_TOKEN); + + assertThatExceptionOfType(OAuth2AuthenticationException.class) + .isThrownBy(() -> convertToToken(request)) + .withMessageContaining("Found multiple bearer tokens in the request"); + } + + @Test + void resolveWhenValidQueryParameterIsPresentTogetherWithBodyParameterAndValidHeaderThenAuthenticationExceptionIsThrown() { + this.converter.setAllowUriQueryParameter(true); + this.converter.setAllowFormEncodedBodyParameter(true); + var request = post("/").header(AUTHORIZATION, "Bearer " + TEST_TOKEN) + .queryParam("access_token", TEST_TOKEN) + .contentType(APPLICATION_FORM_URLENCODED) + .body("access_token=" + TEST_TOKEN); + + assertThatExceptionOfType(OAuth2AuthenticationException.class) + .isThrownBy(() -> convertToToken(request)) + .withMessageContaining("Found multiple bearer tokens in the request"); + } + private BearerTokenAuthenticationToken convertToToken(MockServerHttpRequest.BaseBuilder request) { return convertToToken(request.build()); } From 578a559aa40b9cebd1e7302b447e0c43f6275918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonah=20Kl=C3=B6ckner?= Date: Tue, 17 Sep 2024 15:44:07 +0200 Subject: [PATCH 2/6] allow other body params (should've read the whole rfc 6750 doc) --- .../ServerBearerTokenAuthenticationConverter.java | 4 ---- ...erverBearerTokenAuthenticationConverterTests.java | 12 ++---------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java index ad93a724e23..384730e0dd1 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java @@ -180,10 +180,6 @@ private Mono resolveAccessTokenFromBody(ServerWebExchange exchange) { if (formData.isEmpty()) { return null; } - if (formData.size() > 1) { - var error = invalidRequest("The HTTP request entity-body is not single-part"); - throw new OAuth2AuthenticationException(error); - } final var tokens = formData.get(ACCESS_TOKEN_NAME); if (tokens == null) { return null; diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverterTests.java index 24b7c9b4d08..01a667e5da8 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverterTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverterTests.java @@ -256,20 +256,12 @@ void resolveWhenBodyParameterHasMultipleAccessTokensThenOAuth2AuthenticationExce } @Test - void resolveWhenBodyParameterIsNotSinglePartThenOAuth2AuthenticationException() { + void resolveBodyContainsOtherParameterAsWellThenTokenIsResolved() { this.converter.setAllowFormEncodedBodyParameter(true); var request = post("/").contentType(APPLICATION_FORM_URLENCODED) .body("access_token=" + TEST_TOKEN + "&other_param=value"); - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> convertToToken(request)) - .satisfies(ex -> { - BearerTokenError error = (BearerTokenError) ex.getError(); - assertThat(error.getDescription()).isEqualTo("The HTTP request entity-body is not single-part"); - assertThat(error.getErrorCode()).isEqualTo(BearerTokenErrorCodes.INVALID_REQUEST); - assertThat(error.getUri()).isEqualTo("https://tools.ietf.org/html/rfc6750#section-3.1"); - assertThat(error.getHttpStatus()).isEqualTo(HttpStatus.BAD_REQUEST); - }); + assertThat(convertToToken(request).getToken()).isEqualTo(TEST_TOKEN); } @Test From d14b2c4acc1dd58b1ababd769fb1d06f0199bb38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonah=20Kl=C3=B6ckner?= Date: Wed, 2 Oct 2024 17:07:30 +0200 Subject: [PATCH 3/6] make Java 8 compliant --- ...verBearerTokenAuthenticationConverter.java | 32 ++++++++------ ...arerTokenAuthenticationConverterTests.java | 42 +++++++++---------- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java index 384730e0dd1..bf8adb5bf85 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java @@ -82,12 +82,15 @@ private Mono token(ServerWebExchange exchange) { resolveAccessTokenFromRequest(request).map(s -> Tuples.of(s, TokenSource.QUERY_PARAMETER)), resolveAccessTokenFromBody(exchange).map(s -> Tuples.of(s, TokenSource.BODY_PARAMETER))) .collectList() - .mapNotNull(tokenTuples -> switch (tokenTuples.size()) { - case 0 -> null; - case 1 -> getTokenIfSupported(tokenTuples.get(0), request); - default -> { - BearerTokenError error = invalidRequest(MULTIPLE_BEARER_TOKENS_ERROR_MSG); - throw new OAuth2AuthenticationException(error); + .mapNotNull(tokenTuples -> { + switch (tokenTuples.size()) { + case 0: + return null; + case 1: + return getTokenIfSupported(tokenTuples.get(0), request); + default: + BearerTokenError error = invalidRequest(MULTIPLE_BEARER_TOKENS_ERROR_MSG); + throw new OAuth2AuthenticationException(error); } }); } @@ -107,11 +110,16 @@ private static Mono resolveAccessTokenFromRequest(ServerHttpRequest requ } private String getTokenIfSupported(Tuple2 tokenTuple, ServerHttpRequest request) { - return switch (tokenTuple.getT2()) { - case HEADER -> tokenTuple.getT1(); - case QUERY_PARAMETER -> isParameterTokenSupportedForRequest(request) ? tokenTuple.getT1() : null; - case BODY_PARAMETER -> isBodyParameterTokenSupportedForRequest(request) ? tokenTuple.getT1() : null; - }; + switch (tokenTuple.getT2()) { + case HEADER: + return tokenTuple.getT1(); + case QUERY_PARAMETER: + return isParameterTokenSupportedForRequest(request) ? tokenTuple.getT1() : null; + case BODY_PARAMETER: + return isBodyParameterTokenSupportedForRequest(request) ? tokenTuple.getT1() : null; + default: + throw new IllegalArgumentException(); + } } /** @@ -185,7 +193,7 @@ private Mono resolveAccessTokenFromBody(ServerWebExchange exchange) { return null; } if (tokens.size() > 1) { - var error = invalidRequest(MULTIPLE_BEARER_TOKENS_ERROR_MSG); + BearerTokenError error = invalidRequest(MULTIPLE_BEARER_TOKENS_ERROR_MSG); throw new OAuth2AuthenticationException(error); } return formData.getFirst(ACCESS_TOKEN_NAME); diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverterTests.java index 01a667e5da8..ce0a75446eb 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverterTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverterTests.java @@ -222,8 +222,8 @@ void resolveWhenQueryParameterHasMultipleAccessTokensThenOAuth2AuthenticationExc @Test void resolveWhenBodyParameterIsPresentThenTokenIsResolved() { this.converter.setAllowFormEncodedBodyParameter(true); - var request = post("/").contentType(APPLICATION_FORM_URLENCODED) - .body("access_token=" + TEST_TOKEN); + MockServerHttpRequest request = post("/").contentType(APPLICATION_FORM_URLENCODED) + .body("access_token=" + TEST_TOKEN); assertThat(convertToToken(request).getToken()).isEqualTo(TEST_TOKEN); } @@ -232,8 +232,8 @@ void resolveWhenBodyParameterIsPresentThenTokenIsResolved() { @Test void resolveWhenBodyParameterIsPresentButNotAllowedThenTokenIsNotResolved() { this.converter.setAllowFormEncodedBodyParameter(false); - var request = post("/").contentType(APPLICATION_FORM_URLENCODED) - .body("access_token=" + TEST_TOKEN); + MockServerHttpRequest request = post("/").contentType(APPLICATION_FORM_URLENCODED) + .body("access_token=" + TEST_TOKEN); assertThat(convertToToken(request)).isNull(); } @@ -241,8 +241,8 @@ void resolveWhenBodyParameterIsPresentButNotAllowedThenTokenIsNotResolved() { @Test void resolveWhenBodyParameterHasMultipleAccessTokensThenOAuth2AuthenticationException() { this.converter.setAllowFormEncodedBodyParameter(true); - var request = post("/").contentType(APPLICATION_FORM_URLENCODED) - .body("access_token=" + TEST_TOKEN + "&access_token=" + TEST_TOKEN); + MockServerHttpRequest request = post("/").contentType(APPLICATION_FORM_URLENCODED) + .body("access_token=" + TEST_TOKEN + "&access_token=" + TEST_TOKEN); assertThatExceptionOfType(OAuth2AuthenticationException.class) .isThrownBy(() -> convertToToken(request)) @@ -258,8 +258,8 @@ void resolveWhenBodyParameterHasMultipleAccessTokensThenOAuth2AuthenticationExce @Test void resolveBodyContainsOtherParameterAsWellThenTokenIsResolved() { this.converter.setAllowFormEncodedBodyParameter(true); - var request = post("/").contentType(APPLICATION_FORM_URLENCODED) - .body("access_token=" + TEST_TOKEN + "&other_param=value"); + MockServerHttpRequest request = post("/").contentType(APPLICATION_FORM_URLENCODED) + .body("access_token=" + TEST_TOKEN + "&other_param=value"); assertThat(convertToToken(request).getToken()).isEqualTo(TEST_TOKEN); } @@ -267,7 +267,7 @@ void resolveBodyContainsOtherParameterAsWellThenTokenIsResolved() { @Test void resolveWhenNoBodyParameterThenTokenIsNotResolved() { this.converter.setAllowFormEncodedBodyParameter(true); - var request = post("/").contentType(APPLICATION_FORM_URLENCODED); + MockServerHttpRequest.BaseBuilder request = post("/").contentType(APPLICATION_FORM_URLENCODED); assertThat(convertToToken(request)).isNull(); } @@ -275,8 +275,8 @@ void resolveWhenNoBodyParameterThenTokenIsNotResolved() { @Test void resolveWhenWrongBodyParameterThenTokenIsNotResolved() { this.converter.setAllowFormEncodedBodyParameter(true); - var request = post("/").contentType(APPLICATION_FORM_URLENCODED) - .body("other_param=value"); + MockServerHttpRequest request = post("/").contentType(APPLICATION_FORM_URLENCODED) + .body("other_param=value"); assertThat(convertToToken(request)).isNull(); } @@ -284,9 +284,9 @@ void resolveWhenWrongBodyParameterThenTokenIsNotResolved() { @Test void resolveWhenValidHeaderIsPresentTogetherWithBodyParameterThenAuthenticationExceptionIsThrown() { this.converter.setAllowFormEncodedBodyParameter(true); - var request = post("/").header(AUTHORIZATION, "Bearer " + TEST_TOKEN) - .contentType(APPLICATION_FORM_URLENCODED) - .body("access_token=" + TEST_TOKEN); + MockServerHttpRequest request = post("/").header(AUTHORIZATION, "Bearer " + TEST_TOKEN) + .contentType(APPLICATION_FORM_URLENCODED) + .body("access_token=" + TEST_TOKEN); assertThatExceptionOfType(OAuth2AuthenticationException.class) .isThrownBy(() -> convertToToken(request)) @@ -297,9 +297,9 @@ void resolveWhenValidHeaderIsPresentTogetherWithBodyParameterThenAuthenticationE void resolveWhenValidQueryParameterIsPresentTogetherWithBodyParameterThenAuthenticationExceptionIsThrown() { this.converter.setAllowUriQueryParameter(true); this.converter.setAllowFormEncodedBodyParameter(true); - var request = post("/").queryParam("access_token", TEST_TOKEN) - .contentType(APPLICATION_FORM_URLENCODED) - .body("access_token=" + TEST_TOKEN); + MockServerHttpRequest request = post("/").queryParam("access_token", TEST_TOKEN) + .contentType(APPLICATION_FORM_URLENCODED) + .body("access_token=" + TEST_TOKEN); assertThatExceptionOfType(OAuth2AuthenticationException.class) .isThrownBy(() -> convertToToken(request)) @@ -310,10 +310,10 @@ void resolveWhenValidQueryParameterIsPresentTogetherWithBodyParameterThenAuthent void resolveWhenValidQueryParameterIsPresentTogetherWithBodyParameterAndValidHeaderThenAuthenticationExceptionIsThrown() { this.converter.setAllowUriQueryParameter(true); this.converter.setAllowFormEncodedBodyParameter(true); - var request = post("/").header(AUTHORIZATION, "Bearer " + TEST_TOKEN) - .queryParam("access_token", TEST_TOKEN) - .contentType(APPLICATION_FORM_URLENCODED) - .body("access_token=" + TEST_TOKEN); + MockServerHttpRequest request = post("/").header(AUTHORIZATION, "Bearer " + TEST_TOKEN) + .queryParam("access_token", TEST_TOKEN) + .contentType(APPLICATION_FORM_URLENCODED) + .body("access_token=" + TEST_TOKEN); assertThatExceptionOfType(OAuth2AuthenticationException.class) .isThrownBy(() -> convertToToken(request)) From 5e2b30419cd893abdb57cc15ac1f31d5ed2f8978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonah=20Kl=C3=B6ckner?= Date: Wed, 2 Oct 2024 17:11:44 +0200 Subject: [PATCH 4/6] important import order --- ...rverBearerTokenAuthenticationConverterTests.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverterTests.java index ce0a75446eb..33e06470b6a 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverterTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverterTests.java @@ -16,16 +16,11 @@ package org.springframework.security.oauth2.server.resource.web.server.authentication; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.springframework.http.HttpHeaders.AUTHORIZATION; -import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; -import static org.springframework.mock.http.server.reactive.MockServerHttpRequest.post; - import java.util.Base64; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; @@ -35,6 +30,12 @@ import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes; import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; +import static org.springframework.mock.http.server.reactive.MockServerHttpRequest.post; + /** * @author Rob Winch * @since 5.1 From 157267a0ce5354f2d9c886718da81fc996d021b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonah=20Kl=C3=B6ckner?= Date: Wed, 2 Oct 2024 17:20:14 +0200 Subject: [PATCH 5/6] replace left over vars for Java 8 compliance --- .../ServerBearerTokenAuthenticationConverter.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java index bf8adb5bf85..10c918a0c7a 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java @@ -76,7 +76,7 @@ public Mono convert(ServerWebExchange exchange) { } private Mono token(ServerWebExchange exchange) { - final var request = exchange.getRequest(); + final ServerHttpRequest request = exchange.getRequest(); return Flux.merge(resolveFromAuthorizationHeader(request.getHeaders()).map(s -> Tuples.of(s, TokenSource.HEADER)), resolveAccessTokenFromRequest(request).map(s -> Tuples.of(s, TokenSource.QUERY_PARAMETER)), @@ -179,7 +179,7 @@ private Mono resolveAccessTokenFromBody(ServerWebExchange exchange) { return Mono.empty(); } - final var request = exchange.getRequest(); + final ServerHttpRequest request = exchange.getRequest(); if (request.getMethod() == HttpMethod.POST && MediaType.APPLICATION_FORM_URLENCODED.equalsTypeAndSubtype(request.getHeaders().getContentType())) { @@ -188,7 +188,7 @@ private Mono resolveAccessTokenFromBody(ServerWebExchange exchange) { if (formData.isEmpty()) { return null; } - final var tokens = formData.get(ACCESS_TOKEN_NAME); + final List tokens = formData.get(ACCESS_TOKEN_NAME); if (tokens == null) { return null; } From 0b7b3ec47b5adf6125f15778f01f5035e65d585c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonah=20Kl=C3=B6ckner?= Date: Tue, 29 Oct 2024 14:36:39 +0100 Subject: [PATCH 6/6] Add missing javaDoc Issue gh-15818 --- .../authentication/ServerBearerTokenAuthenticationConverter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java index 10c918a0c7a..6918985523c 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java @@ -152,6 +152,7 @@ public void setBearerTokenHeaderName(String bearerTokenHeaderName) { * Defaults to {@code false}. * @param allowFormEncodedBodyParameter if the form-encoded body parameter is * supported + * @since 6.5 */ public void setAllowFormEncodedBodyParameter(boolean allowFormEncodedBodyParameter) { this.allowFormEncodedBodyParameter = allowFormEncodedBodyParameter;