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