> jwt) {
- return jwt().token(jwt);
+
+ /**
+ * Establish a {@link SecurityContext} that has a
+ * {@link JwtAuthenticationToken} for the
+ * {@link Authentication} and a {@link Jwt} for the
+ * {@link Authentication#getPrincipal()}. All details are
+ * declarative and do not require the JWT 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
+ *
+ *
+ * @param jwtBuilderConsumer For configuring the underlying {@link Jwt}
+ * @return the {@link JwtRequestPostProcessor} for additional customization
+ * @since 5.2
+ */
+ public static JwtRequestPostProcessor jwt(Consumer jwtBuilderConsumer) {
+ Jwt.Builder jwtBuilder = Jwt.withTokenValue("token")
+ .header("alg", "none")
+ .claim(SUB, "user")
+ .claim("scope", "read");
+ jwtBuilderConsumer.accept(jwtBuilder);
+ return new JwtRequestPostProcessor(jwtBuilder.build());
}
/**
@@ -590,7 +622,7 @@ public final class SecurityMockMvcRequestPostProcessors {
* Support class for {@link RequestPostProcessor}'s that establish a Spring Security
* context
*/
- static class SecurityContextRequestPostProcessorSupport {
+ private static abstract class SecurityContextRequestPostProcessorSupport {
/**
* Saves the specified {@link Authentication} into an empty
@@ -599,7 +631,7 @@ public final class SecurityMockMvcRequestPostProcessors {
* @param authentication the {@link Authentication} to save
* @param request the {@link HttpServletRequest} to use
*/
- static final void save(Authentication authentication, HttpServletRequest request) {
+ final void save(Authentication authentication, HttpServletRequest request) {
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(authentication);
save(securityContext, request);
@@ -611,7 +643,7 @@ public final class SecurityMockMvcRequestPostProcessors {
* @param securityContext the {@link SecurityContext} to save
* @param request the {@link HttpServletRequest} to use
*/
- static final void save(SecurityContext securityContext, HttpServletRequest request) {
+ final void save(SecurityContext securityContext, HttpServletRequest request) {
SecurityContextRepository securityContextRepository = WebTestUtils
.getSecurityContextRepository(request);
boolean isTestRepository = securityContextRepository instanceof TestSecurityContextRepository;
@@ -639,7 +671,7 @@ public final class SecurityMockMvcRequestPostProcessors {
* stateless mode
*/
static class TestSecurityContextRepository implements SecurityContextRepository {
- final static String ATTR_NAME = TestSecurityContextRepository.class
+ private final static String ATTR_NAME = TestSecurityContextRepository.class
.getName().concat(".REPO");
private final SecurityContextRepository delegate;
@@ -751,6 +783,8 @@ public final class SecurityMockMvcRequestPostProcessors {
@Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
+ SecurityContext context = SecurityContextHolder.createEmptyContext();
+ context.setAuthentication(this.authentication);
save(this.authentication, request);
return request;
}
@@ -938,22 +972,64 @@ public final class SecurityMockMvcRequestPostProcessors {
}
}
- private SecurityMockMvcRequestPostProcessors() {
- }
-
/**
* @author Jérôme Wacongne <ch4mp@c4-soft.com>
+ * @author Josh Cummings
* @since 5.2
*/
- public static class JwtRequestPostProcessor extends JwtAuthenticationTokenTestingBuilder
- implements
- RequestPostProcessor {
+ public final static class JwtRequestPostProcessor implements RequestPostProcessor {
+ private Jwt jwt;
+ private Collection extends GrantedAuthority> authorities;
+
+ private JwtRequestPostProcessor(Jwt jwt) {
+ this.jwt = jwt;
+ this.authorities = new JwtGrantedAuthoritiesConverter().convert(jwt);
+ }
+
+ /**
+ * Use the provided authorities in the token
+ * @param authorities the authorities to use
+ * @return the {@link JwtRequestPostProcessor} for further configuration
+ */
+ public JwtRequestPostProcessor authorities(Collection authorities) {
+ Assert.notNull(authorities, "authorities cannot be null");
+ this.authorities = authorities;
+ return this;
+ }
+
+ /**
+ * Use the provided authorities in the token
+ * @param authorities the authorities to use
+ * @return the {@link JwtRequestPostProcessor} for further configuration
+ */
+ public JwtRequestPostProcessor authorities(GrantedAuthority... authorities) {
+ Assert.notNull(authorities, "authorities cannot be null");
+ this.authorities = Arrays.asList(authorities);
+ return this;
+ }
+
+ /**
+ * Provides the configured {@link Jwt} so that custom authorities can be derived
+ * from it
+ *
+ * @param authoritiesConverter the conversion strategy from {@link Jwt} to a {@link Collection}
+ * of {@link GrantedAuthority}s
+ * @return the {@link JwtRequestPostProcessor} for further configuration
+ */
+ public JwtRequestPostProcessor authorities(Converter> authoritiesConverter) {
+ Assert.notNull(authoritiesConverter, "authoritiesConverter cannot be null");
+ this.authorities = authoritiesConverter.convert(this.jwt);
+ return this;
+ }
@Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
- SecurityContextRequestPostProcessorSupport.save(build(), request);
- return request;
+ JwtAuthenticationToken token = new JwtAuthenticationToken(this.jwt, this.authorities);
+ return new AuthenticationRequestPostProcessor(token).postProcessRequest(request);
}
}
+
+ private SecurityMockMvcRequestPostProcessors() {
+ }
}
diff --git a/test/src/test/java/org/springframework/security/test/support/JwtAuthenticationTokenTestingBuilderTests.java b/test/src/test/java/org/springframework/security/test/support/JwtAuthenticationTokenTestingBuilderTests.java
deleted file mode 100644
index e153f91491..0000000000
--- a/test/src/test/java/org/springframework/security/test/support/JwtAuthenticationTokenTestingBuilderTests.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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.support;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.junit.Test;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import org.springframework.security.oauth2.jwt.Jwt;
-import org.springframework.security.oauth2.jwt.JwtClaimNames;
-import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
-
-/**
- * @author Jérôme Wacongne <ch4mp@c4-soft.com>
- * @since 5.2
- */
-public class JwtAuthenticationTokenTestingBuilderTests {
-
- @Test
- public void untouchedBuilderSetsDefaultValues() {
- final JwtAuthenticationToken actual = new JwtAuthenticationTokenTestingBuilder<>().build();
-
- assertThat(actual.getName()).isEqualTo("user");
- assertThat(actual.getAuthorities()).containsExactly(new SimpleGrantedAuthority("SCOPE_USER"));
- assertThat(actual.getPrincipal()).isInstanceOf(Jwt.class);
- assertThat(actual.getCredentials()).isInstanceOf(Jwt.class);
- assertThat(actual.getDetails()).isNull();
-
- // Token default values are tested in JwtTestingBuilderTests
- assertThat(actual.getToken()).isEqualTo(new JwtAuthenticationTokenTestingBuilder.JwtTestingBuilder().build());
- }
-
- @Test
- public void nameOverridesDefaultValue() {
- assertThat(new JwtAuthenticationTokenTestingBuilder<>().name("ch4mpy").build().getName()).isEqualTo("ch4mpy");
- }
-
- @Test
- public void authoritiesAddsToDefaultValue() {
- assertThat(new JwtAuthenticationTokenTestingBuilder<>().authorities("TEST").build().getAuthorities())
- .containsExactlyInAnyOrder(new SimpleGrantedAuthority("SCOPE_USER"), new SimpleGrantedAuthority("TEST"));
- }
-
- @Test
- public void scopesOveridesDefaultValue() {
- assertThat(new JwtAuthenticationTokenTestingBuilder<>().scopes("TEST").build().getAuthorities())
- .containsExactly(new SimpleGrantedAuthority("SCOPE_TEST"));
- }
-
- @Test
- public void nameSetsAuthenticationNameAndTokenSubjectClaim() {
- final JwtAuthenticationToken actual = new JwtAuthenticationTokenTestingBuilder<>().name("ch4mpy").build();
-
- assertThat(actual.getName()).isEqualTo("ch4mpy");
- assertThat(actual.getTokenAttributes().get(JwtClaimNames.SUB)).isEqualTo("ch4mpy");
- }
-
- @Test
- public void buildMergesConvertedClaimsAndAuthorities() {
- final JwtAuthenticationToken actual = new JwtAuthenticationTokenTestingBuilder<>().name("ch4mpy")
- .authorities(new SimpleGrantedAuthority("TEST_AUTHORITY"))
- .scopes("scope:claim")
- .build();
-
- assertThat(actual.getAuthorities()).containsExactlyInAnyOrder(
- new SimpleGrantedAuthority("TEST_AUTHORITY"),
- new SimpleGrantedAuthority("SCOPE_scope:claim"));
- }
-
-}
diff --git a/test/src/test/java/org/springframework/security/test/support/JwtTestingBuilderTests.java b/test/src/test/java/org/springframework/security/test/support/JwtTestingBuilderTests.java
deleted file mode 100644
index 75923fac63..0000000000
--- a/test/src/test/java/org/springframework/security/test/support/JwtTestingBuilderTests.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.support;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import java.time.Instant;
-
-import org.junit.Test;
-import org.springframework.security.oauth2.jwt.Jwt;
-import org.springframework.security.oauth2.jwt.JwtClaimNames;
-import org.springframework.security.test.support.JwtAuthenticationTokenTestingBuilder.JwtTestingBuilder;
-
-/**
- *
- *
- * @author Jérôme Wacongne <ch4mp@c4-soft.com>
- */
-public class JwtTestingBuilderTests {
-
- @Test
- public void testDefaultValuesAreSet() {
- final Jwt actual = new JwtTestingBuilder().build();
-
- assertThat(actual.getTokenValue()).isEqualTo("test.jwt.value");
- assertThat(actual.getClaimAsString(JwtClaimNames.SUB)).isEqualTo("user");
- assertThat(actual.getHeaders()).hasSize(1);
- }
-
- @Test
- public void iatClaimAndExpClaimSetIssuedAtAndExpiresAt() {
- final Jwt actual = new JwtTestingBuilder()
- .claim(JwtClaimNames.IAT, Instant.parse("2019-03-21T13:52:25Z"))
- .claim(JwtClaimNames.EXP, Instant.parse("2019-03-22T13:52:25Z"))
- .build();
-
- assertThat(actual.getIssuedAt()).isEqualTo(Instant.parse("2019-03-21T13:52:25Z"));
- assertThat(actual.getExpiresAt()).isEqualTo(Instant.parse("2019-03-22T13:52:25Z"));
- assertThat(actual.getClaimAsInstant(JwtClaimNames.IAT)).isEqualTo(Instant.parse("2019-03-21T13:52:25Z"));
- assertThat(actual.getClaimAsInstant(JwtClaimNames.EXP)).isEqualTo(Instant.parse("2019-03-22T13:52:25Z"));
- }
-
-}
diff --git a/test/src/test/java/org/springframework/security/test/web/reactive/server/AbstractMockServerConfigurersTests.java b/test/src/test/java/org/springframework/security/test/web/reactive/server/AbstractMockServerConfigurersTests.java
index aa5b018b72..6af2661f64 100644
--- a/test/src/test/java/org/springframework/security/test/web/reactive/server/AbstractMockServerConfigurersTests.java
+++ b/test/src/test/java/org/springframework/security/test/web/reactive/server/AbstractMockServerConfigurersTests.java
@@ -16,22 +16,26 @@
package org.springframework.security.test.web.reactive.server;
+import java.security.Principal;
+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.annotation.CurrentSecurityContext;
+import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-import java.security.Principal;
-
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rob Winch
+ * @author Josh Cummings
* @since 5.0
*/
abstract class AbstractMockServerConfigurersTests {
protected PrincipalController controller = new PrincipalController();
+ protected SecurityContextController securityContextController = new SecurityContextController();
protected User.UserBuilder userBuilder = User
.withUsername("user")
@@ -71,4 +75,21 @@ abstract class AbstractMockServerConfigurersTests {
this.principal = null;
}
}
+
+ @RestController
+ protected static class SecurityContextController {
+ volatile SecurityContext securityContext;
+
+ @RequestMapping("/**")
+ public SecurityContext get(@CurrentSecurityContext SecurityContext securityContext) {
+ this.securityContext = securityContext;
+ return securityContext;
+ }
+
+ public SecurityContext removeSecurityContext() {
+ SecurityContext result = this.securityContext;
+ this.securityContext = null;
+ return result;
+ }
+ }
}
diff --git a/test/src/test/java/org/springframework/security/test/web/reactive/server/JwtMutatorTests.java b/test/src/test/java/org/springframework/security/test/web/reactive/server/JwtMutatorTests.java
deleted file mode 100644
index fb1ee4bd66..0000000000
--- a/test/src/test/java/org/springframework/security/test/web/reactive/server/JwtMutatorTests.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.reactive.server;
-
-import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockJwt;
-
-import org.junit.Test;
-
-/**
- * @author Jérôme Wacongne <ch4mp@c4-soft.com>
- * @since 5.2
- */
-public class JwtMutatorTests {
-// @formatter:off
- @Test
- public void defaultJwtConfigurerConfiguresAuthenticationDefaultNameAndAuthorities() {
- TestController.clientBuilder()
- .apply(mockJwt()).build()
- .get().uri("/greet").exchange()
- .expectStatus().isOk()
- .expectBody().toString().equals("Hello user!");
-
- TestController.clientBuilder()
- .apply(mockJwt()).build()
- .get().uri("/authorities").exchange()
- .expectStatus().isOk()
- .expectBody().toString().equals("[\"ROLE_USER\"]");
- }
-
- @Test
- public void nameAndScopesConfigureAuthenticationNameAndAuthorities() {
- TestController.clientBuilder()
- .apply(mockJwt().name("ch4mpy").scopes("message:read")).build()
- .get().uri("/greet").exchange()
- .expectStatus().isOk()
- .expectBody().toString().equals("Hello ch4mpy!");
-
- TestController.clientBuilder()
- .apply(mockJwt().name("ch4mpy").scopes("message:read")).build()
- .get().uri("/authorities").exchange()
- .expectStatus().isOk()
- .expectBody().toString().equals("[\"SCOPE_message:read\"]");
-
- TestController.clientBuilder()
- .apply(mockJwt().name("ch4mpy").scopes("message:read")).build()
- .get().uri("/jwt").exchange()
- .expectStatus().isOk()
- .expectBody().toString().equals(
- "Hello,ch4mpy! You are sucessfully authenticated and granted with [message:read] scopes using a JavaWebToken.");
- }
-// @formatter:on
-}
diff --git a/test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurersJwtTests.java b/test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurersJwtTests.java
new file mode 100644
index 0000000000..adcf9a922f
--- /dev/null
+++ b/test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurersJwtTests.java
@@ -0,0 +1,139 @@
+/*
+ * 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.reactive.server;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import org.springframework.core.ReactiveAdapterRegistry;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
+import org.springframework.security.web.reactive.result.method.annotation.CurrentSecurityContextArgumentResolver;
+import org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockJwt;
+import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
+
+/**
+ * @author Jérôme Wacongne <ch4mp@c4-soft.com>
+ * @author Josh Cummings
+ * @since 5.2
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class SecurityMockServerConfigurersJwtTests extends AbstractMockServerConfigurersTests {
+ @Mock
+ GrantedAuthority authority1;
+
+ @Mock
+ GrantedAuthority authority2;
+
+ WebTestClient client = WebTestClient
+ .bindToController(securityContextController)
+ .webFilter(new SecurityContextServerWebExchangeWebFilter())
+ .argumentResolvers(resolvers -> resolvers.addCustomResolver(
+ new CurrentSecurityContextArgumentResolver(new ReactiveAdapterRegistry())))
+ .apply(springSecurity())
+ .configureClient()
+ .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
+ .build();
+
+ @Test
+ public void mockJwtWhenUsingDefaultsTheCreatesJwtAuthentication() {
+ client
+ .mutateWith(mockJwt())
+ .get()
+ .exchange()
+ .expectStatus().isOk();
+
+ SecurityContext context = securityContextController.removeSecurityContext();
+ assertThat(context.getAuthentication()).isInstanceOf(
+ JwtAuthenticationToken.class);
+ JwtAuthenticationToken token = (JwtAuthenticationToken) context.getAuthentication();
+ assertThat(token.getAuthorities()).isNotEmpty();
+ assertThat(token.getToken()).isNotNull();
+ assertThat(token.getToken().getSubject()).isEqualTo("user");
+ assertThat(token.getToken().getHeaders().get("alg")).isEqualTo("none");
+ }
+
+ @Test
+ public void mockJwtWhenProvidingBuilderConsumerThenProducesJwtAuthentication() {
+ String name = new String("user");
+ client
+ .mutateWith(mockJwt(jwt -> jwt.subject(name)))
+ .get()
+ .exchange()
+ .expectStatus().isOk();
+
+ SecurityContext context = securityContextController.removeSecurityContext();
+ assertThat(context.getAuthentication()).isInstanceOf(
+ JwtAuthenticationToken.class);
+ JwtAuthenticationToken token = (JwtAuthenticationToken) context.getAuthentication();
+ assertThat(token.getToken().getSubject()).isSameAs(name);
+ }
+
+ @Test
+ public void mockJwtWhenProvidingCustomAuthoritiesThenProducesJwtAuthentication() {
+ client
+ .mutateWith(mockJwt(jwt -> jwt.claim("scope", "ignored authorities"))
+ .authorities(this.authority1, this.authority2))
+ .get()
+ .exchange()
+ .expectStatus().isOk();
+
+ SecurityContext context = securityContextController.removeSecurityContext();
+ assertThat((List) context.getAuthentication().getAuthorities())
+ .containsOnly(this.authority1, this.authority2);
+ }
+
+ @Test
+ public void mockJwtWhenProvidingScopedAuthoritiesThenProducesJwtAuthentication() {
+ client
+ .mutateWith(mockJwt(jwt -> jwt.claim("scope", "scoped authorities")))
+ .get()
+ .exchange()
+ .expectStatus().isOk();
+
+ SecurityContext context = securityContextController.removeSecurityContext();
+ assertThat((List) context.getAuthentication().getAuthorities())
+ .containsOnly(new SimpleGrantedAuthority("SCOPE_scoped"),
+ new SimpleGrantedAuthority("SCOPE_authorities"));
+ }
+
+ @Test
+ public void mockJwtWhenProvidingGrantedAuthoritiesThenProducesJwtAuthentication() {
+ client
+ .mutateWith(mockJwt(jwt -> jwt.claim("scope", "ignored authorities"))
+ .authorities(jwt -> Arrays.asList(this.authority1)))
+ .get()
+ .exchange()
+ .expectStatus().isOk();
+
+ SecurityContext context = securityContextController.removeSecurityContext();
+ assertThat((List) context.getAuthentication().getAuthorities())
+ .containsOnly(this.authority1);
+ }
+}
diff --git a/test/src/test/java/org/springframework/security/test/web/reactive/server/TestController.java b/test/src/test/java/org/springframework/security/test/web/reactive/server/TestController.java
deleted file mode 100644
index 449fd6d37e..0000000000
--- a/test/src/test/java/org/springframework/security/test/web/reactive/server/TestController.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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.reactive.server;
-
-import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
-
-import java.security.Principal;
-import java.util.stream.Collectors;
-
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.MediaType;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.oauth2.jwt.Jwt;
-import org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter;
-import org.springframework.security.web.server.csrf.CsrfWebFilter;
-import org.springframework.test.web.reactive.server.WebTestClient;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-/**
- * @author Jérôme Wacongne <ch4mp@c4-soft.com>
- * @since 5.2
- */
-@RestController
-public class TestController {
-
- @GetMapping("/greet")
- public String greet(final Principal authentication) {
- return String.format("Hello, %s!", authentication.getName());
- }
-
- @GetMapping("/authorities")
- public String authentication(final Authentication authentication) {
- return authentication.getAuthorities()
- .stream()
- .map(GrantedAuthority::getAuthority)
- .collect(Collectors.toList())
- .toString();
- }
-
- @GetMapping("/jwt")
- // TODO: investigate why "@AuthenticationPrincipal Jwt token" does not work here
- public String jwt(final Authentication authentication) {
- final Jwt token = (Jwt) authentication.getPrincipal();
- final String scopes = token.getClaimAsString("scope");
-
- return String.format(
- "Hello, %s! You are sucessfully authenticated and granted with %s scopes using a Jwt.",
- token.getSubject(),
- scopes);
- }
-
- public static WebTestClient.Builder clientBuilder() {
- return WebTestClient.bindToController(new TestController())
- .webFilter(new CsrfWebFilter(), new SecurityContextServerWebExchangeWebFilter())
- .apply(springSecurity())
- .configureClient()
- .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
- }
-
- public static WebTestClient client() {
- return (WebTestClient) clientBuilder().build();
- }
-}
diff --git a/test/src/test/java/org/springframework/security/test/web/servlet/request/JwtRequestPostProcessorTests.java b/test/src/test/java/org/springframework/security/test/web/servlet/request/JwtRequestPostProcessorTests.java
deleted file mode 100644
index fcf40a9122..0000000000
--- a/test/src/test/java/org/springframework/security/test/web/servlet/request/JwtRequestPostProcessorTests.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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 static org.assertj.core.api.Assertions.assertThat;
-import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import org.springframework.security.core.context.SecurityContext;
-import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
-import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.JwtRequestPostProcessor;
-import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.SecurityContextRequestPostProcessorSupport.TestSecurityContextRepository;
-
-/**
- * @author Jérôme Wacongne <ch4mp@c4-soft.com>
- * @since 5.2
- */
-public class JwtRequestPostProcessorTests {
- @Mock
- MockHttpServletRequest request;
-
- final static String TEST_NAME = "ch4mpy";
- final static String[] TEST_AUTHORITIES = { "TEST_AUTHORITY" };
-
- @Before
- public void setup() throws Exception {
- request = new MockHttpServletRequest();
- }
-
- @Test
- public void nameAndAuthoritiesAndClaimsConfigureSecurityContextAuthentication() {
- final JwtRequestPostProcessor rpp =
- jwt().name(TEST_NAME).authorities(TEST_AUTHORITIES).scopes("test:claim");
-
- final JwtAuthenticationToken actual = (JwtAuthenticationToken) authentication(rpp.postProcessRequest(request));
-
- assertThat(actual.getName()).isEqualTo(TEST_NAME);
- assertThat(actual.getAuthorities()).containsExactlyInAnyOrder(
- new SimpleGrantedAuthority("TEST_AUTHORITY"),
- new SimpleGrantedAuthority("SCOPE_test:claim"));
- assertThat(actual.getTokenAttributes().get("scope")).isEqualTo("test:claim");
- }
-
- static Authentication authentication(final MockHttpServletRequest req) {
- final SecurityContext securityContext = (SecurityContext) req.getAttribute(TestSecurityContextRepository.ATTR_NAME);
- return securityContext == null ? null : securityContext.getAuthentication();
- }
-
-}
diff --git a/test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessorsJwtTests.java b/test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessorsJwtTests.java
new file mode 100644
index 0000000000..565de65bc0
--- /dev/null
+++ b/test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessorsJwtTests.java
@@ -0,0 +1,157 @@
+/*
+ * 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.Arrays;
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockServletContext;
+import org.springframework.security.config.BeanIds;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
+import org.springframework.security.test.context.TestSecurityContextHolder;
+import org.springframework.security.test.web.support.WebTestUtils;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+import org.springframework.security.web.FilterChainProxy;
+import org.springframework.security.web.context.SecurityContextPersistenceFilter;
+import org.springframework.security.web.context.SecurityContextRepository;
+import org.springframework.security.web.util.matcher.AnyRequestMatcher;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
+
+/**
+ * Tests for {@link SecurityMockMvcRequestPostProcessors#jwt}
+ *
+ * @author Jérôme Wacongne <ch4mp@c4-soft.com>
+ * @author Josh Cummings
+ * @since 5.2
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class SecurityMockMvcRequestPostProcessorsJwtTests {
+ @Captor
+ private ArgumentCaptor contextCaptor;
+
+ @Mock
+ private SecurityContextRepository repository;
+
+ private MockHttpServletRequest request;
+
+ @Mock
+ private GrantedAuthority authority1;
+ @Mock
+ private GrantedAuthority authority2;
+
+ @Before
+ public void setup() {
+ SecurityContextPersistenceFilter filter = new SecurityContextPersistenceFilter(this.repository);
+ MockServletContext servletContext = new MockServletContext();
+ servletContext.setAttribute(BeanIds.SPRING_SECURITY_FILTER_CHAIN,
+ new FilterChainProxy(new DefaultSecurityFilterChain(AnyRequestMatcher.INSTANCE, filter)));
+ this.request = new MockHttpServletRequest(servletContext);
+ WebTestUtils.setSecurityContextRepository(this.request, this.repository);
+ }
+
+ @After
+ public void cleanup() {
+ TestSecurityContextHolder.clearContext();
+ }
+
+ @Test
+ public void jwtWhenUsingDefaultsThenProducesDefaultJwtAuthentication() {
+ jwt().postProcessRequest(this.request);
+
+ verify(this.repository).saveContext(this.contextCaptor.capture(), eq(this.request),
+ any(HttpServletResponse.class));
+ SecurityContext context = this.contextCaptor.getValue();
+ assertThat(context.getAuthentication()).isInstanceOf(
+ JwtAuthenticationToken.class);
+ JwtAuthenticationToken token = (JwtAuthenticationToken) context.getAuthentication();
+ assertThat(token.getAuthorities()).isNotEmpty();
+ assertThat(token.getToken()).isNotNull();
+ assertThat(token.getToken().getSubject()).isEqualTo("user");
+ assertThat(token.getToken().getHeaders().get("alg")).isEqualTo("none");
+ }
+
+ @Test
+ public void jwtWhenProvidingBuilderConsumerThenProducesJwtAuthentication() {
+ String name = new String("user");
+ jwt(jwt -> jwt.subject(name)).postProcessRequest(this.request);
+
+ verify(this.repository).saveContext(this.contextCaptor.capture(), eq(this.request),
+ any(HttpServletResponse.class));
+ SecurityContext context = this.contextCaptor.getValue();
+ assertThat(context.getAuthentication()).isInstanceOf(
+ JwtAuthenticationToken.class);
+ JwtAuthenticationToken token = (JwtAuthenticationToken) context.getAuthentication();
+ assertThat(token.getToken().getSubject()).isSameAs(name);
+ }
+
+ @Test
+ public void jwtWhenProvidingCustomAuthoritiesThenProducesJwtAuthentication() {
+ jwt(jwt -> jwt.claim("scope", "ignored authorities"))
+ .authorities(this.authority1, this.authority2)
+ .postProcessRequest(this.request);
+
+ verify(this.repository).saveContext(this.contextCaptor.capture(), eq(this.request),
+ any(HttpServletResponse.class));
+ SecurityContext context = this.contextCaptor.getValue();
+ assertThat((List) context.getAuthentication().getAuthorities())
+ .containsOnly(this.authority1, this.authority2);
+ }
+
+ @Test
+ public void jwtWhenProvidingScopedAuthoritiesThenProducesJwtAuthentication() {
+ jwt(jwt -> jwt.claim("scope", "scoped authorities"))
+ .postProcessRequest(this.request);
+
+ verify(this.repository).saveContext(this.contextCaptor.capture(), eq(this.request),
+ any(HttpServletResponse.class));
+ SecurityContext context = this.contextCaptor.getValue();
+ assertThat((List) context.getAuthentication().getAuthorities())
+ .containsOnly(new SimpleGrantedAuthority("SCOPE_scoped"),
+ new SimpleGrantedAuthority("SCOPE_authorities"));
+ }
+
+ @Test
+ public void jwtWhenProvidingGrantedAuthoritiesThenProducesJwtAuthentication() {
+ jwt(jwt -> jwt.claim("scope", "ignored authorities"))
+ .authorities(jwt -> Arrays.asList(this.authority1))
+ .postProcessRequest(this.request);
+
+ verify(this.repository).saveContext(this.contextCaptor.capture(), eq(this.request),
+ any(HttpServletResponse.class));
+ SecurityContext context = this.contextCaptor.getValue();
+ assertThat((List) context.getAuthentication().getAuthorities())
+ .containsOnly(this.authority1);
+ }
+}