Add PKCE OAuth2 client support

- Support has been added for "RFC7636: Proof Key for Code Exchange by OAuth Public Clients" (PKCE, pronounced "pixy") to mitigate against attacks targeting the interception of the authorization code
 - PkceParameterNames was added for the 3 additional parameters used by PKCE (i.e. code_verifier, code_challenge, and code_challenge_method)
 - Default code_verifier length has been set to 128 characters--the maximum allowed by RFC7636
 - ClientAuthenticationMethod.NONE was added to allow clients to request tokens without providing a client secret

Fixes gh-6446
This commit is contained in:
Stephen Doxsee 2019-02-08 10:49:41 -05:00 committed by Joe Grandja
parent 2b960b074b
commit 7739a0e91a
15 changed files with 445 additions and 59 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@ -23,6 +23,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriComponentsBuilder;
@ -74,11 +75,20 @@ public class OAuth2AuthorizationCodeGrantRequestEntityConverter implements Conve
MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
formParameters.add(OAuth2ParameterNames.GRANT_TYPE, authorizationCodeGrantRequest.getGrantType().getValue());
formParameters.add(OAuth2ParameterNames.CODE, authorizationExchange.getAuthorizationResponse().getCode());
formParameters.add(OAuth2ParameterNames.REDIRECT_URI, authorizationExchange.getAuthorizationRequest().getRedirectUri());
if (ClientAuthenticationMethod.POST.equals(clientRegistration.getClientAuthenticationMethod())) {
String redirectUri = authorizationExchange.getAuthorizationRequest().getRedirectUri();
String codeVerifier = authorizationExchange.getAuthorizationRequest().getAttribute(PkceParameterNames.CODE_VERIFIER);
if (redirectUri != null) {
formParameters.add(OAuth2ParameterNames.REDIRECT_URI, redirectUri);
}
if (!ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
formParameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
}
if (ClientAuthenticationMethod.POST.equals(clientRegistration.getClientAuthenticationMethod())) {
formParameters.add(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
}
if (codeVerifier != null) {
formParameters.add(PkceParameterNames.CODE_VERIFIER, codeVerifier);
}
return formParameters;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@ -18,9 +18,12 @@ package org.springframework.security.oauth2.client.endpoint;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.util.Assert;
@ -44,6 +47,7 @@ import static org.springframework.security.oauth2.core.web.reactive.function.OAu
* @see <a target="_blank" href="https://connect2id.com/products/nimbus-oauth-openid-connect-sdk">Nimbus OAuth 2.0 SDK</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request (Authorization Code Grant)</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response (Authorization Code Grant)</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.2">Section 4.2 Client Creates the Code Challenge</a>
*/
public class WebClientReactiveAuthorizationCodeTokenResponseClient implements ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
private WebClient webClient = WebClient.builder()
@ -63,12 +67,16 @@ public class WebClientReactiveAuthorizationCodeTokenResponseClient implements Re
ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration();
OAuth2AuthorizationExchange authorizationExchange = authorizationGrantRequest.getAuthorizationExchange();
String tokenUri = clientRegistration.getProviderDetails().getTokenUri();
BodyInserters.FormInserter<String> body = body(authorizationExchange);
BodyInserters.FormInserter<String> body = body(authorizationExchange, clientRegistration);
return this.webClient.post()
.uri(tokenUri)
.accept(MediaType.APPLICATION_JSON)
.headers(headers -> headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret()))
.headers(headers -> {
if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
}
})
.body(body)
.exchange()
.flatMap(response -> response.body(oauth2AccessTokenResponse()))
@ -83,14 +91,24 @@ public class WebClientReactiveAuthorizationCodeTokenResponseClient implements Re
});
}
private static BodyInserters.FormInserter<String> body(OAuth2AuthorizationExchange authorizationExchange) {
private static BodyInserters.FormInserter<String> body(OAuth2AuthorizationExchange authorizationExchange, ClientRegistration clientRegistration) {
OAuth2AuthorizationResponse authorizationResponse = authorizationExchange.getAuthorizationResponse();
String redirectUri = authorizationExchange.getAuthorizationRequest().getRedirectUri();
BodyInserters.FormInserter<String> body = BodyInserters
.fromFormData("grant_type", AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
.with("code", authorizationResponse.getCode());
.fromFormData(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
.with(OAuth2ParameterNames.CODE, authorizationResponse.getCode());
String redirectUri = authorizationExchange.getAuthorizationRequest().getRedirectUri();
String codeVerifier = authorizationExchange.getAuthorizationRequest().getAttribute(PkceParameterNames.CODE_VERIFIER);
if (redirectUri != null) {
body.with("redirect_uri", redirectUri);
body.with(OAuth2ParameterNames.REDIRECT_URI, redirectUri);
}
if (!ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
body.with(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
}
if (ClientAuthenticationMethod.POST.equals(clientRegistration.getClientAuthenticationMethod())) {
body.with(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
}
if (codeVerifier != null) {
body.with(PkceParameterNames.CODE_VERIFIER, codeVerifier);
}
return body;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@ -500,6 +500,11 @@ public final class ClientRegistration implements Serializable {
clientRegistration.clientId = this.clientId;
clientRegistration.clientSecret = StringUtils.hasText(this.clientSecret) ? this.clientSecret : "";
clientRegistration.clientAuthenticationMethod = this.clientAuthenticationMethod;
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType) &&
!StringUtils.hasText(this.clientSecret)) {
clientRegistration.clientAuthenticationMethod = ClientAuthenticationMethod.NONE;
}
clientRegistration.authorizationGrantType = this.authorizationGrantType;
clientRegistration.redirectUriTemplate = this.redirectUriTemplate;
clientRegistration.scopes = this.scopes;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@ -117,7 +117,10 @@ public final class ClientRegistrations {
if (metadataAuthMethods.contains(com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod.CLIENT_SECRET_POST)) {
return ClientAuthenticationMethod.POST;
}
throw new IllegalArgumentException("Only ClientAuthenticationMethod.BASIC and ClientAuthenticationMethod.POST are supported. The issuer \"" + issuer + "\" returned a configuration of " + metadataAuthMethods);
if (metadataAuthMethods.contains(com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod.NONE)) {
return ClientAuthenticationMethod.NONE;
}
throw new IllegalArgumentException("Only ClientAuthenticationMethod.BASIC, ClientAuthenticationMethod.POST and ClientAuthenticationMethod.NONE are supported. The issuer \"" + issuer + "\" returned a configuration of " + metadataAuthMethods);
}
private static List<String> getScopes(OIDCProviderMetadata metadata) {

View File

@ -20,14 +20,19 @@ import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
@ -52,6 +57,7 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
private final ClientRegistrationRepository clientRegistrationRepository;
private final AntPathRequestMatcher authorizationRequestMatcher;
private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
private final StringKeyGenerator codeVerifierGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
/**
* Constructs a {@code DefaultOAuth2AuthorizationRequestResolver} using the provided parameters.
@ -102,9 +108,17 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId);
}
Map<String, Object> attributes = new HashMap<>();
attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());
OAuth2AuthorizationRequest.Builder builder;
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
builder = OAuth2AuthorizationRequest.authorizationCode();
if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
Map<String, Object> additionalParameters = new HashMap<>();
addPkceParameters(attributes, additionalParameters);
builder.additionalParameters(additionalParameters);
}
} else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
builder = OAuth2AuthorizationRequest.implicit();
} else {
@ -115,9 +129,6 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
String redirectUriStr = this.expandRedirectUri(request, clientRegistration, redirectUriAction);
Map<String, Object> attributes = new HashMap<>();
attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());
OAuth2AuthorizationRequest authorizationRequest = builder
.clientId(clientRegistration.getClientId())
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
@ -156,4 +167,34 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
.buildAndExpand(uriVariables)
.toUriString();
}
/**
* Creates and adds additional PKCE parameters for use in the OAuth 2.0 Authorization and Access Token Requests
*
* @param attributes where {@link PkceParameterNames#CODE_VERIFIER} is stored for the token request
* @param additionalParameters where {@link PkceParameterNames#CODE_CHALLENGE} and, usually,
* {@link PkceParameterNames#CODE_CHALLENGE_METHOD} are added to be used in the authorization request.
*
* @since 5.2
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-1.1">1.1. Protocol Flow</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.1">4.1. Client Creates a Code Verifier</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.2">4.2. Client Creates the Code Challenge</a>
*/
private void addPkceParameters(Map<String, Object> attributes, Map<String, Object> additionalParameters) {
String codeVerifier = this.codeVerifierGenerator.generateKey();
attributes.put(PkceParameterNames.CODE_VERIFIER, codeVerifier);
try {
String codeChallenge = createCodeChallenge(codeVerifier);
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeChallenge);
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
} catch (NoSuchAlgorithmException e) {
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeVerifier);
}
}
private String createCodeChallenge(String codeVerifier) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII));
return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
}
}

