Add AuthenticationMethod type

This section defines three methods of sending bearer access tokens
in resource requests to resource servers.
Clients MUST NOT use more than
one method to transmit the token in each request.

RFC6750 Section 2 Authenticated Requests
https://tools.ietf.org/html/rfc6750#section-2

Add AuthenticationMethod in ClientRegistration UserInfoEndpoint.

Add AuthenticationMethod for OAuth2UserService to get User.

To support the use of the POST method.
https://tools.ietf.org/html/rfc6750#section-2.2

gh-5500
This commit is contained in:
mhyeon.lee 2018-07-11 23:52:00 +09:00 committed by Joe Grandja
parent a4fdc28b27
commit 3c461b704c
9 changed files with 394 additions and 6 deletions

View File

@ -15,6 +15,7 @@
*/
package org.springframework.security.oauth2.client.registration;
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
@ -197,6 +198,7 @@ public final class ClientRegistration {
*/
public class UserInfoEndpoint {
private String uri;
private AuthenticationMethod authenticationMethod = AuthenticationMethod.HEADER;
private String userNameAttributeName;
private UserInfoEndpoint() {
@ -211,6 +213,16 @@ public final class ClientRegistration {
return this.uri;
}
/**
* Returns the authentication method for the user info endpoint.
*
* @since 5.1
* @return the {@link AuthenticationMethod} for the user info endpoint.
*/
public AuthenticationMethod getAuthenticationMethod() {
return this.authenticationMethod;
}
/**
* Returns the attribute name used to access the user's name from the user info response.
*
@ -247,6 +259,7 @@ public final class ClientRegistration {
private String authorizationUri;
private String tokenUri;
private String userInfoUri;
private AuthenticationMethod userInfoAuthenticationMethod = AuthenticationMethod.HEADER;
private String userNameAttributeName;
private String jwkSetUri;
private String clientName;
@ -383,6 +396,18 @@ public final class ClientRegistration {
return this;
}
/**
* Sets the authentication method for the user info endpoint.
*
* @since 5.1
* @param userInfoAuthenticationMethod the authentication method for the user info endpoint
* @return the {@link Builder}
*/
public Builder userInfoAuthenticationMethod(AuthenticationMethod userInfoAuthenticationMethod) {
this.userInfoAuthenticationMethod = userInfoAuthenticationMethod;
return this;
}
/**
* Sets the attribute name used to access the user's name from the user info response.
*
@ -446,6 +471,7 @@ public final class ClientRegistration {
providerDetails.authorizationUri = this.authorizationUri;
providerDetails.tokenUri = this.tokenUri;
providerDetails.userInfoEndpoint.uri = this.userInfoUri;
providerDetails.userInfoEndpoint.authenticationMethod = this.userInfoAuthenticationMethod;
providerDetails.userInfoEndpoint.userNameAttributeName = this.userNameAttributeName;
providerDetails.jwkSetUri = this.jwkSetUri;
clientRegistration.providerDetails = providerDetails;

View File

@ -26,8 +26,10 @@ import java.util.Set;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
@ -99,9 +101,22 @@ public class DefaultReactiveOAuth2UserService implements ReactiveOAuth2UserServi
ParameterizedTypeReference<Map<String, Object>> typeReference = new ParameterizedTypeReference<Map<String, Object>>() {
};
Mono<Map<String, Object>> userAttributes = this.webClient.get()
.uri(userInfoUri)
.headers(bearerToken(userRequest.getAccessToken().getTokenValue()))
AuthenticationMethod authenticationMethod = userRequest.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getAuthenticationMethod();
WebClient.RequestHeadersSpec<?> requestHeadersSpec;
if (AuthenticationMethod.FORM.equals(authenticationMethod)) {
requestHeadersSpec = this.webClient.post()
.uri(userInfoUri)
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.syncBody("access_token=" + userRequest.getAccessToken().getTokenValue());
} else {
requestHeadersSpec = this.webClient.get()
.uri(userInfoUri)
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.headers(bearerToken(userRequest.getAccessToken().getTokenValue()));
}
Mono<Map<String, Object>> userAttributes = requestHeadersSpec
.retrieve()
.onStatus(s -> s != HttpStatus.OK, response -> {
return parse(response).map(userInfoErrorResponse -> {

View File

@ -32,6 +32,7 @@ import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
@ -79,8 +80,11 @@ final class NimbusUserInfoResponseClient {
OAuth2AccessToken oauth2AccessToken) throws OAuth2AuthenticationException {
URI userInfoUri = URI.create(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri());
BearerAccessToken accessToken = new BearerAccessToken(oauth2AccessToken.getTokenValue());
AuthenticationMethod authenticationMethod = clientRegistration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod();
HTTPRequest.Method httpMethod = AuthenticationMethod.FORM.equals(authenticationMethod)
? HTTPRequest.Method.POST : HTTPRequest.Method.GET;
UserInfoRequest userInfoRequest = new UserInfoRequest(userInfoUri, accessToken);
UserInfoRequest userInfoRequest = new UserInfoRequest(userInfoUri, httpMethod, accessToken);
HTTPRequest httpRequest = userInfoRequest.toHTTPRequest();
httpRequest.setAccept(MediaType.APPLICATION_JSON_VALUE);
httpRequest.setConnectTimeout(30000);

View File

@ -17,6 +17,8 @@ package org.springframework.security.oauth2.client.oidc.userinfo;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@ -26,9 +28,11 @@ import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
@ -56,7 +60,7 @@ import static org.mockito.Mockito.when;
*
* @author Joe Grandja
*/
@PowerMockIgnore("okhttp3.*")
@PowerMockIgnore({"okhttp3.*", "okio.Buffer"})
@PrepareForTest(ClientRegistration.class)
@RunWith(PowerMockRunner.class)
public class OidcUserServiceTests {
@ -79,6 +83,7 @@ public class OidcUserServiceTests {
when(this.providerDetails.getUserInfoEndpoint()).thenReturn(this.userInfoEndpoint);
when(this.clientRegistration.getAuthorizationGrantType()).thenReturn(AuthorizationGrantType.AUTHORIZATION_CODE);
when(this.userInfoEndpoint.getAuthenticationMethod()).thenReturn(AuthenticationMethod.HEADER);
when(this.userInfoEndpoint.getUserNameAttributeName()).thenReturn(StandardClaimNames.SUB);
this.accessToken = mock(OAuth2AccessToken.class);
@ -354,4 +359,71 @@ public class OidcUserServiceTests {
assertThat(server.takeRequest(1, TimeUnit.SECONDS).getHeader(HttpHeaders.ACCEPT))
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
}
// gh-5500
@Test
public void loadUserWhenAuthenticationMethodHeaderSuccessResponseThenHttpMethodGet() throws Exception {
MockWebServer server = new MockWebServer();
String userInfoResponse = "{\n" +
" \"sub\": \"subject1\",\n" +
" \"name\": \"first last\",\n" +
" \"given_name\": \"first\",\n" +
" \"family_name\": \"last\",\n" +
" \"preferred_username\": \"user1\",\n" +
" \"email\": \"user1@example.com\"\n" +
"}\n";
server.enqueue(new MockResponse()
.setHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.setBody(userInfoResponse));
server.start();
String userInfoUri = server.url("/user").toString();
when(this.userInfoEndpoint.getUri()).thenReturn(userInfoUri);
when(this.userInfoEndpoint.getAuthenticationMethod()).thenReturn(AuthenticationMethod.HEADER);
when(this.accessToken.getTokenValue()).thenReturn("access-token");
this.userService.loadUser(new OidcUserRequest(this.clientRegistration, this.accessToken, this.idToken));
server.shutdown();
RecordedRequest request = server.takeRequest();
assertThat(request.getMethod()).isEqualTo(HttpMethod.GET.name());
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
assertThat(request.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer " + this.accessToken.getTokenValue());
}
// gh-5500
@Test
public void loadUserWhenAuthenticationMethodFormSuccessResponseThenHttpMethodPost() throws Exception {
MockWebServer server = new MockWebServer();
String userInfoResponse = "{\n" +
" \"sub\": \"subject1\",\n" +
" \"name\": \"first last\",\n" +
" \"given_name\": \"first\",\n" +
" \"family_name\": \"last\",\n" +
" \"preferred_username\": \"user1\",\n" +
" \"email\": \"user1@example.com\"\n" +
"}\n";
server.enqueue(new MockResponse()
.setHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.setBody(userInfoResponse));
server.start();
String userInfoUri = server.url("/user").toString();
when(this.userInfoEndpoint.getUri()).thenReturn(userInfoUri);
when(this.userInfoEndpoint.getAuthenticationMethod()).thenReturn(AuthenticationMethod.FORM);
when(this.accessToken.getTokenValue()).thenReturn("access-token");
this.userService.loadUser(new OidcUserRequest(this.clientRegistration, this.accessToken, this.idToken));
server.shutdown();
RecordedRequest request = server.takeRequest();
assertThat(request.getMethod()).isEqualTo(HttpMethod.POST.name());
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
assertThat(request.getHeader(HttpHeaders.CONTENT_TYPE)).contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
assertThat(request.getBody().readUtf8()).isEqualTo("access_token=" + this.accessToken.getTokenValue());
}
}

View File

@ -16,6 +16,7 @@
package org.springframework.security.oauth2.client.registration;
import org.junit.Test;
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
@ -52,6 +53,7 @@ public class ClientRegistrationTests {
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(AUTHORIZATION_URI)
.tokenUri(TOKEN_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.jwkSetUri(JWK_SET_URI)
.clientName(CLIENT_NAME)
.build();
@ -68,6 +70,7 @@ public class ClientRegistrationTests {
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(AUTHORIZATION_URI)
.tokenUri(TOKEN_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.jwkSetUri(JWK_SET_URI)
.clientName(CLIENT_NAME)
.build();
@ -81,6 +84,7 @@ public class ClientRegistrationTests {
assertThat(registration.getScopes()).isEqualTo(SCOPES);
assertThat(registration.getProviderDetails().getAuthorizationUri()).isEqualTo(AUTHORIZATION_URI);
assertThat(registration.getProviderDetails().getTokenUri()).isEqualTo(TOKEN_URI);
assertThat(registration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod()).isEqualTo(AuthenticationMethod.FORM);
assertThat(registration.getProviderDetails().getJwkSetUri()).isEqualTo(JWK_SET_URI);
assertThat(registration.getClientName()).isEqualTo(CLIENT_NAME);
}
@ -96,6 +100,7 @@ public class ClientRegistrationTests {
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(AUTHORIZATION_URI)
.tokenUri(TOKEN_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.jwkSetUri(JWK_SET_URI)
.clientName(CLIENT_NAME)
.build();
@ -112,6 +117,7 @@ public class ClientRegistrationTests {
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(AUTHORIZATION_URI)
.tokenUri(TOKEN_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.jwkSetUri(JWK_SET_URI)
.clientName(CLIENT_NAME)
.build();
@ -128,6 +134,7 @@ public class ClientRegistrationTests {
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(AUTHORIZATION_URI)
.tokenUri(TOKEN_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.jwkSetUri(JWK_SET_URI)
.clientName(CLIENT_NAME)
.build();
@ -144,6 +151,7 @@ public class ClientRegistrationTests {
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(AUTHORIZATION_URI)
.tokenUri(TOKEN_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.jwkSetUri(JWK_SET_URI)
.clientName(CLIENT_NAME)
.build();
@ -160,6 +168,7 @@ public class ClientRegistrationTests {
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(AUTHORIZATION_URI)
.tokenUri(TOKEN_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.jwkSetUri(JWK_SET_URI)
.clientName(CLIENT_NAME)
.build();
@ -177,6 +186,7 @@ public class ClientRegistrationTests {
.scope((String[]) null)
.authorizationUri(AUTHORIZATION_URI)
.tokenUri(TOKEN_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.jwkSetUri(JWK_SET_URI)
.clientName(CLIENT_NAME)
.build();
@ -193,6 +203,7 @@ public class ClientRegistrationTests {
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(null)
.tokenUri(TOKEN_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.jwkSetUri(JWK_SET_URI)
.clientName(CLIENT_NAME)
.build();
@ -209,6 +220,7 @@ public class ClientRegistrationTests {
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(AUTHORIZATION_URI)
.tokenUri(null)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.jwkSetUri(JWK_SET_URI)
.clientName(CLIENT_NAME)
.build();
@ -225,6 +237,7 @@ public class ClientRegistrationTests {
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(AUTHORIZATION_URI)
.tokenUri(TOKEN_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.jwkSetUri(null)
.clientName(CLIENT_NAME)
.build();
@ -241,6 +254,7 @@ public class ClientRegistrationTests {
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(AUTHORIZATION_URI)
.tokenUri(TOKEN_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.jwkSetUri(JWK_SET_URI)
.clientName(null)
.build();
@ -256,6 +270,7 @@ public class ClientRegistrationTests {
.redirectUriTemplate(REDIRECT_URI)
.scope("scope1")
.authorizationUri(AUTHORIZATION_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.tokenUri(TOKEN_URI)
.clientName(CLIENT_NAME)
.build();
@ -284,6 +299,7 @@ public class ClientRegistrationTests {
.redirectUriTemplate(REDIRECT_URI)
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(AUTHORIZATION_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.clientName(CLIENT_NAME)
.build();
@ -293,6 +309,7 @@ public class ClientRegistrationTests {
assertThat(registration.getRedirectUriTemplate()).isEqualTo(REDIRECT_URI);
assertThat(registration.getScopes()).isEqualTo(SCOPES);
assertThat(registration.getProviderDetails().getAuthorizationUri()).isEqualTo(AUTHORIZATION_URI);
assertThat(registration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod()).isEqualTo(AuthenticationMethod.FORM);
assertThat(registration.getClientName()).isEqualTo(CLIENT_NAME);
}
@ -304,6 +321,7 @@ public class ClientRegistrationTests {
.redirectUriTemplate(REDIRECT_URI)
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(AUTHORIZATION_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.clientName(CLIENT_NAME)
.build();
}
@ -316,6 +334,7 @@ public class ClientRegistrationTests {
.redirectUriTemplate(REDIRECT_URI)
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(AUTHORIZATION_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.clientName(CLIENT_NAME)
.build();
}
@ -328,6 +347,7 @@ public class ClientRegistrationTests {
.redirectUriTemplate(null)
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(AUTHORIZATION_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.clientName(CLIENT_NAME)
.build();
}
@ -341,6 +361,7 @@ public class ClientRegistrationTests {
.redirectUriTemplate(REDIRECT_URI)
.scope((String[]) null)
.authorizationUri(AUTHORIZATION_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.clientName(CLIENT_NAME)
.build();
}
@ -353,6 +374,7 @@ public class ClientRegistrationTests {
.redirectUriTemplate(REDIRECT_URI)
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(null)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.clientName(CLIENT_NAME)
.build();
}
@ -365,6 +387,7 @@ public class ClientRegistrationTests {
.redirectUriTemplate(REDIRECT_URI)
.scope(SCOPES.toArray(new String[0]))
.authorizationUri(AUTHORIZATION_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.clientName(null)
.build();
}

View File

@ -17,6 +17,8 @@ package org.springframework.security.oauth2.client.userinfo;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@ -26,9 +28,11 @@ import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
@ -46,7 +50,7 @@ import static org.mockito.Mockito.when;
*
* @author Joe Grandja
*/
@PowerMockIgnore("okhttp3.*")
@PowerMockIgnore({"okhttp3.*", "okio.Buffer"})
@PrepareForTest(ClientRegistration.class)
@RunWith(PowerMockRunner.class)
public class DefaultOAuth2UserServiceTests {
@ -115,6 +119,7 @@ public class DefaultOAuth2UserServiceTests {
String userInfoUri = server.url("/user").toString();
when(this.userInfoEndpoint.getUri()).thenReturn(userInfoUri);
when(this.userInfoEndpoint.getAuthenticationMethod()).thenReturn(AuthenticationMethod.HEADER);
when(this.userInfoEndpoint.getUserNameAttributeName()).thenReturn("user-name");
when(this.accessToken.getTokenValue()).thenReturn("access-token");
@ -162,6 +167,7 @@ public class DefaultOAuth2UserServiceTests {
String userInfoUri = server.url("/user").toString();
when(this.userInfoEndpoint.getUri()).thenReturn(userInfoUri);
when(this.userInfoEndpoint.getAuthenticationMethod()).thenReturn(AuthenticationMethod.HEADER);
when(this.userInfoEndpoint.getUserNameAttributeName()).thenReturn("user-name");
when(this.accessToken.getTokenValue()).thenReturn("access-token");
@ -184,6 +190,7 @@ public class DefaultOAuth2UserServiceTests {
String userInfoUri = server.url("/user").toString();
when(this.userInfoEndpoint.getUri()).thenReturn(userInfoUri);
when(this.userInfoEndpoint.getAuthenticationMethod()).thenReturn(AuthenticationMethod.HEADER);
when(this.userInfoEndpoint.getUserNameAttributeName()).thenReturn("user-name");
when(this.accessToken.getTokenValue()).thenReturn("access-token");
@ -201,6 +208,7 @@ public class DefaultOAuth2UserServiceTests {
String userInfoUri = "http://invalid-provider.com/user";
when(this.userInfoEndpoint.getUri()).thenReturn(userInfoUri);
when(this.userInfoEndpoint.getAuthenticationMethod()).thenReturn(AuthenticationMethod.HEADER);
when(this.userInfoEndpoint.getUserNameAttributeName()).thenReturn("user-name");
when(this.accessToken.getTokenValue()).thenReturn("access-token");
@ -229,6 +237,7 @@ public class DefaultOAuth2UserServiceTests {
String userInfoUri = server.url("/user").toString();
when(this.userInfoEndpoint.getUri()).thenReturn(userInfoUri);
when(this.userInfoEndpoint.getAuthenticationMethod()).thenReturn(AuthenticationMethod.HEADER);
when(this.userInfoEndpoint.getUserNameAttributeName()).thenReturn("user-name");
when(this.accessToken.getTokenValue()).thenReturn("access-token");
@ -237,4 +246,73 @@ public class DefaultOAuth2UserServiceTests {
assertThat(server.takeRequest(1, TimeUnit.SECONDS).getHeader(HttpHeaders.ACCEPT))
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
}
// gh-5500
@Test
public void loadUserWhenAuthenticationMethodHeaderSuccessResponseThenHttpMethodGet() throws Exception {
MockWebServer server = new MockWebServer();
String userInfoResponse = "{\n" +
" \"user-name\": \"user1\",\n" +
" \"first-name\": \"first\",\n" +
" \"last-name\": \"last\",\n" +
" \"middle-name\": \"middle\",\n" +
" \"address\": \"address\",\n" +
" \"email\": \"user1@example.com\"\n" +
"}\n";
server.enqueue(new MockResponse()
.setHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.setBody(userInfoResponse));
server.start();
String userInfoUri = server.url("/user").toString();
when(this.userInfoEndpoint.getUri()).thenReturn(userInfoUri);
when(this.userInfoEndpoint.getAuthenticationMethod()).thenReturn(AuthenticationMethod.HEADER);
when(this.userInfoEndpoint.getUserNameAttributeName()).thenReturn("user-name");
when(this.accessToken.getTokenValue()).thenReturn("access-token");
this.userService.loadUser(new OAuth2UserRequest(this.clientRegistration, this.accessToken));
server.shutdown();
RecordedRequest request = server.takeRequest();
assertThat(request.getMethod()).isEqualTo(HttpMethod.GET.name());
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
assertThat(request.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer " + this.accessToken.getTokenValue());
}
// gh-5500
@Test
public void loadUserWhenAuthenticationMethodFormSuccessResponseThenHttpMethodPost() throws Exception {
MockWebServer server = new MockWebServer();
String userInfoResponse = "{\n" +
" \"user-name\": \"user1\",\n" +
" \"first-name\": \"first\",\n" +
" \"last-name\": \"last\",\n" +
" \"middle-name\": \"middle\",\n" +
" \"address\": \"address\",\n" +
" \"email\": \"user1@example.com\"\n" +
"}\n";
server.enqueue(new MockResponse()
.setHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.setBody(userInfoResponse));
server.start();
String userInfoUri = server.url("/user").toString();
when(this.userInfoEndpoint.getUri()).thenReturn(userInfoUri);
when(this.userInfoEndpoint.getAuthenticationMethod()).thenReturn(AuthenticationMethod.FORM);
when(this.userInfoEndpoint.getUserNameAttributeName()).thenReturn("user-name");
when(this.accessToken.getTokenValue()).thenReturn("access-token");
this.userService.loadUser(new OAuth2UserRequest(this.clientRegistration, this.accessToken));
server.shutdown();
RecordedRequest request = server.takeRequest();
assertThat(request.getMethod()).isEqualTo(HttpMethod.POST.name());
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
assertThat(request.getHeader(HttpHeaders.CONTENT_TYPE)).contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
assertThat(request.getBody().readUtf8()).isEqualTo("access_token=" + this.accessToken.getTokenValue());
}
}

View File

@ -22,15 +22,19 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import okhttp3.mockwebserver.RecordedRequest;
import reactor.test.StepVerifier;
import java.time.Duration;
@ -67,6 +71,7 @@ public class DefaultReactiveOAuth2UserServiceTests {
.authorizationUri("https://github.com/login/oauth/authorize")
.tokenUri("https://github.com/login/oauth/access_token")
.userInfoUri(userInfoUri)
.userInfoAuthenticationMethod(AuthenticationMethod.HEADER)
.userNameAttributeName("user-name")
.clientName("GitHub")
.clientId("clientId")
@ -140,6 +145,51 @@ public class DefaultReactiveOAuth2UserServiceTests {
assertThat(userAuthority.getAttributes()).isEqualTo(user.getAttributes());
}
// gh-5500
@Test
public void loadUserWhenAuthenticationMethodHeaderSuccessResponseThenHttpMethodGet() throws Exception {
this.clientRegistration.userInfoAuthenticationMethod(AuthenticationMethod.HEADER);
String userInfoResponse = "{\n" +
" \"user-name\": \"user1\",\n" +
" \"first-name\": \"first\",\n" +
" \"last-name\": \"last\",\n" +
" \"middle-name\": \"middle\",\n" +
" \"address\": \"address\",\n" +
" \"email\": \"user1@example.com\"\n" +
"}\n";
enqueueApplicationJsonBody(userInfoResponse);
this.userService.loadUser(oauth2UserRequest()).block();
RecordedRequest request = this.server.takeRequest();
assertThat(request.getMethod()).isEqualTo(HttpMethod.GET.name());
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
assertThat(request.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer " + this.accessToken.getTokenValue());
}
// gh-5500
@Test
public void loadUserWhenAuthenticationMethodFormSuccessResponseThenHttpMethodPost() throws Exception {
this.clientRegistration.userInfoAuthenticationMethod( AuthenticationMethod.FORM);
String userInfoResponse = "{\n" +
" \"user-name\": \"user1\",\n" +
" \"first-name\": \"first\",\n" +
" \"last-name\": \"last\",\n" +
" \"middle-name\": \"middle\",\n" +
" \"address\": \"address\",\n" +
" \"email\": \"user1@example.com\"\n" +
"}\n";
enqueueApplicationJsonBody(userInfoResponse);
this.userService.loadUser(oauth2UserRequest()).block();
RecordedRequest request = this.server.takeRequest();
assertThat(request.getMethod()).isEqualTo(HttpMethod.POST.name());
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
assertThat(request.getHeader(HttpHeaders.CONTENT_TYPE)).contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
assertThat(request.getBody().readUtf8()).isEqualTo("access_token=" + this.accessToken.getTokenValue());
}
@Test
public void loadUserWhenUserInfoSuccessResponseInvalidThenThrowOAuth2AuthenticationException() throws Exception {
String userInfoResponse = "{\n" +

View File

@ -0,0 +1,72 @@
/*
* Copyright 2002-2018 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;
import java.io.Serializable;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.util.Assert;
/**
* The authentication method used when sending bearer access tokens in resource requests to resource servers.
*
* @author MyeongHyeon Lee
* @since 5.1
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6750#section-2">Section 2 Authenticated Requests</a>
*/
public final class AuthenticationMethod implements Serializable {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
public static final AuthenticationMethod HEADER = new AuthenticationMethod("header");
public static final AuthenticationMethod FORM = new AuthenticationMethod("form");
public static final AuthenticationMethod QUERY = new AuthenticationMethod("query");
private final String value;
/**
* Constructs an {@code AuthenticationMethod} using the provided value.
*
* @param value the value of the authentication method type
*/
public AuthenticationMethod(String value) {
Assert.hasText(value, "value cannot be empty");
this.value = value;
}
/**
* Returns the value of the authentication method type.
*
* @return the value of the authentication method type
*/
public String getValue() {
return this.value;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
AuthenticationMethod that = (AuthenticationMethod) obj;
return this.getValue().equals(that.getValue());
}
@Override
public int hashCode() {
return this.getValue().hashCode();
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2002-2018 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;
import static org.assertj.core.api.Assertions.*;
import org.junit.Test;
/**
* Tests for {@link AuthenticationMethod}.
*
* @author MyeongHyeon Lee
*/
public class AuthenticationMethodTests {
@Test
public void constructorWhenValueIsNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> new AuthenticationMethod(null)).hasMessage("value cannot be empty");
}
@Test
public void getValueWhenHeaderAuthenticationTypeThenReturnHeader() {
assertThat(AuthenticationMethod.HEADER.getValue()).isEqualTo("header");
}
@Test
public void getValueWhenFormAuthenticationTypeThenReturnForm() {
assertThat(AuthenticationMethod.FORM.getValue()).isEqualTo("form");
}
@Test
public void getValueWhenFormAuthenticationTypeThenReturnQuery() {
assertThat(AuthenticationMethod.QUERY.getValue()).isEqualTo("query");
}
}