Skip to content

Commit 9b41b71

Browse files
author
Steve Riesenberg
committed
PR changes, round 2
1 parent 548699d commit 9b41b71

File tree

3 files changed

+182
-70
lines changed

3 files changed

+182
-70
lines changed

oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcUserInfoTests.java

Lines changed: 101 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
import java.time.Instant;
1919
import java.util.Arrays;
2020
import java.util.HashSet;
21-
import java.util.List;
2221
import java.util.Set;
22+
import java.util.function.Function;
2323

2424
import com.nimbusds.jose.jwk.JWKSet;
2525
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
@@ -37,10 +37,10 @@
3737
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
3838
import org.springframework.security.config.test.SpringTestRule;
3939
import org.springframework.security.oauth2.core.OAuth2AccessToken;
40+
import org.springframework.security.oauth2.core.authentication.OAuth2AuthenticationContext;
4041
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
4142
import org.springframework.security.oauth2.core.oidc.OidcScopes;
4243
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
43-
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
4444
import org.springframework.security.oauth2.jose.TestJwks;
4545
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
4646
import org.springframework.security.oauth2.jwt.JoseHeader;
@@ -57,10 +57,14 @@
5757
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
5858
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
5959
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
60+
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
61+
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
6062
import org.springframework.security.web.SecurityFilterChain;
6163
import org.springframework.security.web.util.matcher.RequestMatcher;
6264
import org.springframework.test.web.servlet.MockMvc;
65+
import org.springframework.test.web.servlet.ResultMatcher;
6366

67+
import static org.springframework.test.web.servlet.ResultMatcher.matchAll;
6468
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
6569
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
6670
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
@@ -73,9 +77,6 @@
7377
*/
7478
public class OidcUserInfoTests {
7579
private static final String DEFAULT_OIDC_USER_INFO_ENDPOINT_URI = "/userinfo";
76-
private static final List<String> OPENID_USER_INFO_SCOPES = Arrays.asList(
77-
OidcScopes.OPENID, OidcScopes.ADDRESS, OidcScopes.EMAIL, OidcScopes.PHONE, OidcScopes.PROFILE
78-
);
7980

8081
@Rule
8182
public final SpringTestRule spring = new SpringTestRule();
@@ -96,30 +97,12 @@ public void requestWhenUserInfoRequestGetThenUserInfoResponse() throws Exception
9697
OAuth2Authorization authorization = createAuthorization();
9798
this.authorizationService.save(authorization);
9899

100+
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
99101
// @formatter:off
100102
this.mvc.perform(get(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI)
101-
.header(HttpHeaders.AUTHORIZATION, "Bearer " + authorization.getAccessToken().getToken().getTokenValue()))
103+
.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue()))
102104
.andExpect(status().is2xxSuccessful())
103-
.andExpect(jsonPath("sub").value("user1"))
104-
.andExpect(jsonPath("name").value("First Last"))
105-
.andExpect(jsonPath("given_name").value("First"))
106-
.andExpect(jsonPath("family_name").value("Last"))
107-
.andExpect(jsonPath("middle_name").value("Middle"))
108-
.andExpect(jsonPath("nickname").value("User"))
109-
.andExpect(jsonPath("preferred_username").value("user"))
110-
.andExpect(jsonPath("profile").value("https://example.com/user1"))
111-
.andExpect(jsonPath("picture").value("https://example.com/user1.jpg"))
112-
.andExpect(jsonPath("website").value("https://example.com"))
113-
.andExpect(jsonPath("email").value("user1@example.com"))
114-
.andExpect(jsonPath("email_verified").value("true"))
115-
.andExpect(jsonPath("gender").value("female"))
116-
.andExpect(jsonPath("birthdate").value("1970-01-01"))
117-
.andExpect(jsonPath("zoneinfo").value("Europe/Paris"))
118-
.andExpect(jsonPath("locale").value("en-US"))
119-
.andExpect(jsonPath("phone_number").value("+1 (604) 555-1234;ext=5678"))
120-
.andExpect(jsonPath("phone_number_verified").value("false"))
121-
.andExpect(jsonPath("address").value("Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance"))
122-
.andExpect(jsonPath("updated_at").value("1970-01-01T00:00:00Z"));
105+
.andExpect(userInfoResponse());
123106
// @formatter:on
124107
}
125108

@@ -130,40 +113,70 @@ public void requestWhenUserInfoRequestPostThenUserInfoResponse() throws Exceptio
130113
OAuth2Authorization authorization = createAuthorization();
131114
this.authorizationService.save(authorization);
132115

