|
18 | 18 |
|
19 | 19 | import java.time.Instant;
|
20 | 20 | import java.util.HashMap;
|
21 |
| -import java.util.HashSet; |
22 | 21 | import java.util.Map;
|
23 |
| -import java.util.Set; |
| 22 | +import java.util.function.BiFunction; |
24 | 23 | import java.util.function.Function;
|
25 | 24 | import java.util.function.Predicate;
|
26 | 25 |
|
27 | 26 | import reactor.core.publisher.Mono;
|
28 | 27 |
|
29 | 28 | import org.springframework.core.convert.TypeDescriptor;
|
30 | 29 | import org.springframework.core.convert.converter.Converter;
|
31 |
| -import org.springframework.security.core.GrantedAuthority; |
32 | 30 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
33 | 31 | import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
34 | 32 | import org.springframework.security.oauth2.client.userinfo.DefaultReactiveOAuth2UserService;
|
|
40 | 38 | import org.springframework.security.oauth2.core.OAuth2Error;
|
41 | 39 | import org.springframework.security.oauth2.core.converter.ClaimConversionService;
|
42 | 40 | import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
|
| 41 | +import org.springframework.security.oauth2.core.oidc.OidcIdToken; |
43 | 42 | import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
44 | 43 | import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
|
45 | 44 | import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
46 | 45 | import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
47 | 46 | import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
|
48 | 47 | import org.springframework.security.oauth2.core.user.OAuth2User;
|
49 | 48 | import org.springframework.util.Assert;
|
50 |
| -import org.springframework.util.StringUtils; |
51 | 49 |
|
52 | 50 | /**
|
53 | 51 | * An implementation of an {@link ReactiveOAuth2UserService} that supports OpenID Connect
|
@@ -75,6 +73,8 @@ public class OidcReactiveOAuth2UserService implements ReactiveOAuth2UserService<
|
75 | 73 |
|
76 | 74 | private Predicate<OidcUserRequest> retrieveUserInfo = OidcUserRequestUtils::shouldRetrieveUserInfo;
|
77 | 75 |
|
| 76 | + private BiFunction<OidcUserRequest, OidcUserInfo, Mono<OidcUser>> oidcUserMapper = this::getUser; |
| 77 | + |
78 | 78 | /**
|
79 | 79 | * Returns the default {@link Converter}'s used for type conversion of claim values
|
80 | 80 | * for an {@link OidcUserInfo}.
|
@@ -103,29 +103,15 @@ public Mono<OidcUser> loadUser(OidcUserRequest userRequest) throws OAuth2Authent
|
103 | 103 | Assert.notNull(userRequest, "userRequest cannot be null");
|
104 | 104 | // @formatter:off
|
105 | 105 | return getUserInfo(userRequest)
|
106 |
| - .map((userInfo) -> |
107 |
| - new OidcUserAuthority(userRequest.getIdToken(), userInfo) |
108 |
| - ) |
109 |
| - .defaultIfEmpty(new OidcUserAuthority(userRequest.getIdToken(), null)) |
110 |
| - .map((authority) -> { |
111 |
| - OidcUserInfo userInfo = authority.getUserInfo(); |
112 |
| - Set<GrantedAuthority> authorities = new HashSet<>(); |
113 |
| - authorities.add(authority); |
114 |
| - OAuth2AccessToken token = userRequest.getAccessToken(); |
115 |
| - for (String scope : token.getScopes()) { |
116 |
| - authorities.add(new SimpleGrantedAuthority("SCOPE_" + scope)); |
117 |
| - } |
118 |
| - String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails() |
119 |
| - .getUserInfoEndpoint().getUserNameAttributeName(); |
120 |
| - if (StringUtils.hasText(userNameAttributeName)) { |
121 |
| - return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo, |
122 |
| - userNameAttributeName); |
123 |
| - } |
124 |
| - return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo); |
125 |
| - }); |
| 106 | + .flatMap((userInfo) -> this.oidcUserMapper.apply(userRequest, userInfo)) |
| 107 | + .switchIfEmpty(Mono.defer(() -> this.oidcUserMapper.apply(userRequest, null))); |
126 | 108 | // @formatter:on
|
127 | 109 | }
|
128 | 110 |
|
| 111 | + private Mono<OidcUser> getUser(OidcUserRequest userRequest, OidcUserInfo userInfo) { |
| 112 | + return Mono.just(OidcUserRequestUtils.getUser(userRequest, userInfo)); |
| 113 | + } |
| 114 | + |
129 | 115 | private Mono<OidcUserInfo> getUserInfo(OidcUserRequest userRequest) {
|
130 | 116 | if (!this.retrieveUserInfo.test(userRequest)) {
|
131 | 117 | return Mono.empty();
|
@@ -193,4 +179,60 @@ public final void setRetrieveUserInfo(Predicate<OidcUserRequest> retrieveUserInf
|
193 | 179 | this.retrieveUserInfo = retrieveUserInfo;
|
194 | 180 | }
|
195 | 181 |
|
| 182 | + /** |
| 183 | + * Sets the {@code BiFunction} used to map the {@link OidcUser user} from the |
| 184 | + * {@link OidcUserRequest user request} and {@link OidcUserInfo user info}. |
| 185 | + * <p> |
| 186 | + * This is useful when you need to map the user or authorities from the access token |
| 187 | + * itself. For example, when the authorization server provides authorization |
| 188 | + * information in the access token payload you can do the following: <pre> |
| 189 | + * @Bean |
| 190 | + * public OidcReactiveOAuth2UserService oidcUserService() { |
| 191 | + * var userService = new OidcReactiveOAuth2UserService(); |
| 192 | + * userService.setOidcUserMapper(oidcUserMapper()); |
| 193 | + * return userService; |
| 194 | + * } |
| 195 | + * |
| 196 | + * private static BiFunction<OidcUserRequest, OidcUserInfo, Mono<OidcUser>> oidcUserMapper() { |
| 197 | + * return (userRequest, userInfo) -> { |
| 198 | + * var accessToken = userRequest.getAccessToken(); |
| 199 | + * var grantedAuthorities = new HashSet<GrantedAuthority>(); |
| 200 | + * // TODO: Map authorities from the access token |
| 201 | + * var userNameAttributeName = "preferred_username"; |
| 202 | + * return Mono.just(new DefaultOidcUser( |
| 203 | + * grantedAuthorities, |
| 204 | + * userRequest.getIdToken(), |
| 205 | + * userInfo, |
| 206 | + * userNameAttributeName |
| 207 | + * )); |
| 208 | + * }; |
| 209 | + * } |
| 210 | + * </pre> |
| 211 | + * <p> |
| 212 | + * Note that you can access the {@code userNameAttributeName} via the |
| 213 | + * {@link ClientRegistration} as follows: <pre> |
| 214 | + * var userNameAttributeName = userRequest.getClientRegistration() |
| 215 | + * .getProviderDetails() |
| 216 | + * .getUserInfoEndpoint() |
| 217 | + * .getUserNameAttributeName(); |
| 218 | + * </pre> |
| 219 | + * <p> |
| 220 | + * By default, a {@link DefaultOidcUser} is created with authorities mapped as |
| 221 | + * follows: |
| 222 | + * <ul> |
| 223 | + * <li>An {@link OidcUserAuthority} is created from the {@link OidcIdToken} and |
| 224 | + * {@link OidcUserInfo} with an authority of {@code OIDC_USER}</li> |
| 225 | + * <li>Additional {@link SimpleGrantedAuthority authorities} are mapped from the |
| 226 | + * {@link OAuth2AccessToken#getScopes() access token scopes} with a prefix of |
| 227 | + * {@code SCOPE_}</li> |
| 228 | + * </ul> |
| 229 | + * @param oidcUserMapper the function used to map the {@link OidcUser} from the |
| 230 | + * {@link OidcUserRequest} and {@link OidcUserInfo} |
| 231 | + * @since 6.3 |
| 232 | + */ |
| 233 | + public final void setOidcUserMapper(BiFunction<OidcUserRequest, OidcUserInfo, Mono<OidcUser>> oidcUserMapper) { |
| 234 | + Assert.notNull(oidcUserMapper, "oidcUserMapper cannot be null"); |
| 235 | + this.oidcUserMapper = oidcUserMapper; |
| 236 | + } |
| 237 | + |
196 | 238 | }
|
0 commit comments