View File

@ -24,8 +24,10 @@ import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.util.Assert;
@ -34,6 +36,9 @@ import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
@ -68,6 +73,8 @@ public class DefaultServerOAuth2AuthorizationRequestResolver
private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
private final StringKeyGenerator codeVerifierGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
/**
* Creates a new instance
* @param clientRegistrationRepository the repository to resolve the {@link ClientRegistration}
@ -124,6 +131,11 @@ public class DefaultServerOAuth2AuthorizationRequestResolver
OAuth2AuthorizationRequest.Builder builder;
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
builder = OAuth2AuthorizationRequest.authorizationCode();
if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
Map<String, Object> additionalParameters = new HashMap<>();
addPkceParameters(attributes, additionalParameters);
builder.additionalParameters(additionalParameters);
}
}
else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
builder = OAuth2AuthorizationRequest.implicit();
@ -164,4 +176,34 @@ public class DefaultServerOAuth2AuthorizationRequestResolver
.buildAndExpand(uriVariables)
.toUriString();
}
/**
* Creates and adds additional PKCE parameters for use in the OAuth 2.0 Authorization and Access Token Requests
*
* @param attributes where {@link PkceParameterNames#CODE_VERIFIER} is stored for the token request
* @param additionalParameters where {@link PkceParameterNames#CODE_CHALLENGE} and, usually,
* {@link PkceParameterNames#CODE_CHALLENGE_METHOD} are added to be used in the authorization request.
*
* @since 5.2
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-1.1">1.1. Protocol Flow</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.1">4.1. Client Creates a Code Verifier</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.2">4.2. Client Creates the Code Challenge</a>
*/
private void addPkceParameters(Map<String, Object> attributes, Map<String, Object> additionalParameters) {
String codeVerifier = this.codeVerifierGenerator.generateKey();
attributes.put(PkceParameterNames.CODE_VERIFIER, codeVerifier);
try {
String codeChallenge = createCodeChallenge(codeVerifier);
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeChallenge);
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
} catch (NoSuchAlgorithmException e) {
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeVerifier);
}
}
private String createCodeChallenge(String codeVerifier) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII));
return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@ -15,7 +15,6 @@
*/
package org.springframework.security.oauth2.client.endpoint;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@ -28,8 +27,14 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExch
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.util.MultiValueMap;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
@ -40,11 +45,8 @@ import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VAL
*/
public class OAuth2AuthorizationCodeGrantRequestEntityConverterTests {
private OAuth2AuthorizationCodeGrantRequestEntityConverter converter = new OAuth2AuthorizationCodeGrantRequestEntityConverter();
private OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest;
@Before
public void setup() {
ClientRegistration clientRegistration = ClientRegistration.withRegistrationId("registration-1")
private ClientRegistration.Builder clientRegistrationBuilder = ClientRegistration
.withRegistrationId("registration-1")
.clientId("client-1")
.clientSecret("secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
@ -55,33 +57,31 @@ public class OAuth2AuthorizationCodeGrantRequestEntityConverterTests {
.tokenUri("https://provider.com/oauth2/token")
.userInfoUri("https://provider.com/user")
.userNameAttributeName("id")
.clientName("client-1")
.build();
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest
.clientName("client-1");
private OAuth2AuthorizationRequest.Builder authorizationRequestBuilder = OAuth2AuthorizationRequest
.authorizationCode()
.clientId(clientRegistration.getClientId())
.clientId("client-1")
.state("state-1234")
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
.redirectUri(clientRegistration.getRedirectUriTemplate())
.scopes(clientRegistration.getScopes())
.build();
OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponse
.authorizationUri("https://provider.com/oauth2/authorize")
.redirectUri("https://client.com/callback/client-1")
.scopes(new HashSet(Arrays.asList("read", "write")));
private OAuth2AuthorizationResponse.Builder authorizationResponseBuilder = OAuth2AuthorizationResponse
.success("code-1234")
.state("state-1234")
.redirectUri(clientRegistration.getRedirectUriTemplate())
.build();
OAuth2AuthorizationExchange authorizationExchange =
new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse);
this.authorizationCodeGrantRequest = new OAuth2AuthorizationCodeGrantRequest(
clientRegistration, authorizationExchange);
}
.redirectUri("https://client.com/callback/client-1");
@SuppressWarnings("unchecked")
@Test
public void convertWhenGrantRequestValidThenConverts() {
RequestEntity<?> requestEntity = this.converter.convert(this.authorizationCodeGrantRequest);
ClientRegistration clientRegistration = clientRegistrationBuilder.build();
OAuth2AuthorizationRequest authorizationRequest = authorizationRequestBuilder.build();
OAuth2AuthorizationResponse authorizationResponse = authorizationResponseBuilder.build();
OAuth2AuthorizationExchange authorizationExchange =
new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse);
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest = new OAuth2AuthorizationCodeGrantRequest(
clientRegistration, authorizationExchange);
ClientRegistration clientRegistration = this.authorizationCodeGrantRequest.getClientRegistration();
RequestEntity<?> requestEntity = this.converter.convert(authorizationCodeGrantRequest);
assertThat(requestEntity.getMethod()).isEqualTo(HttpMethod.POST);
assertThat(requestEntity.getUrl().toASCIIString()).isEqualTo(
@ -97,7 +97,55 @@ public class OAuth2AuthorizationCodeGrantRequestEntityConverterTests {
assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE)).isEqualTo(
AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
assertThat(formParameters.getFirst(OAuth2ParameterNames.CODE)).isEqualTo("code-1234");
assertThat(formParameters.getFirst(OAuth2ParameterNames.CLIENT_ID)).isNull();
assertThat(formParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI)).isEqualTo(
clientRegistration.getRedirectUriTemplate());
}
@SuppressWarnings("unchecked")
@Test
public void convertWhenPkceGrantRequestValidThenConverts() {
ClientRegistration clientRegistration = clientRegistrationBuilder
.clientSecret(null)
.build();
Map<String, Object> attributes = new HashMap<>();
attributes.put(PkceParameterNames.CODE_VERIFIER, "code-verifier-1234");
Map<String, Object> additionalParameters = new HashMap<>();
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge-1234");
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
OAuth2AuthorizationRequest authorizationRequest = authorizationRequestBuilder
.attributes(attributes)
.additionalParameters(additionalParameters)
.build();
OAuth2AuthorizationResponse authorizationResponse = authorizationResponseBuilder.build();
OAuth2AuthorizationExchange authorizationExchange =
new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse);
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest = new OAuth2AuthorizationCodeGrantRequest(
clientRegistration, authorizationExchange);
RequestEntity<?> requestEntity = this.converter.convert(authorizationCodeGrantRequest);
assertThat(requestEntity.getMethod()).isEqualTo(HttpMethod.POST);
assertThat(requestEntity.getUrl().toASCIIString()).isEqualTo(
clientRegistration.getProviderDetails().getTokenUri());
HttpHeaders headers = requestEntity.getHeaders();
assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON_UTF8);
assertThat(headers.getContentType()).isEqualTo(
MediaType.valueOf(APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"));
assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isNull();
MultiValueMap<String, String> formParameters = (MultiValueMap<String, String>) requestEntity.getBody();
assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE)).isEqualTo(
AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
assertThat(formParameters.getFirst(OAuth2ParameterNames.CODE)).isEqualTo("code-1234");
assertThat(formParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI)).isEqualTo(
clientRegistration.getRedirectUriTemplate());
assertThat(formParameters.getFirst(OAuth2ParameterNames.CLIENT_ID)).isEqualTo("client-1");
assertThat(formParameters.getFirst(PkceParameterNames.CODE_VERIFIER)).isEqualTo("code-verifier-1234");
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@ -32,9 +32,12 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenRespon
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.web.reactive.function.client.WebClient;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@ -84,6 +87,9 @@ public class WebClientReactiveAuthorizationCodeTokenResponseClientTests {
Instant expiresAtBefore = Instant.now().plusSeconds(3600);
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(authorizationCodeGrantRequest()).block();
String body = this.server.takeRequest().getBody().readUtf8();
assertThat(body).isEqualTo("grant_type=authorization_code&code=code&redirect_uri=%7BbaseUrl%7D%2F%7Baction%7D%2Foauth2%2Fcode%2F%7BregistrationId%7D");
Instant expiresAtAfter = Instant.now().plusSeconds(3600);
@ -288,4 +294,51 @@ public class WebClientReactiveAuthorizationCodeTokenResponseClientTests {
verify(customClient, atLeastOnce()).post();
}
@Test
public void getTokenResponseWhenOAuth2AuthorizationRequestContainsPkceParametersThenTokenRequestBodyShouldContainCodeVerifier() throws Exception {
String accessTokenSuccessResponse = "{\n" +
" \"access_token\": \"access-token-1234\",\n" +
" \"token_type\": \"bearer\",\n" +
" \"expires_in\": \"3600\"\n" +
"}\n";
this.server.enqueue(jsonResponse(accessTokenSuccessResponse));
this.tokenResponseClient.getTokenResponse(pkceAuthorizationCodeGrantRequest()).block();
String body = this.server.takeRequest().getBody().readUtf8();
assertThat(body).isEqualTo("grant_type=authorization_code&code=code&redirect_uri=%7BbaseUrl%7D%2F%7Baction%7D%2Foauth2%2Fcode%2F%7BregistrationId%7D&client_id=client-id&code_verifier=code-verifier-1234");
}
private OAuth2AuthorizationCodeGrantRequest pkceAuthorizationCodeGrantRequest() {
ClientRegistration registration = this.clientRegistration
.clientSecret(null)
.build();
Map<String, Object> attributes = new HashMap<>();
attributes.put(PkceParameterNames.CODE_VERIFIER, "code-verifier-1234");
Map<String, Object> additionalParameters = new HashMap<>();
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge-1234");
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest
.authorizationCode()
.clientId(registration.getClientId())
.state("state")
.authorizationUri(registration.getProviderDetails().getAuthorizationUri())
.redirectUri(registration.getRedirectUriTemplate())
.scopes(registration.getScopes())
.attributes(attributes)
.additionalParameters(additionalParameters)
.build();
OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponse
.success("code")
.state("state")
.redirectUri(registration.getRedirectUriTemplate())
.build();
OAuth2AuthorizationExchange authorizationExchange = new OAuth2AuthorizationExchange(authorizationRequest,
authorizationResponse);
return new OAuth2AuthorizationCodeGrantRequest(registration, authorizationExchange);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@ -174,6 +174,41 @@ public class ClientRegistrationTests {
assertThat(clientRegistration.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.BASIC);
}
@Test
public void buildWhenAuthorizationCodeGrantClientAuthenticationMethodNotProvidedAndClientSecretNullThenDefaultToNone() {
ClientRegistration clientRegistration = ClientRegistration.withRegistrationId(REGISTRATION_ID)
.clientId(CLIENT_ID)
.clientSecret(null)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUriTemplate(REDIRECT_URI)
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(AUTHORIZATION_URI)
.tokenUri(TOKEN_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.jwkSetUri(JWK_SET_URI)
.clientName(CLIENT_NAME)
.build();
assertThat(clientRegistration.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.NONE);
}
@Test
public void buildWhenAuthorizationCodeGrantClientAuthenticationMethodNotProvidedAndClientSecretBlankThenDefaultToNone() {
ClientRegistration clientRegistration = ClientRegistration.withRegistrationId(REGISTRATION_ID)
.clientId(CLIENT_ID)
.clientSecret(" ")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUriTemplate(REDIRECT_URI)
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(AUTHORIZATION_URI)
.tokenUri(TOKEN_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.jwkSetUri(JWK_SET_URI)
.clientName(CLIENT_NAME)
.build();
assertThat(clientRegistration.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.NONE);
assertThat(clientRegistration.getClientSecret()).isEqualTo("");
}
@Test(expected = IllegalArgumentException.class)
public void buildWhenAuthorizationCodeGrantRedirectUriIsNullThenThrowIllegalArgumentException() {
ClientRegistration.withRegistrationId(REGISTRATION_ID)

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@ -92,7 +92,8 @@ public class ClientRegistrationsTest {
+ " \"token_endpoint\": \"https://example.com/oauth2/v4/token\", \n"
+ " \"token_endpoint_auth_methods_supported\": [\n"
+ " \"client_secret_post\", \n"
+ " \"client_secret_basic\"\n"
+ " \"client_secret_basic\", \n"
+ " \"none\"\n"
+ " ], \n"
+ " \"userinfo_endpoint\": \"https://example.com/oauth2/v3/userinfo\"\n"
+ "}";
@ -119,7 +120,7 @@ public class ClientRegistrationsTest {
@Test
public void issuerWhenAllInformationThenSuccess() throws Exception {
ClientRegistration registration = registration("");
ClientRegistration registration = registration("").build();
ClientRegistration.ProviderDetails provider = registration.getProviderDetails();
assertThat(registration.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.BASIC);
@ -154,7 +155,7 @@ public class ClientRegistrationsTest {
public void issuerWhenScopesNullThenScopesDefaulted() throws Exception {
this.response.remove("scopes_supported");
ClientRegistration registration = registration("");
ClientRegistration registration = registration("").build();
assertThat(registration.getScopes()).containsOnly("openid");
}
@ -163,7 +164,7 @@ public class ClientRegistrationsTest {
public void issuerWhenGrantTypesSupportedNullThenDefaulted() throws Exception {
this.response.remove("grant_types_supported");
ClientRegistration registration = registration("");
ClientRegistration registration = registration("").build();
assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
}
@ -184,7 +185,7 @@ public class ClientRegistrationsTest {
public void issuerWhenTokenEndpointAuthMethodsNullThenDefaulted() throws Exception {
this.response.remove("token_endpoint_auth_methods_supported");
ClientRegistration registration = registration("");
ClientRegistration registration = registration("").build();
assertThat(registration.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.BASIC);
}
@ -193,11 +194,20 @@ public class ClientRegistrationsTest {
public void issuerWhenTokenEndpointAuthMethodsPostThenMethodIsPost() throws Exception {
this.response.put("token_endpoint_auth_methods_supported", Arrays.asList("client_secret_post"));
ClientRegistration registration = registration("");
ClientRegistration registration = registration("").build();
assertThat(registration.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.POST);
}
@Test
public void issuerWhenTokenEndpointAuthMethodsNoneThenMethodIsNone() throws Exception {
this.response.put("token_endpoint_auth_methods_supported", Arrays.asList("none"));
ClientRegistration registration = registration("").build();
assertThat(registration.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.NONE);
}
/**
* We currently only support client_secret_basic, so verify we have a meaningful error until we add support.
* @throws Exception
@ -208,7 +218,7 @@ public class ClientRegistrationsTest {
assertThatThrownBy(() -> registration(""))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Only ClientAuthenticationMethod.BASIC and ClientAuthenticationMethod.POST are supported. The issuer \"" + this.issuer + "\" returned a configuration of [tls_client_auth]");
.hasMessageContaining("Only ClientAuthenticationMethod.BASIC, ClientAuthenticationMethod.POST and ClientAuthenticationMethod.NONE are supported. The issuer \"" + this.issuer + "\" returned a configuration of [tls_client_auth]");
}
@Test
@ -229,7 +239,7 @@ public class ClientRegistrationsTest {
.hasMessageContaining("The Issuer \"https://example.com\" provided in the OpenID Configuration did not match the requested issuer \"" + this.issuer + "\"");
}
private ClientRegistration registration(String path) throws Exception {
private ClientRegistration.Builder registration(String path) throws Exception {
this.issuer = createIssuerFromServer(path);
this.response.put("issuer", this.issuer);
String body = this.mapper.writeValueAsString(this.response);
@ -240,8 +250,7 @@ public class ClientRegistrationsTest {
return ClientRegistrations.fromOidcIssuerLocation(this.issuer)
.clientId("client-id")
.clientSecret("client-secret")
.build();
.clientSecret("client-secret");
}
private String createIssuerFromServer(String path) {

View File

@ -23,9 +23,11 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@ -39,6 +41,7 @@ import static org.assertj.core.api.Assertions.entry;
public class DefaultOAuth2AuthorizationRequestResolverTests {
private ClientRegistration registration1;
private ClientRegistration registration2;
private ClientRegistration pkceRegistration;
private ClientRegistrationRepository clientRegistrationRepository;
private String authorizationRequestBaseUri = "/oauth2/authorization";
private DefaultOAuth2AuthorizationRequestResolver resolver;
@ -47,8 +50,15 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
public void setUp() {
this.registration1 = TestClientRegistrations.clientRegistration().build();
this.registration2 = TestClientRegistrations.clientRegistration2().build();
this.pkceRegistration = TestClientRegistrations.clientRegistration()
.registrationId("pkce-client-registration-id")
.clientId("pkce-client-id")
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
.clientSecret(null)
.build();
this.clientRegistrationRepository = new InMemoryClientRegistrationRepository(
this.registration1, this.registration2);
this.registration1, this.registration2, this.pkceRegistration);
this.resolver = new DefaultOAuth2AuthorizationRequestResolver(
this.clientRegistrationRepository, this.authorizationRequestBaseUri);
}
@ -255,4 +265,40 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
"scope=read:user&state=.{15,}&" +
"redirect_uri=http://localhost/login/oauth2/code/registration-id-2");
}
@Test
public void resolveWhenAuthorizationRequestWithValidPkceClientThenResolves() {
ClientRegistration clientRegistration = this.pkceRegistration;
String requestUri = this.authorizationRequestBaseUri + "/" + clientRegistration.getRegistrationId();
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
request.setServletPath(requestUri);
OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
assertThat(authorizationRequest).isNotNull();
assertThat(authorizationRequest.getAuthorizationUri()).isEqualTo(
clientRegistration.getProviderDetails().getAuthorizationUri());
assertThat(authorizationRequest.getGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
assertThat(authorizationRequest.getResponseType()).isEqualTo(OAuth2AuthorizationResponseType.CODE);
assertThat(authorizationRequest.getClientId()).isEqualTo(clientRegistration.getClientId());
assertThat(authorizationRequest.getRedirectUri())
.isEqualTo("http://localhost/login/oauth2/code/" + clientRegistration.getRegistrationId());
assertThat(authorizationRequest.getScopes()).isEqualTo(clientRegistration.getScopes());
assertThat(authorizationRequest.getState()).isNotNull();
assertThat(authorizationRequest.getAdditionalParameters()).doesNotContainKey(OAuth2ParameterNames.REGISTRATION_ID);
assertThat(authorizationRequest.getAdditionalParameters()).containsKey(PkceParameterNames.CODE_CHALLENGE);
assertThat(authorizationRequest.getAdditionalParameters())
.contains(entry(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"));
assertThat(authorizationRequest.getAttributes())
.contains(entry(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()));
assertThat(authorizationRequest.getAttributes())
.containsKey(PkceParameterNames.CODE_VERIFIER);
assertThat((String) authorizationRequest.getAttribute(PkceParameterNames.CODE_VERIFIER)).matches("^([a-zA-Z0-9\\-\\.\\_\\~]){128}$");
assertThat(authorizationRequest.getAuthorizationRequestUri())
.matches("https://example.com/login/oauth/authorize\\?" +
"response_type=code&client_id=pkce-client-id&" +
"scope=read:user&state=.{15,}&" +
"redirect_uri=http://localhost/login/oauth2/code/pkce-client-registration-id&" +
"code_challenge_method=S256&" +
"code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@ -27,7 +27,9 @@ import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@ -87,4 +89,24 @@ public class DefaultServerOAuth2AuthorizationRequestResolverTests {
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(path));
return this.resolver.resolve(exchange).block();
}
@Test
public void resolveWhenAuthorizationRequestWithValidPkceClientThenResolves() {
when(this.clientRegistrationRepository.findByRegistrationId(any())).thenReturn(
Mono.just(TestClientRegistrations.clientRegistration()
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
.clientSecret(null)
.build()));
OAuth2AuthorizationRequest request = resolve("/oauth2/authorization/registration-id");
assertThat((String) request.getAttribute(PkceParameterNames.CODE_VERIFIER)).matches("^([a-zA-Z0-9\\-\\.\\_\\~]){128}$");
assertThat(request.getAuthorizationRequestUri()).matches("https://example.com/login/oauth/authorize\\?" +
"response_type=code&client_id=client-id&" +
"scope=read:user&state=.*?&" +
"redirect_uri=/login/oauth2/code/registration-id&" +
"code_challenge_method=S256&" +
"code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@ -31,6 +31,12 @@ public final class ClientAuthenticationMethod implements Serializable {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
public static final ClientAuthenticationMethod BASIC = new ClientAuthenticationMethod("basic");
public static final ClientAuthenticationMethod POST = new ClientAuthenticationMethod("post");
/**
* @since 5.2
*/
public static final ClientAuthenticationMethod NONE = new ClientAuthenticationMethod("none");
private final String value;
/**

View File

@ -0,0 +1,43 @@
/*
* Copyright 2002-2019 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.endpoint;
/**
* Standard parameter names defined in the OAuth Parameters Registry
* and used by the authorization endpoint and token endpoint.
*
* @author Stephen Doxsee
* @author Kevin Bolduc
* @since 5.2
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-6.1">6.1 OAuth Parameters Registry</a>
*/
public interface PkceParameterNames {
/**
* {@code code_challenge} - used in Authorization Request.
*/
String CODE_CHALLENGE = "code_challenge";
/**
* {@code code_challenge_method} - used in Authorization Request.
*/
String CODE_CHALLENGE_METHOD = "code_challenge_method";
/**
* {@code code_verifier} - used in Token Request.
*/
String CODE_VERIFIER = "code_verifier";
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2019 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.
@ -40,4 +40,9 @@ public class ClientAuthenticationMethodTests {
public void getValueWhenAuthenticationMethodPostThenReturnPost() {
assertThat(ClientAuthenticationMethod.POST.getValue()).isEqualTo("post");
}
@Test
public void getValueWhenAuthenticationMethodNoneThenReturnNone() {
assertThat(ClientAuthenticationMethod.NONE.getValue()).isEqualTo("none");
}
}