116+
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
133117
// @formatter:off
134118
this.mvc.perform(post(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI)
135-
.header(HttpHeaders.AUTHORIZATION, "Bearer " + authorization.getAccessToken().getToken().getTokenValue()))
119+
.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue()))
136120
.andExpect(status().is2xxSuccessful())
137-
.andExpect(jsonPath("sub").value("user1"))
138-
.andExpect(jsonPath("name").value("First Last"))
139-
.andExpect(jsonPath("given_name").value("First"))
140-
.andExpect(jsonPath("family_name").value("Last"))
141-
.andExpect(jsonPath("middle_name").value("Middle"))
142-
.andExpect(jsonPath("nickname").value("User"))
143-
.andExpect(jsonPath("preferred_username").value("user"))
144-
.andExpect(jsonPath("profile").value("https://example.com/user1"))
145-
.andExpect(jsonPath("picture").value("https://example.com/user1.jpg"))
146-
.andExpect(jsonPath("website").value("https://example.com"))
147-
.andExpect(jsonPath("email").value("user1@example.com"))
148-
.andExpect(jsonPath("email_verified").value("true"))
149-
.andExpect(jsonPath("gender").value("female"))
150-
.andExpect(jsonPath("birthdate").value("1970-01-01"))
151-
.andExpect(jsonPath("zoneinfo").value("Europe/Paris"))
152-
.andExpect(jsonPath("locale").value("en-US"))
153-
.andExpect(jsonPath("phone_number").value("+1 (604) 555-1234;ext=5678"))
154-
.andExpect(jsonPath("phone_number_verified").value("false"))
155-
.andExpect(jsonPath("address").value("Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance"))
156-
.andExpect(jsonPath("updated_at").value("1970-01-01T00:00:00Z"));
121+
.andExpect(userInfoResponse());
122+
// @formatter:on
123+
}
124+
125+
@Test
126+
public void requestWhenSignedJwtAndCustomUserInfoMapperThenUserInfoResponse() throws Exception {
127+
this.spring.register(CustomUserInfoConfiguration.class).autowire();
128+
129+
OAuth2Authorization authorization = createAuthorization();
130+
this.authorizationService.save(authorization);
131+
132+
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
133+
// @formatter:off
134+
this.mvc.perform(get(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI)
135+
.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue()))
136+
.andExpect(status().is2xxSuccessful())
137+
.andExpect(userInfoResponse());
138+
// @formatter:on
139+
}
140+
141+
private static ResultMatcher userInfoResponse() {
142+
// @formatter:off
143+
return matchAll(
144+
jsonPath("sub").value("user1"),
145+
jsonPath("name").value("First Last"),
146+
jsonPath("given_name").value("First"),
147+
jsonPath("family_name").value("Last"),
148+
jsonPath("middle_name").value("Middle"),
149+
jsonPath("nickname").value("User"),
150+
jsonPath("preferred_username").value("user"),
151+
jsonPath("profile").value("https://example.com/user1"),
152+
jsonPath("picture").value("https://example.com/user1.jpg"),
153+
jsonPath("website").value("https://example.com"),
154+
jsonPath("email").value("user1@example.com"),
155+
jsonPath("email_verified").value("true"),
156+
jsonPath("gender").value("female"),
157+
jsonPath("birthdate").value("1970-01-01"),
158+
jsonPath("zoneinfo").value("Europe/Paris"),
159+
jsonPath("locale").value("en-US"),
160+
jsonPath("phone_number").value("+1 (604) 555-1234;ext=5678"),
161+
jsonPath("phone_number_verified").value("false"),
162+
jsonPath("address").value("Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance"),
163+
jsonPath("updated_at").value("1970-01-01T00:00:00Z")
164+
);
157165
// @formatter:on
158166
}
159167

160168
private OAuth2Authorization createAuthorization() {
161169
JoseHeader headers = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256).build();
162-
JwtClaimsSet claimSet = JwtClaimsSet.builder().claim(StandardClaimNames.SUB, "user").build();
170+
// @formatter:off
171+
JwtClaimsSet claimSet = JwtClaimsSet.builder()
172+
.claims(claims -> claims.putAll(createUserInfo().getClaims()))
173+
.build();
174+
// @formatter:on
163175
Jwt jwt = this.jwtEncoder.encode(headers, claimSet);
164176

165177
Instant now = Instant.now();
166-
Set<String> scopes = new HashSet<>(OPENID_USER_INFO_SCOPES);
178+
Set<String> scopes = new HashSet<>(Arrays.asList(
179+
OidcScopes.OPENID, OidcScopes.ADDRESS, OidcScopes.EMAIL, OidcScopes.PHONE, OidcScopes.PROFILE));
167180
OAuth2AccessToken accessToken = new OAuth2AccessToken(
168181
OAuth2AccessToken.TokenType.BEARER, jwt.getTokenValue(), now, now.plusSeconds(300), scopes);
169182
OidcIdToken idToken = OidcIdToken.withTokenValue("id-token")
@@ -203,6 +216,45 @@ private static OidcUserInfo createUserInfo() {
203216
// @formatter:on
204217
}
205218

