diff --git a/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerController.java b/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerController.java
index bf3fb9f57d..234a16f4cd 100644
--- a/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerController.java
+++ b/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerController.java
@@ -18,6 +18,8 @@ package sample;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
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;
/**
@@ -35,4 +37,9 @@ public class OAuth2ResourceServerController {
public String message() {
return "secret message";
}
+
+ @PostMapping("/message")
+ public String createMessage(@RequestBody String message) {
+ return String.format("Message was created. Content: %s", message);
+ }
}
diff --git a/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java b/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java
index ae7cf6736c..42d424fd7a 100644
--- a/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java
+++ b/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java
@@ -16,6 +16,7 @@
package sample;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@@ -36,7 +37,8 @@ public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfig
http
.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()
)
.oauth2ResourceServer(oauth2ResourceServer ->
diff --git a/samples/boot/oauth2resourceserver-opaque/src/test/java/sample/OAuth2ResourceServerControllerTests.java b/samples/boot/oauth2resourceserver-opaque/src/test/java/sample/OAuth2ResourceServerControllerTests.java
new file mode 100644
index 0000000000..77c766649d
--- /dev/null
+++ b/samples/boot/oauth2resourceserver-opaque/src/test/java/sample/OAuth2ResourceServerControllerTests.java
@@ -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")));
+ }
+}
diff --git a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java
index 6bcb01c5b2..4e7f0a2a64 100644
--- a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java
+++ b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java
@@ -21,18 +21,24 @@ import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import com.nimbusds.oauth2.sdk.util.StringUtils;
+
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.io.DefaultResourceLoader;
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.OAuth2AuthorizedClientRepository;
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.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
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.OidcUserAuthority;
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.JwtGrantedAuthoritiesConverter;
+import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames;
import org.springframework.security.test.context.TestSecurityContextHolder;
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
import org.springframework.security.test.web.support.WebTestUtils;
@@ -246,6 +256,34 @@ public final class SecurityMockMvcRequestPostProcessors {
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
+ *
+ *
+ * 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:
+ *
+ *
+ *
+ * - Invoking apply {@link SecurityMockMvcConfigurers#springSecurity()}
+ * - Adding Spring Security's FilterChainProxy to MockMvc
+ * - Manually adding {@link SecurityContextPersistenceFilter} to the MockMvc
+ * instance may make sense when using MockMvcBuilders standaloneSetup
+ *
+ *
+ * @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}
* 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 attributes = new HashMap<>();
+ private Converter