opaqueToken MockMvc Configuration Order

Fixes gh-7800
This commit is contained in:
Josh Cummings 2020-01-10 16:47:07 -07:00
parent ad7c44f7fd
commit 8f1d0cf528
No known key found for this signature in database
GPG Key ID: 49EF60DD7FF83443
3 changed files with 63 additions and 45 deletions

View File

@ -45,7 +45,7 @@ public class OAuth2ResourceServerControllerTests {
@Test @Test
public void indexGreetsAuthenticatedUser() throws Exception { public void indexGreetsAuthenticatedUser() throws Exception {
this.mvc.perform(get("/").with(opaqueToken().attribute("sub", "ch4mpy"))) this.mvc.perform(get("/").with(opaqueToken().attributes(a -> a.put("sub", "ch4mpy"))))
.andExpect(content().string(is("Hello, ch4mpy!"))); .andExpect(content().string(is("Hello, ch4mpy!")));
} }

View File

@ -1147,30 +1147,27 @@ public final class SecurityMockMvcRequestPostProcessors {
* @since 5.3 * @since 5.3
*/ */
public final static class OpaqueTokenRequestPostProcessor implements RequestPostProcessor { public final static class OpaqueTokenRequestPostProcessor implements RequestPostProcessor {
private final Map<String, Object> attributes = new HashMap<>(); private Supplier<Map<String, Object>> attributes = this::defaultAttributes;
private Converter<Map<String, Object>, Instant> expiresAtConverter = private Supplier<Collection<GrantedAuthority>> authorities = this::defaultAuthorities;
attributes -> getInstant(attributes, "exp");
private Converter<Map<String, Object>, Instant> issuedAtConverter =
attributes -> getInstant(attributes, "iat");
private Converter<Map<String, Object>, Collection<GrantedAuthority>> authoritiesConverter =
attributes -> getAuthorities(attributes);
private OAuth2AuthenticatedPrincipal principal; private Supplier<OAuth2AuthenticatedPrincipal> principal = this::defaultPrincipal;
private OpaqueTokenRequestPostProcessor() { private OpaqueTokenRequestPostProcessor() { }
this.attributes.put(OAuth2IntrospectionClaimNames.SUBJECT, "user");
this.attributes.put(OAuth2IntrospectionClaimNames.SCOPE, "read");
}
/** /**
* Add the provided attribute to the resulting principal * Mutate the attributes using the given {@link Consumer}
* @param name the attribute name *
* @param value the attribute value * @param attributesConsumer The {@link Consumer} for mutating the {@Map} of attributes
* @return the {@link OpaqueTokenRequestPostProcessor} for further configuration * @return the {@link OpaqueTokenRequestPostProcessor} for further configuration
*/ */
public OpaqueTokenRequestPostProcessor attribute(String name, Object value) { public OpaqueTokenRequestPostProcessor attributes(Consumer<Map<String, Object>> attributesConsumer) {
Assert.notNull(name, "name cannot be null"); Assert.notNull(attributesConsumer, "attributesConsumer cannot be null");
this.attributes.put(name, value); this.attributes = () -> {
Map<String, Object> attributes = defaultAttributes();
attributesConsumer.accept(attributes);
return attributes;
};
this.principal = this::defaultPrincipal;
return this; return this;
} }
@ -1181,7 +1178,8 @@ public final class SecurityMockMvcRequestPostProcessors {
*/ */
public OpaqueTokenRequestPostProcessor authorities(Collection<GrantedAuthority> authorities) { public OpaqueTokenRequestPostProcessor authorities(Collection<GrantedAuthority> authorities) {
Assert.notNull(authorities, "authorities cannot be null"); Assert.notNull(authorities, "authorities cannot be null");
this.authoritiesConverter = attributes -> authorities; this.authorities = () -> authorities;
this.principal = this::defaultPrincipal;
return this; return this;
} }
@ -1192,7 +1190,8 @@ public final class SecurityMockMvcRequestPostProcessors {
*/ */
public OpaqueTokenRequestPostProcessor authorities(GrantedAuthority... authorities) { public OpaqueTokenRequestPostProcessor authorities(GrantedAuthority... authorities) {
Assert.notNull(authorities, "authorities cannot be null"); Assert.notNull(authorities, "authorities cannot be null");
this.authoritiesConverter = attributes -> Arrays.asList(authorities); this.authorities = () -> Arrays.asList(authorities);
this.principal = this::defaultPrincipal;
return this; return this;
} }
@ -1203,46 +1202,41 @@ public final class SecurityMockMvcRequestPostProcessors {
*/ */
public OpaqueTokenRequestPostProcessor scopes(String... scopes) { public OpaqueTokenRequestPostProcessor scopes(String... scopes) {
Assert.notNull(scopes, "scopes cannot be null"); Assert.notNull(scopes, "scopes cannot be null");
this.authoritiesConverter = attributes -> getAuthorities(Arrays.asList(scopes)); this.authorities = () -> getAuthorities(Arrays.asList(scopes));
this.principal = this::defaultPrincipal;
return this; return this;
} }
/** /**
* Use the provided principal * Use the provided principal
*
* Providing the principal takes precedence over
* any authorities or attributes provided via {@link #attribute(String, Object)},
* {@link #authorities} or {@link #scopes}.
*
* @param principal the principal to use * @param principal the principal to use
* @return the {@link OpaqueTokenRequestPostProcessor} for further configuration * @return the {@link OpaqueTokenRequestPostProcessor} for further configuration
*/ */
public OpaqueTokenRequestPostProcessor principal(OAuth2AuthenticatedPrincipal principal) { public OpaqueTokenRequestPostProcessor principal(OAuth2AuthenticatedPrincipal principal) {
Assert.notNull(principal, "principal cannot be null"); Assert.notNull(principal, "principal cannot be null");
this.principal = principal; this.principal = () -> principal;
return this; return this;
} }
@Override @Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
CsrfFilter.skipRequest(request); CsrfFilter.skipRequest(request);
OAuth2AuthenticatedPrincipal principal = getPrincipal(); OAuth2AuthenticatedPrincipal principal = this.principal.get();
OAuth2AccessToken accessToken = getOAuth2AccessToken(principal); OAuth2AccessToken accessToken = getOAuth2AccessToken(principal);
BearerTokenAuthentication token = new BearerTokenAuthentication BearerTokenAuthentication token = new BearerTokenAuthentication
(principal, accessToken, principal.getAuthorities()); (principal, accessToken, principal.getAuthorities());
return new AuthenticationRequestPostProcessor(token).postProcessRequest(request); return new AuthenticationRequestPostProcessor(token).postProcessRequest(request);
} }
private OAuth2AuthenticatedPrincipal getPrincipal() { private Map<String, Object> defaultAttributes() {
if (this.principal != null) { Map<String, Object> attributes = new HashMap<>();
return this.principal; attributes.put(OAuth2IntrospectionClaimNames.SUBJECT, "user");
} attributes.put(OAuth2IntrospectionClaimNames.SCOPE, "read");
return attributes;
return new DefaultOAuth2AuthenticatedPrincipal
(this.attributes, this.authoritiesConverter.convert(this.attributes));
} }
private Collection<GrantedAuthority> getAuthorities(Map<String, Object> attributes) { private Collection<GrantedAuthority> defaultAuthorities() {
Map<String, Object> attributes = this.attributes.get();
Object scope = attributes.get(OAuth2IntrospectionClaimNames.SCOPE); Object scope = attributes.get(OAuth2IntrospectionClaimNames.SCOPE);
if (scope == null) { if (scope == null) {
return Collections.emptyList(); return Collections.emptyList();
@ -1257,12 +1251,24 @@ public final class SecurityMockMvcRequestPostProcessors {
return getAuthorities(Arrays.asList(scopes.split(" "))); return getAuthorities(Arrays.asList(scopes.split(" ")));
} }
private OAuth2AuthenticatedPrincipal defaultPrincipal() {
return new DefaultOAuth2AuthenticatedPrincipal
(this.attributes.get(), this.authorities.get());
}
private Collection<GrantedAuthority> getAuthorities(Collection<?> scopes) { private Collection<GrantedAuthority> getAuthorities(Collection<?> scopes) {
return scopes.stream() return scopes.stream()
.map(scope -> new SimpleGrantedAuthority("SCOPE_" + scope)) .map(scope -> new SimpleGrantedAuthority("SCOPE_" + scope))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private OAuth2AccessToken getOAuth2AccessToken(OAuth2AuthenticatedPrincipal principal) {
Instant expiresAt = getInstant(principal.getAttributes(), "exp");
Instant issuedAt = getInstant(principal.getAttributes(), "iat");
return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
"token", issuedAt, expiresAt);
}
private Instant getInstant(Map<String, Object> attributes, String name) { private Instant getInstant(Map<String, Object> attributes, String name) {
Object value = attributes.get(name); Object value = attributes.get(name);
if (value == null) { if (value == null) {
@ -1273,13 +1279,6 @@ public final class SecurityMockMvcRequestPostProcessors {
} }
throw new IllegalArgumentException(name + " attribute must be of type Instant"); throw new IllegalArgumentException(name + " attribute must be of type Instant");
} }
private OAuth2AccessToken getOAuth2AccessToken(OAuth2AuthenticatedPrincipal principal) {
Instant expiresAt = this.expiresAtConverter.convert(principal.getAttributes());
Instant issuedAt = this.issuedAtConverter.convert(principal.getAttributes());
return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
"token", issuedAt, expiresAt);
}
} }
/** /**

View File

@ -47,6 +47,7 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.powermock.api.mockito.PowerMockito.when; import static org.powermock.api.mockito.PowerMockito.when;
import static org.springframework.security.oauth2.core.TestOAuth2AuthenticatedPrincipals.active;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.opaqueToken; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.opaqueToken;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@ -98,7 +99,7 @@ public class SecurityMockMvcRequestPostProcessorsOpaqueTokenTests {
@Test @Test
public void opaqueTokenWhenAttributeSpecifiedThenUserHasAttribute() throws Exception { public void opaqueTokenWhenAttributeSpecifiedThenUserHasAttribute() throws Exception {
this.mvc.perform(get("/opaque-token/iss") this.mvc.perform(get("/opaque-token/iss")
.with(opaqueToken().attribute("iss", "https://idp.example.org"))) .with(opaqueToken().attributes(a -> a.put("iss", "https://idp.example.org"))))
.andExpect(content().string("https://idp.example.org")); .andExpect(content().string("https://idp.example.org"));
} }
@ -113,6 +114,24 @@ public class SecurityMockMvcRequestPostProcessorsOpaqueTokenTests {
.andExpect(content().string("ben")); .andExpect(content().string("ben"));
} }
// gh-7800
@Test
public void opaqueTokenWhenPrincipalSpecifiedThenLastCalledTakesPrecedence() throws Exception {
OAuth2AuthenticatedPrincipal principal = active(a -> a.put("scope", "user"));
this.mvc.perform(get("/opaque-token/sub")
.with(opaqueToken()
.attributes(a -> a.put("sub", "foo"))
.principal(principal)))
.andExpect(status().isOk())
.andExpect(content().string((String) principal.getAttribute("sub")));
this.mvc.perform(get("/opaque-token/sub")
.with(opaqueToken()
.principal(principal)
.attributes(a -> a.put("sub", "bar"))))
.andExpect(content().string("bar"));
}
@EnableWebSecurity @EnableWebSecurity
@EnableWebMvc @EnableWebMvc
static class OAuth2LoginConfig extends WebSecurityConfigurerAdapter { static class OAuth2LoginConfig extends WebSecurityConfigurerAdapter {