219+
@EnableWebSecurity
220+
static class CustomUserInfoConfiguration extends AuthorizationServerConfiguration {
221+
222+
@Bean
223+
@Override
224+
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
225+
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
226+
new OAuth2AuthorizationServerConfigurer<>();
227+
RequestMatcher endpointsMatcher = authorizationServerConfigurer
228+
.getEndpointsMatcher();
229+
230+
// Custom User Info Mapper that retrieves claims from a signed JWT
231+
Function<OAuth2AuthenticationContext, OidcUserInfo> userInfoMapper = context -> {
232+
OidcUserInfoAuthenticationToken authentication = context.getAuthentication();
233+
JwtAuthenticationToken principal = (JwtAuthenticationToken) authentication.getPrincipal();
234+
235+
return new OidcUserInfo(principal.getToken().getClaims());
236+
};
237+
238+
// @formatter:off
239+
http
240+
.requestMatcher(endpointsMatcher)
241+
.authorizeRequests(authorizeRequests ->
242+
authorizeRequests.anyRequest().authenticated()
243+
)
244+
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
245+
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
246+
.apply(authorizationServerConfigurer)
247+
.oidc(oidc -> oidc
248+
.userInfoEndpoint(userInfo -> userInfo
249+
.userInfoMapper(userInfoMapper)
250+
)
251+
);
252+
// @formatter:on
253+
254+
return http.build();
255+
}
256+
}
257+
206258
@EnableWebSecurity
207259
static class AuthorizationServerConfiguration {
208260

oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/ProviderSettingsTests.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public void buildWhenDefaultThenDefaultsAreSet() {
3838
assertThat(providerSettings.getTokenRevocationEndpoint()).isEqualTo("/oauth2/revoke");
3939
assertThat(providerSettings.getTokenIntrospectionEndpoint()).isEqualTo("/oauth2/introspect");
4040
assertThat(providerSettings.getOidcClientRegistrationEndpoint()).isEqualTo("/connect/register");
41+
assertThat(providerSettings.getOidcUserInfoEndpoint()).isEqualTo("/userinfo");
4142
}
4243

4344
@Test
@@ -48,6 +49,7 @@ public void buildWhenSettingsProvidedThenSet() {
4849
String tokenRevocationEndpoint = "/oauth2/v1/revoke";
4950
String tokenIntrospectionEndpoint = "/oauth2/v1/introspect";
5051
String oidcClientRegistrationEndpoint = "/connect/v1/register";
52+
String oidcUserInfoEndpoint = "/connect/v1/userinfo";
5153
String issuer = "https://example.com:9000";
5254

5355
ProviderSettings providerSettings = ProviderSettings.builder()
@@ -59,6 +61,7 @@ public void buildWhenSettingsProvidedThenSet() {
5961
.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint)
6062
.tokenRevocationEndpoint(tokenRevocationEndpoint)
6163
.oidcClientRegistrationEndpoint(oidcClientRegistrationEndpoint)
64+
.oidcUserInfoEndpoint(oidcUserInfoEndpoint)
6265
.build();
6366

6467
assertThat(providerSettings.getIssuer()).isEqualTo(issuer);
@@ -68,6 +71,7 @@ public void buildWhenSettingsProvidedThenSet() {
6871
assertThat(providerSettings.getTokenRevocationEndpoint()).isEqualTo(tokenRevocationEndpoint);
6972
assertThat(providerSettings.getTokenIntrospectionEndpoint()).isEqualTo(tokenIntrospectionEndpoint);
7073
assertThat(providerSettings.getOidcClientRegistrationEndpoint()).isEqualTo(oidcClientRegistrationEndpoint);
74+
assertThat(providerSettings.getOidcUserInfoEndpoint()).isEqualTo(oidcUserInfoEndpoint);
7175
}
7276

7377
@Test
@@ -124,6 +128,13 @@ public void oidcClientRegistrationEndpointWhenNullThenThrowIllegalArgumentExcept
124128
.withMessage("value cannot be null");
125129
}
126130

131+
@Test
132+
public void oidcUserInfoEndpointWhenNullThenThrowIllegalArgumentException() {
133+
assertThatIllegalArgumentException()
134+
.isThrownBy(() -> ProviderSettings.builder().oidcUserInfoEndpoint(null))
135+
.withMessage("value cannot be null");
136+
}
137+
127138
@Test
128139
public void jwksEndpointWhenNullThenThrowIllegalArgumentException() {
129140
assertThatIllegalArgumentException()

0 commit comments

Comments
 (0)