Skip to content

Commit 8f1d0cf

Browse files
committed
opaqueToken MockMvc Configuration Order
Fixes gh-7800
1 parent ad7c44f commit 8f1d0cf

File tree

3 files changed

+63
-45
lines changed

3 files changed

+63
-45
lines changed

samples/boot/oauth2resourceserver-opaque/src/test/java/sample/OAuth2ResourceServerControllerTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public class OAuth2ResourceServerControllerTests {
4545

4646
@Test
4747
public void indexGreetsAuthenticatedUser() throws Exception {
48-
this.mvc.perform(get("/").with(opaqueToken().attribute("sub", "ch4mpy")))
48+
this.mvc.perform(get("/").with(opaqueToken().attributes(a -> a.put("sub", "ch4mpy"))))
4949
.andExpect(content().string(is("Hello, ch4mpy!")));
5050
}
5151

test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java

Lines changed: 42 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,30 +1147,27 @@ public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request)
11471147
* @since 5.3
11481148
*/
11491149
public final static class OpaqueTokenRequestPostProcessor implements RequestPostProcessor {
1150-
private final Map<String, Object> attributes = new HashMap<>();
1151-
private Converter<Map<String, Object>, Instant> expiresAtConverter =
1152-
attributes -> getInstant(attributes, "exp");
1153-
private Converter<Map<String, Object>, Instant> issuedAtConverter =
1154-
attributes -> getInstant(attributes, "iat");
1155-
private Converter<Map<String, Object>, Collection<GrantedAuthority>> authoritiesConverter =
1156-
attributes -> getAuthorities(attributes);
1150+
private Supplier<Map<String, Object>> attributes = this::defaultAttributes;
1151+
private Supplier<Collection<GrantedAuthority>> authorities = this::defaultAuthorities;
11571152

1158-
private OAuth2AuthenticatedPrincipal principal;
1153+
private Supplier<OAuth2AuthenticatedPrincipal> principal = this::defaultPrincipal;
11591154

1160-
private OpaqueTokenRequestPostProcessor() {
1161-
this.attributes.put(OAuth2IntrospectionClaimNames.SUBJECT, "user");
1162-
this.attributes.put(OAuth2IntrospectionClaimNames.SCOPE, "read");
1163-
}
1155+
private OpaqueTokenRequestPostProcessor() { }
11641156

11651157
/**
1166-
* Add the provided attribute to the resulting principal
1167-
* @param name the attribute name
1168-
* @param value the attribute value
1158+
* Mutate the attributes using the given {@link Consumer}
1159+
*
1160+
* @param attributesConsumer The {@link Consumer} for mutating the {@Map} of attributes
11691161
* @return the {@link OpaqueTokenRequestPostProcessor} for further configuration
11701162
*/
1171-
public OpaqueTokenRequestPostProcessor attribute(String name, Object value) {
1172-
Assert.notNull(name, "name cannot be null");
1173-
this.attributes.put(name, value);
1163+
public OpaqueTokenRequestPostProcessor attributes(Consumer<Map<String, Object>> attributesConsumer) {
1164+
Assert.notNull(attributesConsumer, "attributesConsumer cannot be null");
1165+
this.attributes = () -> {
1166+
Map<String, Object> attributes = defaultAttributes();
1167+
attributesConsumer.accept(attributes);
1168+
return attributes;
1169+
};
1170+
this.principal = this::defaultPrincipal;
11741171
return this;
11751172
}
11761173

@@ -1181,7 +1178,8 @@ public OpaqueTokenRequestPostProcessor attribute(String name, Object value) {
11811178
*/
11821179
public OpaqueTokenRequestPostProcessor authorities(Collection<GrantedAuthority> authorities) {
11831180
Assert.notNull(authorities, "authorities cannot be null");
1184-
this.authoritiesConverter = attributes -> authorities;
1181+
this.authorities = () -> authorities;
1182+
this.principal = this::defaultPrincipal;
11851183
return this;
11861184
}
11871185

@@ -1192,7 +1190,8 @@ public OpaqueTokenRequestPostProcessor authorities(Collection<GrantedAuthority>
11921190
*/
11931191
public OpaqueTokenRequestPostProcessor authorities(GrantedAuthority... authorities) {
11941192
Assert.notNull(authorities, "authorities cannot be null");
1195-
this.authoritiesConverter = attributes -> Arrays.asList(authorities);
1193+
this.authorities = () -> Arrays.asList(authorities);
1194+
this.principal = this::defaultPrincipal;
11961195
return this;
11971196
}
11981197

@@ -1203,46 +1202,41 @@ public OpaqueTokenRequestPostProcessor authorities(GrantedAuthority... authoriti
12031202
*/
12041203
public OpaqueTokenRequestPostProcessor scopes(String... scopes) {
12051204
Assert.notNull(scopes, "scopes cannot be null");
1206-
this.authoritiesConverter = attributes -> getAuthorities(Arrays.asList(scopes));
1205+
this.authorities = () -> getAuthorities(Arrays.asList(scopes));
1206+
this.principal = this::defaultPrincipal;
12071207
return this;
12081208
}
12091209

12101210
/**
12111211
* Use the provided principal
1212-
*
1213-
* Providing the principal takes precedence over
1214-
* any authorities or attributes provided via {@link #attribute(String, Object)},
1215-
* {@link #authorities} or {@link #scopes}.
1216-
*
12171212
* @param principal the principal to use
12181213
* @return the {@link OpaqueTokenRequestPostProcessor} for further configuration
12191214
*/
12201215
public OpaqueTokenRequestPostProcessor principal(OAuth2AuthenticatedPrincipal principal) {
12211216
Assert.notNull(principal, "principal cannot be null");
1222-
this.principal = principal;
1217+
this.principal = () -> principal;
12231218
return this;
12241219
}
12251220

12261221
@Override
12271222
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
12281223
CsrfFilter.skipRequest(request);
1229-
OAuth2AuthenticatedPrincipal principal = getPrincipal();
1224+
OAuth2AuthenticatedPrincipal principal = this.principal.get();
12301225
OAuth2AccessToken accessToken = getOAuth2AccessToken(principal);
12311226
BearerTokenAuthentication token = new BearerTokenAuthentication
12321227
(principal, accessToken, principal.getAuthorities());
12331228
return new AuthenticationRequestPostProcessor(token).postProcessRequest(request);
12341229
}
12351230

1236-
private OAuth2AuthenticatedPrincipal getPrincipal() {
1237-
if (this.principal != null) {
1238-
return this.principal;
1239-
}
1240-
1241-
return new DefaultOAuth2AuthenticatedPrincipal
1242-
(this.attributes, this.authoritiesConverter.convert(this.attributes));
1231+
private Map<String, Object> defaultAttributes() {
1232+
Map<String, Object> attributes = new HashMap<>();
1233+
attributes.put(OAuth2IntrospectionClaimNames.SUBJECT, "user");
1234+
attributes.put(OAuth2IntrospectionClaimNames.SCOPE, "read");
1235+
return attributes;
12431236
}
12441237

1245-
private Collection<GrantedAuthority> getAuthorities(Map<String, Object> attributes) {
1238+
private Collection<GrantedAuthority> defaultAuthorities() {
1239+
Map<String, Object> attributes = this.attributes.get();
12461240
Object scope = attributes.get(OAuth2IntrospectionClaimNames.SCOPE);
12471241
if (scope == null) {
12481242
return Collections.emptyList();
@@ -1257,12 +1251,24 @@ private Collection<GrantedAuthority> getAuthorities(Map<String, Object> attribut
12571251
return getAuthorities(Arrays.asList(scopes.split(" ")));
12581252
}
12591253

1254+
private OAuth2AuthenticatedPrincipal defaultPrincipal() {
1255+
return new DefaultOAuth2AuthenticatedPrincipal
1256+
(this.attributes.get(), this.authorities.get());
1257+
}
1258+
12601259
private Collection<GrantedAuthority> getAuthorities(Collection<?> scopes) {
12611260
return scopes.stream()
12621261
.map(scope -> new SimpleGrantedAuthority("SCOPE_" + scope))
12631262
.collect(Collectors.toList());
12641263
}
12651264

1265+
private OAuth2AccessToken getOAuth2AccessToken(OAuth2AuthenticatedPrincipal principal) {
1266+
Instant expiresAt = getInstant(principal.getAttributes(), "exp");
1267+
Instant issuedAt = getInstant(principal.getAttributes(), "iat");
1268+
return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
1269+
"token", issuedAt, expiresAt);
1270+
}
1271+
12661272
private Instant getInstant(Map<String, Object> attributes, String name) {
12671273
Object value = attributes.get(name);
12681274
if (value == null) {
@@ -1273,13 +1279,6 @@ private Instant getInstant(Map<String, Object> attributes, String name) {
12731279
}
12741280
throw new IllegalArgumentException(name + " attribute must be of type Instant");
12751281
}
1276-
1277-
private OAuth2AccessToken getOAuth2AccessToken(OAuth2AuthenticatedPrincipal principal) {
1278-
Instant expiresAt = this.expiresAtConverter.convert(principal.getAttributes());
1279-
Instant issuedAt = this.issuedAtConverter.convert(principal.getAttributes());
1280-
return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
1281-
"token", issuedAt, expiresAt);
1282-
}
12831282
}
12841283

12851284
/**

test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessorsOpaqueTokenTests.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747

4848
import static org.mockito.Mockito.mock;
4949
import static org.powermock.api.mockito.PowerMockito.when;
50+
import static org.springframework.security.oauth2.core.TestOAuth2AuthenticatedPrincipals.active;
5051
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.opaqueToken;
5152
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
5253
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -98,7 +99,7 @@ public void opaqueTokenWhenAuthoritiesSpecifiedThenGrantsAccess() throws Excepti
9899
@Test
99100
public void opaqueTokenWhenAttributeSpecifiedThenUserHasAttribute() throws Exception {
100101
this.mvc.perform(get("/opaque-token/iss")
101-
.with(opaqueToken().attribute("iss", "https://idp.example.org")))
102+
.with(opaqueToken().attributes(a -> a.put("iss", "https://idp.example.org"))))
102103
.andExpect(content().string("https://idp.example.org"));
103104
}
104105

@@ -113,6 +114,24 @@ public void opaqueTokenWhenPrincipalSpecifiedThenAuthenticationHasPrincipal() th
113114
.andExpect(content().string("ben"));
114115
}
115116

117+
// gh-7800
118+
@Test
119+
public void opaqueTokenWhenPrincipalSpecifiedThenLastCalledTakesPrecedence() throws Exception {
120+
OAuth2AuthenticatedPrincipal principal = active(a -> a.put("scope", "user"));
121+
122+
this.mvc.perform(get("/opaque-token/sub")
123+
.with(opaqueToken()
124+
.attributes(a -> a.put("sub", "foo"))
125+
.principal(principal)))
126+
.andExpect(status().isOk())
127+
.andExpect(content().string((String) principal.getAttribute("sub")));
128+
this.mvc.perform(get("/opaque-token/sub")
129+
.with(opaqueToken()
130+
.principal(principal)
131+
.attributes(a -> a.put("sub", "bar"))))
132+
.andExpect(content().string("bar"));
133+
}
134+
116135
@EnableWebSecurity
117136
@EnableWebMvc
118137
static class OAuth2LoginConfig extends WebSecurityConfigurerAdapter {

0 commit comments

Comments
 (0)