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:
parent
a4fdc28b27
commit
3c461b704c
|
@ -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;
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" +
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue