Skip to content

Commit edf5460

Browse files
author
Rujun Chen
authored
In Oauth2UserService, append authorities instead of override authorities (Azure#17838)
* In XxxOAuth2UserService, append authorities instead of override authorities.
1 parent 3986906 commit edf5460

File tree

6 files changed

+147
-177
lines changed

6 files changed

+147
-177
lines changed

sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/implementation/AzureActiveDirectoryOAuth2UserService.java

Lines changed: 22 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,6 @@
55

66
import com.azure.spring.autoconfigure.aad.AADAuthenticationProperties;
77
import com.azure.spring.autoconfigure.aad.AADTokenClaim;
8-
import com.azure.spring.autoconfigure.aad.JacksonObjectMapperFactory;
9-
import com.azure.spring.autoconfigure.aad.Membership;
10-
import com.azure.spring.autoconfigure.aad.Memberships;
11-
import com.fasterxml.jackson.databind.ObjectMapper;
12-
import com.nimbusds.oauth2.sdk.http.HTTPResponse;
13-
import org.slf4j.Logger;
14-
import org.slf4j.LoggerFactory;
15-
import org.springframework.http.HttpHeaders;
16-
import org.springframework.http.HttpMethod;
17-
import org.springframework.http.MediaType;
188
import org.springframework.security.core.GrantedAuthority;
199
import org.springframework.security.core.authority.SimpleGrantedAuthority;
2010
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
@@ -26,131 +16,63 @@
2616
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
2717
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
2818
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
19+
import org.springframework.util.StringUtils;
2920

30-
import java.io.BufferedReader;
31-
import java.io.IOException;
32-
import java.io.InputStreamReader;
33-
import java.net.HttpURLConnection;
34-
import java.net.URL;
35-
import java.nio.charset.StandardCharsets;
36-
import java.util.LinkedHashSet;
21+
import java.util.Collections;
3722
import java.util.Optional;
3823
import java.util.Set;
3924
import java.util.stream.Collectors;
4025

41-
import static com.azure.spring.autoconfigure.aad.Constants.DEFAULT_AUTHORITY_SET;
4226
import static com.azure.spring.autoconfigure.aad.Constants.ROLE_PREFIX;
4327

4428
/**
4529
* This implementation will retrieve group info of user from Microsoft Graph and map groups to {@link
4630
* GrantedAuthority}.
4731
*/
4832
public class AzureActiveDirectoryOAuth2UserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
49-
private static final Logger LOGGER = LoggerFactory.getLogger(AzureActiveDirectoryOAuth2UserService.class);
5033

5134
private final OidcUserService oidcUserService;
5235
private final AADAuthenticationProperties properties;
36+
private final GraphClient graphClient;
5337

5438
public AzureActiveDirectoryOAuth2UserService(
5539
AADAuthenticationProperties properties
5640
) {
5741
this.properties = properties;
5842
this.oidcUserService = new OidcUserService();
43+
this.graphClient = new GraphClient(properties);
5944
}
6045

6146
@Override
6247
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
6348
// Delegate to the default implementation for loading a user
6449
OidcUser oidcUser = oidcUserService.loadUser(userRequest);
65-
Set<SimpleGrantedAuthority> authorities =
66-
Optional.of(userRequest)
67-
.map(OAuth2UserRequest::getAccessToken)
68-
.map(AbstractOAuth2Token::getTokenValue)
69-
.map(this::getGroups)
70-
.map(this::toGrantedAuthoritySet)
71-
.filter(g -> !g.isEmpty())
72-
.orElse(DEFAULT_AUTHORITY_SET);
50+
Set<String> groups = Optional.of(userRequest)
51+
.map(OAuth2UserRequest::getAccessToken)
52+
.map(AbstractOAuth2Token::getTokenValue)
53+
.map(graphClient::getGroupsFromGraph)
54+
.orElseGet(Collections::emptySet);
55+
Set<String> groupRoles = groups.stream()
56+
.filter(properties::isAllowedGroup)
57+
.map(group -> ROLE_PREFIX + group)
58+
.collect(Collectors.toSet());
59+
Set<String> allRoles = oidcUser.getAuthorities()
60+
.stream()
61+
.map(GrantedAuthority::getAuthority)
62+
.collect(Collectors.toSet());
63+
allRoles.addAll(groupRoles);
64+
Set<SimpleGrantedAuthority> authorities = allRoles.stream()
65+
.map(SimpleGrantedAuthority::new)
66+
.collect(Collectors.toSet());
7367
String nameAttributeKey =
7468
Optional.of(userRequest)
7569
.map(OAuth2UserRequest::getClientRegistration)
7670
.map(ClientRegistration::getProviderDetails)
7771
.map(ClientRegistration.ProviderDetails::getUserInfoEndpoint)
7872
.map(ClientRegistration.ProviderDetails.UserInfoEndpoint::getUserNameAttributeName)
79-
.filter(s -> !s.isEmpty())
73+
.filter(StringUtils::hasText)
8074
.orElse(AADTokenClaim.NAME);
8175
// Create a copy of oidcUser but use the mappedAuthorities instead
8276
return new DefaultOidcUser(authorities, oidcUser.getIdToken(), nameAttributeKey);
8377
}
84-
85-
public Set<SimpleGrantedAuthority> toGrantedAuthoritySet(final Set<String> groups) {
86-
Set<SimpleGrantedAuthority> grantedAuthoritySet =
87-
groups.stream()
88-
.filter(properties::isAllowedGroup)
89-
.map(group -> new SimpleGrantedAuthority(ROLE_PREFIX + group))
90-
.collect(Collectors.toSet());
91-
return Optional.of(grantedAuthoritySet)
92-
.filter(g -> !g.isEmpty())
93-
.orElse(DEFAULT_AUTHORITY_SET);
94-
}
95-
96-
public Set<String> getGroups(String accessToken) {
97-
final Set<String> groups = new LinkedHashSet<>();
98-
final ObjectMapper objectMapper = JacksonObjectMapperFactory.getInstance();
99-
String aadMembershipRestUri = properties.getGraphMembershipUri();
100-
while (aadMembershipRestUri != null) {
101-
Memberships memberships;
102-
try {
103-
String membershipsJson = getUserMemberships(accessToken, aadMembershipRestUri);
104-
memberships = objectMapper.readValue(membershipsJson, Memberships.class);
105-
} catch (IOException ioException) {
106-
LOGGER.error("Can not get group information from graph server.", ioException);
107-
break;
108-
}
109-
memberships.getValue()
110-
.stream()
111-
.filter(this::isGroupObject)
112-
.map(Membership::getDisplayName)
113-
.forEach(groups::add);
114-
aadMembershipRestUri = Optional.of(memberships)
115-
.map(Memberships::getOdataNextLink)
116-
.orElse(null);
117-
}
118-
return groups;
119-
}
120-
121-
private String getUserMemberships(String accessToken, String urlString) throws IOException {
122-
URL url = new URL(urlString);
123-
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
124-
connection.setRequestMethod(HttpMethod.GET.toString());
125-
connection.setRequestProperty(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", accessToken));
126-
connection.setRequestProperty(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
127-
connection.setRequestProperty(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
128-
final String responseInJson = getResponseString(connection);
129-
final int responseCode = connection.getResponseCode();
130-
if (responseCode == HTTPResponse.SC_OK) {
131-
return responseInJson;
132-
} else {
133-
throw new IllegalStateException(
134-
"Response is not " + HTTPResponse.SC_OK + ", response json: " + responseInJson);
135-
}
136-
}
137-
138-
private String getResponseString(HttpURLConnection connection) throws IOException {
139-
try (BufferedReader reader =
140-
new BufferedReader(
141-
new InputStreamReader(connection.getInputStream(),
142-
StandardCharsets.UTF_8))
143-
) {
144-
final StringBuilder stringBuffer = new StringBuilder();
145-
String line;
146-
while ((line = reader.readLine()) != null) {
147-
stringBuffer.append(line);
148-
}
149-
return stringBuffer.toString();
150-
}
151-
}
152-
153-
private boolean isGroupObject(final Membership membership) {
154-
return membership.getObjectType().equals(properties.getUserGroup().getValue());
155-
}
15678
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.azure.spring.aad.implementation;
5+
6+
import com.azure.spring.autoconfigure.aad.AADAuthenticationProperties;
7+
import com.azure.spring.autoconfigure.aad.JacksonObjectMapperFactory;
8+
import com.azure.spring.autoconfigure.aad.Membership;
9+
import com.azure.spring.autoconfigure.aad.Memberships;
10+
import com.fasterxml.jackson.databind.ObjectMapper;
11+
import com.nimbusds.oauth2.sdk.http.HTTPResponse;
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
14+
import org.springframework.http.HttpHeaders;
15+
import org.springframework.http.HttpMethod;
16+
import org.springframework.http.MediaType;
17+
18+
import java.io.BufferedReader;
19+
import java.io.IOException;
20+
import java.io.InputStreamReader;
21+
import java.net.HttpURLConnection;
22+
import java.net.URL;
23+
import java.nio.charset.StandardCharsets;
24+
import java.util.LinkedHashSet;
25+
import java.util.Optional;
26+
import java.util.Set;
27+
28+
public class GraphClient {
29+
private static final Logger LOGGER = LoggerFactory.getLogger(GraphClient.class);
30+
31+
private final AADAuthenticationProperties properties;
32+
33+
public GraphClient(AADAuthenticationProperties properties) {
34+
this.properties = properties;
35+
}
36+
37+
public Set<String> getGroupsFromGraph(String accessToken) {
38+
final Set<String> groups = new LinkedHashSet<>();
39+
final ObjectMapper objectMapper = JacksonObjectMapperFactory.getInstance();
40+
String aadMembershipRestUri = properties.getGraphMembershipUri();
41+
while (aadMembershipRestUri != null) {
42+
Memberships memberships;
43+
try {
44+
String membershipsJson = getUserMemberships(accessToken, aadMembershipRestUri);
45+
memberships = objectMapper.readValue(membershipsJson, Memberships.class);
46+
} catch (IOException ioException) {
47+
LOGGER.error("Can not get group information from graph server.", ioException);
48+
break;
49+
}
50+
memberships.getValue()
51+
.stream()
52+
.filter(this::isGroupObject)
53+
.map(Membership::getDisplayName)
54+
.forEach(groups::add);
55+
aadMembershipRestUri = Optional.of(memberships)
56+
.map(Memberships::getOdataNextLink)
57+
.orElse(null);
58+
}
59+
return groups;
60+
}
61+
62+
private String getUserMemberships(String accessToken, String urlString) throws IOException {
63+
URL url = new URL(urlString);
64+
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
65+
connection.setRequestMethod(HttpMethod.GET.toString());
66+
connection.setRequestProperty(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", accessToken));
67+
connection.setRequestProperty(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
68+
connection.setRequestProperty(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
69+
final String responseInJson = getResponseString(connection);
70+
final int responseCode = connection.getResponseCode();
71+
if (responseCode == HTTPResponse.SC_OK) {
72+
return responseInJson;
73+
} else {
74+
throw new IllegalStateException(
75+
"Response is not " + HTTPResponse.SC_OK + ", response json: " + responseInJson);
76+
}
77+
}
78+
79+
private String getResponseString(HttpURLConnection connection) throws IOException {
80+
try (BufferedReader reader =
81+
new BufferedReader(
82+
new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)
83+
)
84+
) {
85+
final StringBuilder stringBuffer = new StringBuilder();
86+
String line;
87+
while ((line = reader.readLine()) != null) {
88+
stringBuffer.append(line);
89+
}
90+
return stringBuffer.toString();
91+
}
92+
}
93+
94+
private boolean isGroupObject(final Membership membership) {
95+
return membership.getObjectType().equals(properties.getUserGroup().getValue());
96+
}
97+
}

sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/aad/AADOAuth2UserService.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,19 @@
1515
import org.springframework.security.oauth2.core.OAuth2Error;
1616
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
1717
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
18+
import org.springframework.util.StringUtils;
1819

1920
import javax.naming.ServiceUnavailableException;
2021
import java.io.IOException;
2122
import java.net.MalformedURLException;
2223
import java.util.Optional;
2324
import java.util.Set;
25+
import java.util.stream.Collectors;
2426

2527
import static com.azure.spring.autoconfigure.aad.AADOAuth2ErrorCode.CONDITIONAL_ACCESS_POLICY;
2628
import static com.azure.spring.autoconfigure.aad.AADOAuth2ErrorCode.INVALID_REQUEST;
2729
import static com.azure.spring.autoconfigure.aad.AADOAuth2ErrorCode.SERVER_SERVER;
30+
import static com.azure.spring.autoconfigure.aad.Constants.ROLE_PREFIX;
2831

2932
/**
3033
* This implementation will retrieve group info of user from Microsoft Graph and map groups to {@link
@@ -46,7 +49,7 @@ public AADOAuth2UserService(AADAuthenticationProperties aadAuthenticationPropert
4649
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
4750
// Delegate to the default implementation for loading a user
4851
OidcUser oidcUser = oidcUserService.loadUser(userRequest);
49-
final Set<SimpleGrantedAuthority> mappedAuthorities;
52+
final Set<SimpleGrantedAuthority> authorities;
5053
try {
5154
// https://github.com/MicrosoftDocs/azure-docs/issues/8121#issuecomment-387090099
5255
// In AAD App Registration configure oauth2AllowImplicitFlow to true
@@ -63,7 +66,19 @@ public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2Authenticatio
6366
aadAuthenticationProperties.getTenantId()
6467
)
6568
.accessToken();
66-
mappedAuthorities = azureADGraphClient.getGrantedAuthorities(graphApiToken);
69+
Set<String> groups = azureADGraphClient.getGroups(graphApiToken);
70+
Set<String> groupRoles = groups.stream()
71+
.filter(aadAuthenticationProperties::isAllowedGroup)
72+
.map(group -> ROLE_PREFIX + group)
73+
.collect(Collectors.toSet());
74+
Set<String> allRoles = oidcUser.getAuthorities()
75+
.stream()
76+
.map(GrantedAuthority::getAuthority)
77+
.collect(Collectors.toSet());
78+
allRoles.addAll(groupRoles);
79+
authorities = allRoles.stream()
80+
.map(SimpleGrantedAuthority::new)
81+
.collect(Collectors.toSet());
6782
} catch (MalformedURLException e) {
6883
throw toOAuth2AuthenticationException(INVALID_REQUEST, "Failed to acquire token for Graph API.", e);
6984
} catch (ServiceUnavailableException e) {
@@ -85,10 +100,10 @@ public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2Authenticatio
85100
.map(ClientRegistration::getProviderDetails)
86101
.map(ClientRegistration.ProviderDetails::getUserInfoEndpoint)
87102
.map(ClientRegistration.ProviderDetails.UserInfoEndpoint::getUserNameAttributeName)
88-
.filter(s -> !s.isEmpty())
103+
.filter(StringUtils::hasText)
89104
.orElse(AADTokenClaim.NAME);
90105
// Create a copy of oidcUser but use the mappedAuthorities instead
91-
return new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), nameAttributeKey);
106+
return new DefaultOidcUser(authorities, oidcUser.getIdToken(), nameAttributeKey);
92107
}
93108

94109
private OAuth2AuthenticationException toOAuth2AuthenticationException(String errorCode,

sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/aad/AzureADGraphClient.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -146,15 +146,6 @@ private boolean isGroupObject(final Membership membership) {
146146
return membership.getObjectType().equals(aadAuthenticationProperties.getUserGroup().getValue());
147147
}
148148

149-
/**
150-
* @param graphApiToken token of graph api.
151-
* @return set of SimpleGrantedAuthority
152-
* @throws IOException throw exception if get groups failed by IOException.
153-
*/
154-
public Set<SimpleGrantedAuthority> getGrantedAuthorities(String graphApiToken) throws IOException {
155-
return toGrantedAuthoritySet(getGroups(graphApiToken));
156-
}
157-
158149
public Set<SimpleGrantedAuthority> toGrantedAuthoritySet(final Set<String> groups) {
159150
Set<SimpleGrantedAuthority> grantedAuthoritySet =
160151
groups.stream()

0 commit comments

Comments
 (0)