Skip to content

Grant Individual Authorities From Claims #7351

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.userinfo.DefaultReactiveOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
Expand Down Expand Up @@ -99,6 +101,10 @@ public Mono<OidcUser> loadUser(OidcUserRequest userRequest) throws OAuth2Authent
OidcUserInfo userInfo = authority.getUserInfo();
Set<GrantedAuthority> authorities = new HashSet<>();
authorities.add(authority);
OAuth2AccessToken token = userRequest.getAccessToken();
for (String scope : token.getScopes()) {
authorities.add(new SimpleGrantedAuthority("SCOPE_" + scope));
}
String userNameAttributeName = userRequest.getClientRegistration()
.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
if (StringUtils.hasText(userNameAttributeName)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,25 @@
*/
package org.springframework.security.oauth2.client.oidc.userinfo;

import java.time.Instant;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
Expand All @@ -38,15 +49,6 @@
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

/**
* An implementation of an {@link OAuth2UserService} that supports OpenID Connect 1.0 Provider's.
*
Expand Down Expand Up @@ -127,8 +129,12 @@ public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2Authenticatio
}
}

Set<GrantedAuthority> authorities = Collections.singleton(
new OidcUserAuthority(userRequest.getIdToken(), userInfo));
Set<GrantedAuthority> authorities = new LinkedHashSet<>();
authorities.add(new OidcUserAuthority(userRequest.getIdToken(), userInfo));
OAuth2AccessToken token = userRequest.getAccessToken();
for (String authority : token.getScopes()) {
authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));
}

OidcUser user;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@
*/
package org.springframework.security.oauth2.client.userinfo;

import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
Expand All @@ -35,10 +41,6 @@
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;

import java.util.Collections;
import java.util.Map;
import java.util.Set;

/**
* An implementation of an {@link OAuth2UserService} that supports standard OAuth 2.0 Provider's.
* <p>
Expand Down Expand Up @@ -127,7 +129,12 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic
}

Map<String, Object> userAttributes = response.getBody();
Set<GrantedAuthority> authorities = Collections.singleton(new OAuth2UserAuthority(userAttributes));
Set<GrantedAuthority> authorities = new LinkedHashSet<>();
authorities.add(new OAuth2UserAuthority(userAttributes));
OAuth2AccessToken token = userRequest.getAccessToken();
for (String authority : token.getScopes()) {
authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the logic from JwtGrantedAuthoritiesConverter should be replicated here. I believe the additional default authorities can be simplified like this:

Current authority: OAuth2UserAuthority -> ROLE_USER

Additional authorities: OAuth2UserRequest.OAuth2AccessToken.scopes e.g. SCOPE_profile, SCOPE_email

** The assumption is that OAuth2UserRequest.OAuth2AccessToken.scopes contains profile and email, meaning the user authorized the client to access those scopes during the authorization approval step. NOTE: These scopes would be initially configured with the ClientRegistration.scopes.

}

return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
Expand Down Expand Up @@ -131,6 +133,10 @@ public Mono<OAuth2User> loadUser(OAuth2UserRequest userRequest)
GrantedAuthority authority = new OAuth2UserAuthority(attrs);
Set<GrantedAuthority> authorities = new HashSet<>();
authorities.add(authority);
OAuth2AccessToken token = userRequest.getAccessToken();
for (String scope : token.getScopes()) {
authorities.add(new SimpleGrantedAuthority("SCOPE_" + scope));
}

return new DefaultOAuth2User(authorities, attrs, userNameAttributeName);
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,25 @@

package org.springframework.security.oauth2.client.oidc.userinfo;

import java.time.Duration;
import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.function.Function;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import reactor.core.publisher.Mono;

import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
Expand All @@ -36,17 +48,20 @@
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.security.oauth2.client.registration.TestClientRegistrations.clientRegistration;
import static org.springframework.security.oauth2.core.TestOAuth2AccessTokens.noScopes;
import static org.springframework.security.oauth2.core.TestOAuth2AccessTokens.scopes;
import static org.springframework.security.oauth2.core.oidc.TestOidcIdTokens.idToken;

/**
* @author Rob Winch
Expand Down Expand Up @@ -178,6 +193,38 @@ public void loadUserWhenCustomClaimTypeConverterFactorySetThenApplied() {
verify(customClaimTypeConverterFactory).apply(same(userRequest.getClientRegistration()));
}

@Test
public void loadUserWhenTokenContainsScopesThenIndividualScopeAuthorities() {
Map<String, Object> body = new HashMap<>();
body.put("id", "id");
body.put("sub", "test-subject");
OidcReactiveOAuth2UserService userService = new OidcReactiveOAuth2UserService();
OidcUserRequest request = new OidcUserRequest(
clientRegistration().build(), scopes("message:read", "message:write"), idToken(body));
OidcUser user = userService.loadUser(request).block();

assertThat(user.getAuthorities()).hasSize(3);
Iterator<? extends GrantedAuthority> authorities = user.getAuthorities().iterator();
assertThat(authorities.next()).isInstanceOf(OAuth2UserAuthority.class);
assertThat(authorities.next()).isEqualTo(new SimpleGrantedAuthority("SCOPE_message:read"));
assertThat(authorities.next()).isEqualTo(new SimpleGrantedAuthority("SCOPE_message:write"));
}

@Test
public void loadUserWhenTokenDoesNotContainScopesThenNoScopeAuthorities() {
Map<String, Object> body = new HashMap<>();
body.put("id", "id");
body.put("sub", "test-subject");
OidcReactiveOAuth2UserService userService = new OidcReactiveOAuth2UserService();
OidcUserRequest request = new OidcUserRequest(
clientRegistration().build(), noScopes(), idToken(body));
OidcUser user = userService.loadUser(request).block();

assertThat(user.getAuthorities()).hasSize(1);
Iterator<? extends GrantedAuthority> authorities = user.getAuthorities().iterator();
assertThat(authorities.next()).isInstanceOf(OAuth2UserAuthority.class);
}

private OidcUserRequest userRequest() {
return new OidcUserRequest(this.registration.build(), this.accessToken, this.idToken);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
*/
package org.springframework.security.oauth2.client.oidc.userinfo;

import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
Expand All @@ -23,10 +31,13 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.core.AuthenticationMethod;
Expand All @@ -40,19 +51,17 @@
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;

import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.security.oauth2.client.registration.TestClientRegistrations.clientRegistration;
import static org.springframework.security.oauth2.core.TestOAuth2AccessTokens.noScopes;
import static org.springframework.security.oauth2.core.TestOAuth2AccessTokens.scopes;
import static org.springframework.security.oauth2.core.oidc.TestOidcIdTokens.idToken;

/**
* Tests for {@link OidcUserService}.
Expand Down Expand Up @@ -255,7 +264,7 @@ public void loadUserWhenUserInfoSuccessResponseThenReturnUser() {
assertThat(user.getUserInfo().getPreferredUsername()).isEqualTo("user1");
assertThat(user.getUserInfo().getEmail()).isEqualTo("[email protected]");

assertThat(user.getAuthorities().size()).isEqualTo(1);
assertThat(user.getAuthorities().size()).isEqualTo(3);
assertThat(user.getAuthorities().iterator().next()).isInstanceOf(OidcUserAuthority.class);
OidcUserAuthority userAuthority = (OidcUserAuthority) user.getAuthorities().iterator().next();
assertThat(userAuthority.getAuthority()).isEqualTo("ROLE_USER");
Expand Down Expand Up @@ -481,6 +490,38 @@ public void loadUserWhenCustomClaimTypeConverterFactorySetThenApplied() {
verify(customClaimTypeConverterFactory).apply(same(clientRegistration));
}

@Test
public void loadUserWhenTokenContainsScopesThenIndividualScopeAuthorities() {
Map<String, Object> body = new HashMap<>();
body.put("id", "id");
body.put("sub", "test-subject");
OidcUserService userService = new OidcUserService();
OidcUserRequest request = new OidcUserRequest(clientRegistration().build(),
scopes("message:read", "message:write"), idToken(body));
OidcUser user = userService.loadUser(request);

assertThat(user.getAuthorities()).hasSize(3);
Iterator<? extends GrantedAuthority> authorities = user.getAuthorities().iterator();
assertThat(authorities.next()).isInstanceOf(OidcUserAuthority.class);
assertThat(authorities.next()).isEqualTo(new SimpleGrantedAuthority("SCOPE_message:read"));
assertThat(authorities.next()).isEqualTo(new SimpleGrantedAuthority("SCOPE_message:write"));
}

@Test
public void loadUserWhenTokenDoesNotContainScopesThenNoScopeAuthorities() {
Map<String, Object> body = new HashMap<>();
body.put("id", "id");
body.put("sub", "test-subject");
OidcUserService userService = new OidcUserService();
OidcUserRequest request = new OidcUserRequest(clientRegistration().build(),
noScopes(), idToken(body));
OidcUser user = userService.loadUser(request);

assertThat(user.getAuthorities()).hasSize(1);
Iterator<? extends GrantedAuthority> authorities = user.getAuthorities().iterator();
assertThat(authorities.next()).isInstanceOf(OidcUserAuthority.class);
}

private MockResponse jsonResponse(String json) {
return new MockResponse()
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
Expand Down
Loading