parent
2015f392ef
commit
e1fdb24b5d
|
@ -18,6 +18,8 @@ package sample;
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,4 +37,9 @@ public class OAuth2ResourceServerController {
|
||||||
public String message() {
|
public String message() {
|
||||||
return "secret message";
|
return "secret message";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/message")
|
||||||
|
public String createMessage(@RequestBody String message) {
|
||||||
|
return String.format("Message was created. Content: %s", message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package sample;
|
package sample;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
@ -36,7 +37,8 @@ public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfig
|
||||||
http
|
http
|
||||||
.authorizeRequests(authorizeRequests ->
|
.authorizeRequests(authorizeRequests ->
|
||||||
authorizeRequests
|
authorizeRequests
|
||||||
.mvcMatchers("/message/**").hasAuthority("SCOPE_message:read")
|
.antMatchers(HttpMethod.GET, "/message/**").hasAuthority("SCOPE_message:read")
|
||||||
|
.antMatchers(HttpMethod.POST, "/message/**").hasAuthority("SCOPE_message:write")
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
)
|
||||||
.oauth2ResourceServer(oauth2ResourceServer ->
|
.oauth2ResourceServer(oauth2ResourceServer ->
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2019 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package sample;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.opaqueToken;
|
||||||
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Josh Cummings
|
||||||
|
* @since 5.3
|
||||||
|
*/
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@WebMvcTest(OAuth2ResourceServerController.class)
|
||||||
|
public class OAuth2ResourceServerControllerTests {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
MockMvc mvc;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void indexGreetsAuthenticatedUser() throws Exception {
|
||||||
|
this.mvc.perform(get("/").with(opaqueToken().attribute("sub", "ch4mpy")))
|
||||||
|
.andExpect(content().string(is("Hello, ch4mpy!")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void messageCanBeReadWithScopeMessageReadAuthority() throws Exception {
|
||||||
|
this.mvc.perform(get("/message").with(opaqueToken().scopes("message:read")))
|
||||||
|
.andExpect(content().string(is("secret message")));
|
||||||
|
|
||||||
|
this.mvc.perform(get("/message")
|
||||||
|
.with(jwt().authorities(new SimpleGrantedAuthority(("SCOPE_message:read")))))
|
||||||
|
.andExpect(content().string(is("secret message")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void messageCanNotBeReadWithoutScopeMessageReadAuthority() throws Exception {
|
||||||
|
this.mvc.perform(get("/message").with(opaqueToken()))
|
||||||
|
.andExpect(status().isForbidden());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void messageCanNotBeCreatedWithoutAnyScope() throws Exception {
|
||||||
|
this.mvc.perform(post("/message")
|
||||||
|
.content("Hello message")
|
||||||
|
.with(opaqueToken()))
|
||||||
|
.andExpect(status().isForbidden());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void messageCanNotBeCreatedWithScopeMessageReadAuthority() throws Exception {
|
||||||
|
this.mvc.perform(post("/message")
|
||||||
|
.content("Hello message")
|
||||||
|
.with(opaqueToken().scopes("message:read")))
|
||||||
|
.andExpect(status().isForbidden());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void messageCanBeCreatedWithScopeMessageWriteAuthority() throws Exception {
|
||||||
|
this.mvc.perform(post("/message")
|
||||||
|
.content("Hello message")
|
||||||
|
.with(opaqueToken().scopes("message:write")))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(content().string(is("Message was created. Content: Hello message")));
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,18 +21,24 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import com.nimbusds.oauth2.sdk.util.StringUtils;
|
||||||
|
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
import org.springframework.core.io.DefaultResourceLoader;
|
import org.springframework.core.io.DefaultResourceLoader;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
@ -55,7 +61,9 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
|
||||||
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository;
|
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository;
|
||||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
|
import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||||
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
||||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
||||||
|
@ -63,8 +71,10 @@ import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
|
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
|
||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
||||||
|
import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames;
|
||||||
import org.springframework.security.test.context.TestSecurityContextHolder;
|
import org.springframework.security.test.context.TestSecurityContextHolder;
|
||||||
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
|
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
|
||||||
import org.springframework.security.test.web.support.WebTestUtils;
|
import org.springframework.security.test.web.support.WebTestUtils;
|
||||||
|
@ -246,6 +256,34 @@ public final class SecurityMockMvcRequestPostProcessors {
|
||||||
return new JwtRequestPostProcessor();
|
return new JwtRequestPostProcessor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establish a {@link SecurityContext} that has a
|
||||||
|
* {@link BearerTokenAuthentication} for the
|
||||||
|
* {@link Authentication} and a {@link OAuth2AuthenticatedPrincipal} for the
|
||||||
|
* {@link Authentication#getPrincipal()}. All details are
|
||||||
|
* declarative and do not require the token to be valid
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The support works by associating the authentication to the HttpServletRequest. To associate
|
||||||
|
* the request to the SecurityContextHolder you need to ensure that the
|
||||||
|
* SecurityContextPersistenceFilter is associated with the MockMvc instance. A few
|
||||||
|
* ways to do this are:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>Invoking apply {@link SecurityMockMvcConfigurers#springSecurity()}</li>
|
||||||
|
* <li>Adding Spring Security's FilterChainProxy to MockMvc</li>
|
||||||
|
* <li>Manually adding {@link SecurityContextPersistenceFilter} to the MockMvc
|
||||||
|
* instance may make sense when using MockMvcBuilders standaloneSetup</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @return the {@link OpaqueTokenRequestPostProcessor} for additional customization
|
||||||
|
* @since 5.3
|
||||||
|
*/
|
||||||
|
public static OpaqueTokenRequestPostProcessor opaqueToken() {
|
||||||
|
return new OpaqueTokenRequestPostProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establish a {@link SecurityContext} that uses the specified {@link Authentication}
|
* Establish a {@link SecurityContext} that uses the specified {@link Authentication}
|
||||||
* for the {@link Authentication#getPrincipal()} and a custom {@link UserDetails}. All
|
* for the {@link Authentication#getPrincipal()} and a custom {@link UserDetails}. All
|
||||||
|
@ -1070,6 +1108,146 @@ public final class SecurityMockMvcRequestPostProcessors {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Josh Cummings
|
||||||
|
* @since 5.3
|
||||||
|
*/
|
||||||
|
public final static class OpaqueTokenRequestPostProcessor implements RequestPostProcessor {
|
||||||
|
private final Map<String, Object> attributes = new HashMap<>();
|
||||||
|
private Converter<Map<String, Object>, Instant> expiresAtConverter =
|
||||||
|
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 OpaqueTokenRequestPostProcessor() {
|
||||||
|
this.attributes.put(OAuth2IntrospectionClaimNames.SUBJECT, "user");
|
||||||
|
this.attributes.put(OAuth2IntrospectionClaimNames.SCOPE, "read");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the provided attribute to the resulting principal
|
||||||
|
* @param name the attribute name
|
||||||
|
* @param value the attribute value
|
||||||
|
* @return the {@link OpaqueTokenRequestPostProcessor} for further configuration
|
||||||
|
*/
|
||||||
|
public OpaqueTokenRequestPostProcessor attribute(String name, Object value) {
|
||||||
|
Assert.notNull(name, "name cannot be null");
|
||||||
|
this.attributes.put(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the provided authorities in the resulting principal
|
||||||
|
* @param authorities the authorities to use
|
||||||
|
* @return the {@link OpaqueTokenRequestPostProcessor} for further configuration
|
||||||
|
*/
|
||||||
|
public OpaqueTokenRequestPostProcessor authorities(Collection<GrantedAuthority> authorities) {
|
||||||
|
Assert.notNull(authorities, "authorities cannot be null");
|
||||||
|
this.authoritiesConverter = attributes -> authorities;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the provided authorities in the resulting principal
|
||||||
|
* @param authorities the authorities to use
|
||||||
|
* @return the {@link OpaqueTokenRequestPostProcessor} for further configuration
|
||||||
|
*/
|
||||||
|
public OpaqueTokenRequestPostProcessor authorities(GrantedAuthority... authorities) {
|
||||||
|
Assert.notNull(authorities, "authorities cannot be null");
|
||||||
|
this.authoritiesConverter = attributes -> Arrays.asList(authorities);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the provided scopes as the authorities in the resulting principal
|
||||||
|
* @param scopes the scopes to use
|
||||||
|
* @return the {@link OpaqueTokenRequestPostProcessor} for further configuration
|
||||||
|
*/
|
||||||
|
public OpaqueTokenRequestPostProcessor scopes(String... scopes) {
|
||||||
|
Assert.notNull(scopes, "scopes cannot be null");
|
||||||
|
this.authoritiesConverter = attributes -> getAuthorities(Arrays.asList(scopes));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @return the {@link OpaqueTokenRequestPostProcessor} for further configuration
|
||||||
|
*/
|
||||||
|
public OpaqueTokenRequestPostProcessor principal(OAuth2AuthenticatedPrincipal principal) {
|
||||||
|
Assert.notNull(principal, "principal cannot be null");
|
||||||
|
this.principal = principal;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
|
||||||
|
CsrfFilter.skipRequest(request);
|
||||||
|
OAuth2AuthenticatedPrincipal principal = getPrincipal();
|
||||||
|
OAuth2AccessToken accessToken = getOAuth2AccessToken(principal);
|
||||||
|
BearerTokenAuthentication token = new BearerTokenAuthentication
|
||||||
|
(principal, accessToken, principal.getAuthorities());
|
||||||
|
return new AuthenticationRequestPostProcessor(token).postProcessRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OAuth2AuthenticatedPrincipal getPrincipal() {
|
||||||
|
if (this.principal != null) {
|
||||||
|
return this.principal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DefaultOAuth2AuthenticatedPrincipal
|
||||||
|
(this.attributes, this.authoritiesConverter.convert(this.attributes));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<GrantedAuthority> getAuthorities(Map<String, Object> attributes) {
|
||||||
|
Object scope = attributes.get(OAuth2IntrospectionClaimNames.SCOPE);
|
||||||
|
if (scope == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
if (scope instanceof Collection) {
|
||||||
|
return getAuthorities((Collection) scope);
|
||||||
|
}
|
||||||
|
String scopes = scope.toString();
|
||||||
|
if (StringUtils.isBlank(scopes)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return getAuthorities(Arrays.asList(scopes.split(" ")));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<GrantedAuthority> getAuthorities(Collection<?> scopes) {
|
||||||
|
return scopes.stream()
|
||||||
|
.map(scope -> new SimpleGrantedAuthority("SCOPE_" + scope))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Instant getInstant(Map<String, Object> attributes, String name) {
|
||||||
|
Object value = attributes.get(name);
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (value instanceof Instant) {
|
||||||
|
return (Instant) value;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Josh Cummings
|
* @author Josh Cummings
|
||||||
* @since 5.3
|
* @since 5.3
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2019 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.security.test.web.servlet.request;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||||
|
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
import org.springframework.test.context.web.WebAppConfiguration;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.powermock.api.mockito.PowerMockito.when;
|
||||||
|
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.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link SecurityMockMvcRequestPostProcessors#opaqueToken()}
|
||||||
|
*
|
||||||
|
* @author Josh Cummings
|
||||||
|
* @since 5.3
|
||||||
|
*/
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@ContextConfiguration
|
||||||
|
@WebAppConfiguration
|
||||||
|
public class SecurityMockMvcRequestPostProcessorsOpaqueTokenTests {
|
||||||
|
@Autowired
|
||||||
|
WebApplicationContext context;
|
||||||
|
|
||||||
|
MockMvc mvc;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
// @formatter:off
|
||||||
|
this.mvc = MockMvcBuilders
|
||||||
|
.webAppContextSetup(this.context)
|
||||||
|
.apply(springSecurity())
|
||||||
|
.build();
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void opaqueTokenWhenUsingDefaultsThenProducesDefaultAuthentication()
|
||||||
|
throws Exception {
|
||||||
|
|
||||||
|
this.mvc.perform(get("/name").with(opaqueToken()))
|
||||||
|
.andExpect(content().string("user"));
|
||||||
|
this.mvc.perform(get("/admin/scopes").with(opaqueToken()))
|
||||||
|
.andExpect(status().isForbidden());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void opaqueTokenWhenAuthoritiesSpecifiedThenGrantsAccess() throws Exception {
|
||||||
|
this.mvc.perform(get("/admin/scopes")
|
||||||
|
.with(opaqueToken().scopes("admin", "read")))
|
||||||
|
.andExpect(content().string("[\"SCOPE_admin\",\"SCOPE_read\"]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void opaqueTokenWhenAttributeSpecifiedThenUserHasAttribute() throws Exception {
|
||||||
|
this.mvc.perform(get("/opaque-token/iss")
|
||||||
|
.with(opaqueToken().attribute("iss", "https://idp.example.org")))
|
||||||
|
.andExpect(content().string("https://idp.example.org"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void opaqueTokenWhenPrincipalSpecifiedThenAuthenticationHasPrincipal() throws Exception {
|
||||||
|
Collection authorities = Collections.singleton(new SimpleGrantedAuthority("SCOPE_read"));
|
||||||
|
OAuth2AuthenticatedPrincipal principal = mock(OAuth2AuthenticatedPrincipal.class);
|
||||||
|
when(principal.getName()).thenReturn("ben");
|
||||||
|
when(principal.getAuthorities()).thenReturn(authorities);
|
||||||
|
|
||||||
|
this.mvc.perform(get("/name").with(opaqueToken().principal(principal)))
|
||||||
|
.andExpect(content().string("ben"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableWebMvc
|
||||||
|
static class OAuth2LoginConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
@Override
|
||||||
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.authorizeRequests()
|
||||||
|
.mvcMatchers("/admin/**").hasAuthority("SCOPE_admin")
|
||||||
|
.anyRequest().hasAuthority("SCOPE_read")
|
||||||
|
.and()
|
||||||
|
.oauth2ResourceServer()
|
||||||
|
.opaqueToken()
|
||||||
|
.introspector(mock(OpaqueTokenIntrospector.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
static class PrincipalController {
|
||||||
|
@GetMapping("/name")
|
||||||
|
String name(@AuthenticationPrincipal OAuth2AuthenticatedPrincipal principal) {
|
||||||
|
return principal.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/opaque-token/{attribute}")
|
||||||
|
String tokenAttribute(@AuthenticationPrincipal OAuth2AuthenticatedPrincipal principal,
|
||||||
|
@PathVariable("attribute") String attribute) {
|
||||||
|
|
||||||
|
return principal.getAttribute(attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/admin/scopes")
|
||||||
|
List<String> scopes(@AuthenticationPrincipal(expression = "authorities")
|
||||||
|
Collection<GrantedAuthority> authorities) {
|
||||||
|
|
||||||
|
return authorities.stream().map(GrantedAuthority::getAuthority)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue