Add Security Tests
- Embedded LDAP - Authorization Server - Resource Server - Security with MVC - Security with WebFlux
This commit is contained in:
committed by
Sébastien Deleuze
parent
35a61264d5
commit
04e5f249a4
16
STATUS.adoc
16
STATUS.adoc
@@ -182,3 +182,19 @@ h|test
|
||||
|
||||
|===
|
||||
|
||||
== Security
|
||||
|
||||
[%header,cols="4"]
|
||||
|===
|
||||
h|Smoke Test
|
||||
h|appTest
|
||||
h|checkpointRestoreAppTest
|
||||
h|test
|
||||
|
||||
|hello-security
|
||||
|image:https://ci.spring.io/api/v1/teams/spring-checkpoint-restore-smoke-tests/pipelines/spring-checkpoint-restore-smoke-tests-3.2.x/jobs/hello-security-app-test/badge[link=https://ci.spring.io/teams/spring-checkpoint-restore-smoke-tests/pipelines/spring-checkpoint-restore-smoke-tests-3.2.x/jobs/hello-security-app-test]
|
||||
|image:https://ci.spring.io/api/v1/teams/spring-checkpoint-restore-smoke-tests/pipelines/spring-checkpoint-restore-smoke-tests-3.2.x/jobs/hello-security-cr-app-test/badge[link=https://ci.spring.io/teams/spring-checkpoint-restore-smoke-tests/pipelines/spring-checkpoint-restore-smoke-tests-3.2.x/jobs/hello-security-cr-app-test]
|
||||
|
|
||||
|
||||
|===
|
||||
|
||||
|
||||
@@ -84,3 +84,8 @@ groups:
|
||||
- name: spring-pulsar-reactive
|
||||
app_test: true
|
||||
test: false
|
||||
- name: security
|
||||
smoke_tests:
|
||||
- name: hello-security
|
||||
app_test: true
|
||||
test: false
|
||||
|
||||
1
security/security-ldap/README.md
Normal file
1
security/security-ldap/README.md
Normal file
@@ -0,0 +1 @@
|
||||
Tests Spring Security Embedded LDAP
|
||||
28
security/security-ldap/build.gradle
Normal file
28
security/security-ldap/build.gradle
Normal file
@@ -0,0 +1,28 @@
|
||||
plugins {
|
||||
id "java"
|
||||
id "org.springframework.boot"
|
||||
id "org.springframework.cr.smoke-test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
constraints {
|
||||
implementation("org.springframework.security:spring-security-ldap:6.2.0-SNAPSHOT")
|
||||
}
|
||||
|
||||
implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||
implementation("org.springframework.security:spring-security-ldap")
|
||||
implementation("com.unboundid:unboundid-ldapsdk")
|
||||
implementation("org.crac:crac:$cracVersion")
|
||||
implementation(project(":cr-listener"))
|
||||
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
testImplementation("org.springframework.security:spring-security-test")
|
||||
|
||||
appTestImplementation(project(":cr-smoke-test-support"))
|
||||
}
|
||||
|
||||
crSmokeTest {
|
||||
webApplication = true
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.example.security.ldap;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cr.smoketest.support.junit.ApplicationTest;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ApplicationTest
|
||||
public class SecurityLdapApplicationCheckpointTests {
|
||||
|
||||
@Test
|
||||
void anonymousShouldBeUnauthorizedWithoutCredentials(WebTestClient client) {
|
||||
client.get().uri("/").exchange().expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
void homeShouldShowUsername(WebTestClient client) {
|
||||
client.get()
|
||||
.uri("/")
|
||||
.headers((header) -> header.setBasicAuth("user", "password"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody()
|
||||
.consumeWith((result) -> assertThat(new String(result.getResponseBodyContent())).isEqualTo("Hello, user!"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void friendlyShouldShowGivenName(WebTestClient client) {
|
||||
client.get()
|
||||
.uri("/friendly")
|
||||
.headers((header) -> header.setBasicAuth("user", "password"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody()
|
||||
.consumeWith((result) -> assertThat(new String(result.getResponseBodyContent()))
|
||||
.isEqualTo("Hello, Dianne Emu!"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.example.security.ldap;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.ldap.userdetails.Person;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class MainController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String hello(Authentication authentication) {
|
||||
return "Hello, " + authentication.getName() + "!";
|
||||
}
|
||||
|
||||
@GetMapping("/friendly")
|
||||
public String hello(@AuthenticationPrincipal Person person) {
|
||||
return "Hello, " + person.getGivenName() + "!";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.example.security.ldap;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.ldap.LdapBindAuthenticationManagerFactory;
|
||||
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
|
||||
import org.springframework.security.ldap.server.UnboundIdContainer;
|
||||
import org.springframework.security.ldap.userdetails.PersonContextMapper;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
UnboundIdContainer ldapContainer() throws Exception {
|
||||
UnboundIdContainer container = new UnboundIdContainer("dc=springframework,dc=org", "classpath:users.ldif");
|
||||
container.setPort(0);
|
||||
return container;
|
||||
}
|
||||
|
||||
@Bean
|
||||
BaseLdapPathContextSource contextSource(UnboundIdContainer container) {
|
||||
int port = container.getPort();
|
||||
return new DefaultSpringSecurityContextSource("ldap://localhost:" + port + "/dc=springframework,dc=org");
|
||||
}
|
||||
|
||||
@Bean
|
||||
AuthenticationManager ldapAuthenticationManager(BaseLdapPathContextSource contextSource) {
|
||||
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
|
||||
factory.setUserDnPatterns("uid={0},ou=people");
|
||||
factory.setUserDetailsContextMapper(new PersonContextMapper());
|
||||
return factory.createAuthenticationManager();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.example.security.ldap;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SecurityLdapApplication {
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
SpringApplication.run(SecurityLdapApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
43
security/security-ldap/src/main/resources/users.ldif
Normal file
43
security/security-ldap/src/main/resources/users.ldif
Normal file
@@ -0,0 +1,43 @@
|
||||
dn: ou=groups,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: organizationalUnit
|
||||
ou: groups
|
||||
|
||||
dn: ou=people,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: organizationalUnit
|
||||
ou: people
|
||||
|
||||
dn: uid=admin,ou=people,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: person
|
||||
objectclass: organizationalPerson
|
||||
objectclass: inetOrgPerson
|
||||
cn: Rod Johnson
|
||||
sn: Johnson
|
||||
uid: admin
|
||||
userPassword: password
|
||||
|
||||
dn: uid=user,ou=people,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: person
|
||||
objectclass: organizationalPerson
|
||||
objectclass: inetOrgPerson
|
||||
cn: Dianne Emu
|
||||
sn: Emu
|
||||
uid: user
|
||||
userPassword: password
|
||||
givenName: Dianne Emu
|
||||
|
||||
dn: cn=user,ou=groups,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: groupOfNames
|
||||
cn: user
|
||||
member: uid=admin,ou=people,dc=springframework,dc=org
|
||||
member: uid=user,ou=people,dc=springframework,dc=org
|
||||
|
||||
dn: cn=admin,ou=groups,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: groupOfNames
|
||||
cn: admin
|
||||
member: uid=admin,ou=people,dc=springframework,dc=org
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.example.security.ldap;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
|
||||
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;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
public class SecurityLdapApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mvc;
|
||||
|
||||
@Test
|
||||
void rootWhenAuthenticatedThenSaysHelloUser() throws Exception {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/")
|
||||
.with(httpBasic("user", "password")))
|
||||
.andExpect(content().string("Hello, user!"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void rootWhenUnauthenticatedThen401() throws Exception {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/"))
|
||||
.andExpect(status().isUnauthorized());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void tokenWhenBadCredentialsThen401() throws Exception {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/")
|
||||
.with(httpBasic("user", "passwerd")))
|
||||
.andExpect(status().isUnauthorized());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
1
security/security-oauth2-authorization-server/README.md
Normal file
1
security/security-oauth2-authorization-server/README.md
Normal file
@@ -0,0 +1 @@
|
||||
Tests Spring Security OAuth2 Authorization Server
|
||||
24
security/security-oauth2-authorization-server/build.gradle
Normal file
24
security/security-oauth2-authorization-server/build.gradle
Normal file
@@ -0,0 +1,24 @@
|
||||
plugins {
|
||||
id "java"
|
||||
id "org.springframework.boot"
|
||||
id "org.springframework.cr.smoke-test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
implementation("org.springframework.security:spring-security-oauth2-authorization-server:1.0.0-SNAPSHOT")
|
||||
implementation("org.crac:crac:$cracVersion")
|
||||
implementation(project(":cr-listener"))
|
||||
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
testImplementation("org.springframework.security:spring-security-test")
|
||||
|
||||
appTestImplementation("com.fasterxml.jackson.core:jackson-databind")
|
||||
appTestImplementation(project(":cr-smoke-test-support"))
|
||||
}
|
||||
|
||||
crSmokeTest {
|
||||
webApplication = true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
package com.example.security.oauth2authorizationserver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cr.smoketest.support.junit.ApplicationTest;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
|
||||
@ApplicationTest
|
||||
class OAuth2AuthorizationServerApplicationCheckpointTests {
|
||||
|
||||
private static final String CLIENT_ID = "messaging-client";
|
||||
|
||||
private static final String CLIENT_SECRET = "secret";
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Test
|
||||
void performTokenRequestWhenValidClientCredentialsThenOk(WebTestClient client) {
|
||||
// @formatter:off
|
||||
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
|
||||
formData.add("grant_type", "client_credentials");
|
||||
formData.add("scope", "message:read");
|
||||
client.post().uri("/oauth2/token").headers((headers) -> headers.setBasicAuth(CLIENT_ID, CLIENT_SECRET))
|
||||
.body(BodyInserters.fromFormData(formData))
|
||||
.exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$.access_token").exists()
|
||||
.jsonPath("$.expires_in").isNumber()
|
||||
.jsonPath("$.scope").isEqualTo("message:read")
|
||||
.jsonPath("$.token_type").isEqualTo("Bearer");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void performTokenRequestWhenMissingScopeThenOk(WebTestClient client) {
|
||||
// @formatter:off
|
||||
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
|
||||
formData.add("grant_type", "client_credentials");
|
||||
formData.add("scope", "message:read message:write");
|
||||
client.post().uri("/oauth2/token").headers((headers) -> headers.setBasicAuth(CLIENT_ID, CLIENT_SECRET))
|
||||
.body(BodyInserters.fromFormData(formData))
|
||||
.exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$.access_token").exists()
|
||||
.jsonPath("$.expires_in").isNumber()
|
||||
.jsonPath("$.scope").isEqualTo("message:read message:write")
|
||||
.jsonPath("$.token_type").isEqualTo("Bearer");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void performTokenRequestWhenInvalidClientCredentialsThenUnauthorized(WebTestClient client) {
|
||||
// @formatter:off
|
||||
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
|
||||
formData.add("grant_type", "client_credentials");
|
||||
formData.add("scope", "message:read");
|
||||
client.post().uri("/oauth2/token").headers(badCredentials())
|
||||
.body(BodyInserters.fromFormData(formData))
|
||||
.exchange().expectStatus().isUnauthorized()
|
||||
.expectBody().jsonPath("$.error").isEqualTo("invalid_client");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void performTokenRequestWhenMissingGrantTypeThenUnauthorized(WebTestClient client) {
|
||||
// @formatter:off
|
||||
client.post().uri("/oauth2/token").headers(badCredentials())
|
||||
.exchange().expectStatus().isUnauthorized()
|
||||
.expectBody().jsonPath("$.error").isEqualTo("invalid_client");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void performTokenRequestWhenGrantTypeNotRegisteredThenBadRequest(WebTestClient client) {
|
||||
// @formatter:off
|
||||
client.post().uri("/oauth2/token").headers((headers) -> headers.setBasicAuth("login-client", "openid-connect"))
|
||||
.body(BodyInserters.fromFormData("grant_type", "client_credentials"))
|
||||
.exchange().expectStatus().isBadRequest()
|
||||
.expectBody().jsonPath("$.error").isEqualTo("unauthorized_client");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void performIntrospectionRequestWhenValidTokenThenOk(WebTestClient client) throws Exception {
|
||||
// @formatter:off
|
||||
client.post().uri("/oauth2/introspect").headers((headers) -> headers.setBasicAuth(CLIENT_ID, CLIENT_SECRET))
|
||||
.body(BodyInserters.fromFormData("token", getAccessToken(client)))
|
||||
.exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$.active").isEqualTo("true")
|
||||
.jsonPath("$.aud[0]").isEqualTo(CLIENT_ID)
|
||||
.jsonPath("$.client_id").isEqualTo(CLIENT_ID)
|
||||
.jsonPath("$.exp").isNumber()
|
||||
.jsonPath("$.iat").isNumber()
|
||||
.jsonPath("$.iss").isEqualTo("http://localhost:9000")
|
||||
.jsonPath("$.nbf").isNumber()
|
||||
.jsonPath("$.scope").isEqualTo("message:read")
|
||||
.jsonPath("$.sub").isEqualTo(CLIENT_ID)
|
||||
.jsonPath("$.token_type").isEqualTo("Bearer");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void performIntrospectionRequestWhenInvalidCredentialsThenUnauthorized(WebTestClient client) throws Exception {
|
||||
// @formatter:off
|
||||
client.post().uri("/oauth2/introspect").headers(badCredentials())
|
||||
.body(BodyInserters.fromFormData("token", getAccessToken(client)))
|
||||
.exchange().expectStatus().isUnauthorized()
|
||||
.expectBody().jsonPath("$.error").isEqualTo("invalid_client");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private static Consumer<HttpHeaders> badCredentials() {
|
||||
return (headers) -> headers.setBasicAuth("bad", "password");
|
||||
}
|
||||
|
||||
private String getAccessToken(WebTestClient client) throws IOException {
|
||||
// @formatter:off
|
||||
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
|
||||
formData.add("grant_type", "client_credentials");
|
||||
formData.add("scope", "message:read");
|
||||
byte[] responseBody = client.post().uri("/oauth2/token")
|
||||
.headers((headers) -> headers.setBasicAuth(CLIENT_ID, CLIENT_SECRET))
|
||||
.body(BodyInserters.fromFormData(formData))
|
||||
.exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$.access_token").exists()
|
||||
.returnResult().getResponseBody();
|
||||
// @formatter:on
|
||||
|
||||
Map<String, Object> tokenResponse = this.objectMapper.readValue(responseBody, new TypeReference<>() {
|
||||
});
|
||||
|
||||
return tokenResponse.get("access_token").toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2023 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 com.example.security.oauth2authorizationserver;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* OAuth Authorization Server Application.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class OAuth2AuthorizationServerApplication {
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
SpringApplication.run(OAuth2AuthorizationServerApplication.class, args);
|
||||
Thread.currentThread().join(); // To be able to measure memory consumption
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright 2023 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 com.example.security.oauth2authorizationserver;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Role;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
/**
|
||||
* OAuth Authorization Server Configuration.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class OAuth2AuthorizationServerSecurityConfiguration {
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
|
||||
return http.formLogin(Customizer.withDefaults()).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(2)
|
||||
public SecurityFilterChain standardSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(Customizer.withDefaults());
|
||||
// @formatter:on
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RegisteredClientRepository registeredClientRepository() {
|
||||
// @formatter:off
|
||||
RegisteredClient loginClient = RegisteredClient.withId(UUID.randomUUID().toString())
|
||||
.clientId("login-client")
|
||||
.clientSecret("{noop}openid-connect")
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
||||
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/login-client")
|
||||
.redirectUri("http://127.0.0.1:8080/authorized")
|
||||
.scope(OidcScopes.OPENID)
|
||||
.scope(OidcScopes.PROFILE)
|
||||
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
|
||||
.build();
|
||||
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
|
||||
.clientId("messaging-client")
|
||||
.clientSecret("{noop}secret")
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
||||
.scope("message:read")
|
||||
.scope("message:write")
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
return new InMemoryRegisteredClientRepository(loginClient, registeredClient);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JWKSource<SecurityContext> jwkSource(KeyPair keyPair) {
|
||||
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
|
||||
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
|
||||
// @formatter:off
|
||||
RSAKey rsaKey = new RSAKey.Builder(publicKey)
|
||||
.privateKey(privateKey)
|
||||
.keyID(UUID.randomUUID().toString())
|
||||
.build();
|
||||
// @formatter:on
|
||||
JWKSet jwkSet = new JWKSet(rsaKey);
|
||||
return new ImmutableJWKSet<>(jwkSet);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtDecoder jwtDecoder(KeyPair keyPair) {
|
||||
return NimbusJwtDecoder.withPublicKey((RSAPublicKey) keyPair.getPublic()).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthorizationServerSettings providerSettings() {
|
||||
return AuthorizationServerSettings.builder().issuer("http://localhost:9000").build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() {
|
||||
// @formatter:off
|
||||
UserDetails userDetails = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
return new InMemoryUserDetailsManager(userDetails);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
KeyPair generateRsaKey() {
|
||||
KeyPair keyPair;
|
||||
try {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
keyPairGenerator.initialize(2048);
|
||||
keyPair = keyPairGenerator.generateKeyPair();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
}
|
||||
1
security/security-oauth2-resource-server/README.md
Normal file
1
security/security-oauth2-resource-server/README.md
Normal file
@@ -0,0 +1 @@
|
||||
Tests Spring Security OAuth2 Resource Server
|
||||
23
security/security-oauth2-resource-server/build.gradle
Normal file
23
security/security-oauth2-resource-server/build.gradle
Normal file
@@ -0,0 +1,23 @@
|
||||
plugins {
|
||||
id "java"
|
||||
id "org.springframework.boot"
|
||||
id "org.springframework.cr.smoke-test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
|
||||
implementation("com.squareup.okhttp3:mockwebserver")
|
||||
implementation("org.crac:crac:$cracVersion")
|
||||
implementation(project(":cr-listener"))
|
||||
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
testImplementation("org.springframework.security:spring-security-test")
|
||||
|
||||
appTestImplementation(project(":cr-smoke-test-support"))
|
||||
}
|
||||
|
||||
crSmokeTest {
|
||||
webApplication = true
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.example.security.oauth2resourceserver;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cr.smoketest.support.junit.ApplicationTest;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ApplicationTest
|
||||
public class OAuth2ResourceServerApplicationCheckpointTests {
|
||||
|
||||
private static final String NONE_JWT = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWJqZWN0IiwiZXhwIjoyMTY0MjQ1ODgwLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiMDFkOThlZWEtNjc0MC00OGRlLTk4ODAtYzM5ZjgyMGZiNzVlIiwiY2xpZW50X2lkIjoibm9zY29wZXMiLCJzY29wZSI6WyJub25lIl19.VOzgGLOUuQ_R2Ur1Ke41VaobddhKgUZgto7Y3AGxst7SuxLQ4LgWwdSSDRx-jRvypjsCgYPbjAYLhn9nCbfwtCitkymUKUNKdebvVAI0y8YvliWTL5S-GiJD9dN8SSsXUla9A4xB_9Mt5JAlRpQotQSCLojVSKQmjhMpQWmYAlKVjnlImoRwQFPI4w3Ijn4G4EMTKWUYRfrD0-WNT9ZYWBeza6QgV6sraP7ToRB3eQLy2p04cU40X-RHLeYCsMBfxsMMh89CJff-9tn7VDKi1hAGc_Lp9yS9ZaItJuFJTjf8S_vsjVB1nBhvdS_6IED_m_fOU52KiGSO2qL6shxHvg";
|
||||
|
||||
private static final String READ_JWT = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWJqZWN0IiwiZXhwIjoyMTY0MjQ1NjQ4LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiY2I1ZGMwNDYtMDkyMi00ZGJmLWE5MzAtOGI2M2FhZTYzZjk2IiwiY2xpZW50X2lkIjoicmVhZGVyIiwic2NvcGUiOlsibWVzc2FnZTpyZWFkIl19.Pre2ksnMiOGYWQtuIgHB0i3uTnNzD0SMFM34iyQJHK5RLlSjge08s9qHdx6uv5cZ4gZm_cB1D6f4-fLx76bCblK6mVcabbR74w_eCdSBXNXuqG-HNrOYYmmx5iJtdwx5fXPmF8TyVzsq_LvRm_LN4lWNYquT4y36Tox6ZD3feYxXvHQ3XyZn9mVKnlzv-GCwkBohCR3yPow5uVmr04qh_al52VIwKMrvJBr44igr4fTZmzwRAZmQw5rZeyep0b4nsCjadNcndHtMtYKNVuG5zbDLsB7GGvilcI9TDDnUXtwthB_3iq32DAd9x8wJmJ5K8gmX6GjZFtYzKk_zEboXoQ";
|
||||
|
||||
private static final String WRITE_JWT = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWJqZWN0IiwiZXhwIjoyMTY0MjQzOTA0LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiZGI4ZjgwMzQtM2VlNy00NjBjLTk3NTEtMDJiMDA1OWI5NzA4IiwiY2xpZW50X2lkIjoid3JpdGVyIiwic2NvcGUiOlsibWVzc2FnZTp3cml0ZSJdfQ.USvpx_ntKXtchLmc93auJq0qSav6vLm4B7ItPzhrDH2xmogBP35eKeklwXK5GCb7ck1aKJV5SpguBlTCz0bZC1zAWKB6gyFIqedALPAran5QR-8WpGfl0wFqds7d8Jw3xmpUUBduRLab9hkeAhgoVgxevc8d6ITM7kRnHo5wT3VzvBU8DquedVXm5fbBnRPgG4_jOWJKbqYpqaR2z2TnZRWh3CqL82Orh1Ww1dJYF_fae1dTVV4tvN5iSndYcGxMoBaiw3kRRi6EyNxnXnt1pFtZqc1f6D9x4AHiri8_vpBp2vwG5OfQD5-rrleP_XlIB3rNQT7tu3fiqu4vUzQaEg";
|
||||
|
||||
@Test
|
||||
void shouldRespondWhenTokenPresent(WebTestClient client) {
|
||||
client.get()
|
||||
.uri("/")
|
||||
.header("Authorization", bearer(NONE_JWT))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody()
|
||||
.consumeWith(
|
||||
(result) -> assertThat(new String(result.getResponseBodyContent())).isEqualTo("Hello, subject!"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAllowGetWhenTokenWithReadScope(WebTestClient client) {
|
||||
client.get()
|
||||
.uri("/message")
|
||||
.header("Authorization", bearer(READ_JWT))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody()
|
||||
.consumeWith(
|
||||
(result) -> assertThat(new String(result.getResponseBodyContent())).isEqualTo("secret message"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBlockGetWhenTokenWithNoScope(WebTestClient client) {
|
||||
client.get().uri("/message").header("Authorization", bearer(NONE_JWT)).exchange().expectStatus().isForbidden();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAllowPostWhenTokenWithScope(WebTestClient client) {
|
||||
client.post()
|
||||
.uri("/message")
|
||||
.header("Authorization", bearer(WRITE_JWT))
|
||||
.bodyValue("my message")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody()
|
||||
.consumeWith((result) -> assertThat(new String(result.getResponseBodyContent()))
|
||||
.isEqualTo("Message was created. Content: my message"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBlockPostWhenTokenWithBoScope(WebTestClient client) {
|
||||
client.post()
|
||||
.uri("/message")
|
||||
.header("Authorization", bearer(NONE_JWT))
|
||||
.bodyValue("my message")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isForbidden();
|
||||
}
|
||||
|
||||
private static String bearer(String token) {
|
||||
return "Bearer " + token;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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 com.example.security.oauth2resourceserver;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class OAuth2ResourceServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(OAuth2ResourceServerApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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 com.example.security.oauth2resourceserver;
|
||||
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
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;
|
||||
|
||||
@RestController
|
||||
public class OAuth2ResourceServerController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String index(@AuthenticationPrincipal Jwt jwt) {
|
||||
return String.format("Hello, %s!", jwt.getSubject());
|
||||
}
|
||||
|
||||
@GetMapping("/message")
|
||||
public String message() {
|
||||
return "secret message";
|
||||
}
|
||||
|
||||
@PostMapping("/message")
|
||||
public String createMessage(@RequestBody String message) {
|
||||
return String.format("Message was created. Content: %s", message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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 com.example.security.oauth2resourceserver;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableWebSecurity
|
||||
public class OAuth2ResourceServerSecurityConfiguration {
|
||||
|
||||
@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
|
||||
String jwkSetUri;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.requestMatchers(HttpMethod.GET, "/message/**").hasAuthority("SCOPE_message:read")
|
||||
.requestMatchers(HttpMethod.POST, "/message/**").hasAuthority("SCOPE_message:write")
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer((jwt) -> jwt.jwt(Customizer.withDefaults()));
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
JwtDecoder jwtDecoder() {
|
||||
return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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 com.example.security.oauth2resourceserver.env;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
|
||||
/**
|
||||
* Adds {@link MockWebServerPropertySource} to the environment.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class MockWebServerEnvironmentPostProcessor implements EnvironmentPostProcessor, DisposableBean {
|
||||
|
||||
private final MockWebServerPropertySource propertySource = new MockWebServerPropertySource();
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||
environment.getPropertySources().addFirst(this.propertySource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
this.propertySource.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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 com.example.security.oauth2resourceserver.env;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import okhttp3.mockwebserver.Dispatcher;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.crac.Context;
|
||||
import org.crac.Core;
|
||||
import org.crac.Resource;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
* Adds value for mockwebserver.url property.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class MockWebServerPropertySource extends PropertySource<AtomicReference<MockWebServer>>
|
||||
implements DisposableBean, Resource {
|
||||
|
||||
private static final MockResponse JWKS_RESPONSE = response(
|
||||
"{ \"keys\": [ { \"kty\": \"RSA\", \"e\": \"AQAB\", \"n\": \"jvBtqsGCOmnYzwe_-HvgOqlKk6HPiLEzS6uCCcnVkFXrhnkPMZ-uQXTR0u-7ZklF0XC7-AMW8FQDOJS1T7IyJpCyeU4lS8RIf_Z8RX51gPGnQWkRvNw61RfiSuSA45LR5NrFTAAGoXUca_lZnbqnl0td-6hBDVeHYkkpAsSck1NPhlcsn-Pvc2Vleui_Iy1U2mzZCM1Vx6Dy7x9IeP_rTNtDhULDMFbB_JYs-Dg6Zd5Ounb3mP57tBGhLYN7zJkN1AAaBYkElsc4GUsGsUWKqgteQSXZorpf6HdSJsQMZBDd7xG8zDDJ28hGjJSgWBndRGSzQEYU09Xbtzk-8khPuw\" } ] }",
|
||||
200);
|
||||
|
||||
private static final MockResponse NOT_FOUND_RESPONSE = response(
|
||||
"{ \"message\" : \"This mock authorization server responds to just one request: GET /.well-known/jwks.json.\" }",
|
||||
404);
|
||||
|
||||
/**
|
||||
* Name of the random {@link PropertySource}.
|
||||
*/
|
||||
public static final String MOCK_WEB_SERVER_PROPERTY_SOURCE_NAME = "mockwebserver";
|
||||
|
||||
private static final String NAME = "mockwebserver.url";
|
||||
|
||||
private static final Log logger = LogFactory.getLog(MockWebServerPropertySource.class);
|
||||
|
||||
private boolean started;
|
||||
|
||||
private int port;
|
||||
|
||||
public MockWebServerPropertySource() {
|
||||
super(MOCK_WEB_SERVER_PROPERTY_SOURCE_NAME, new AtomicReference<>(new MockWebServer()));
|
||||
logger.info("Initializing MockWebServerPropertySource");
|
||||
Core.getGlobalContext().register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProperty(String name) {
|
||||
if (!name.equals(NAME)) {
|
||||
return null;
|
||||
}
|
||||
logger.trace("Looking up the url for '%s'".formatted(name));
|
||||
try {
|
||||
String url = getUrl();
|
||||
logger.trace("Property value: '%s' = '%s'".formatted(name, url));
|
||||
return url;
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
logger.error("Failed to get property value for '%s'".formatted(name), ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
getSource().get().shutdown();
|
||||
this.started = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeCheckpoint(Context<? extends Resource> context) throws Exception {
|
||||
destroy();
|
||||
getSource().set(new MockWebServer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRestore(Context<? extends Resource> context) throws Exception {
|
||||
getUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL (i.e. "http://localhost:123456")
|
||||
* @return the url with the dynamic port
|
||||
*/
|
||||
private String getUrl() {
|
||||
MockWebServer mockWebServer = getSource().get();
|
||||
if (!this.started) {
|
||||
initializeMockWebServer(mockWebServer);
|
||||
}
|
||||
String url = mockWebServer.url("").url().toExternalForm();
|
||||
return url.substring(0, url.length() - 1);
|
||||
}
|
||||
|
||||
private void initializeMockWebServer(MockWebServer mockWebServer) {
|
||||
Dispatcher dispatcher = new Dispatcher() {
|
||||
@Override
|
||||
public MockResponse dispatch(RecordedRequest request) {
|
||||
if ("/.well-known/jwks.json".equals(request.getPath())) {
|
||||
return JWKS_RESPONSE;
|
||||
}
|
||||
|
||||
return NOT_FOUND_RESPONSE;
|
||||
}
|
||||
};
|
||||
|
||||
mockWebServer.setDispatcher(dispatcher);
|
||||
try {
|
||||
mockWebServer.start(this.port);
|
||||
this.started = true;
|
||||
this.port = mockWebServer.getPort();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException("Could not start " + mockWebServer, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static MockResponse response(String body, int status) {
|
||||
return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.setResponseCode(status)
|
||||
.setBody(body);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.springframework.boot.env.EnvironmentPostProcessor=com.example.security.oauth2resourceserver.env.MockWebServerEnvironmentPostProcessor
|
||||
@@ -0,0 +1,7 @@
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
resourceserver:
|
||||
jwt:
|
||||
jwk-set-uri: ${mockwebserver.url}/.well-known/jwks.json
|
||||
|
||||
1
security/security-webflux/README.adoc
Normal file
1
security/security-webflux/README.adoc
Normal file
@@ -0,0 +1 @@
|
||||
Tests the `SecurityFilterChain` from Spring Security together with Spring WebFlux.
|
||||
21
security/security-webflux/build.gradle
Normal file
21
security/security-webflux/build.gradle
Normal file
@@ -0,0 +1,21 @@
|
||||
plugins {
|
||||
id "java"
|
||||
id "org.springframework.boot"
|
||||
id "org.springframework.cr.smoke-test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
|
||||
implementation("org.springframework.boot:spring-boot-starter-webflux")
|
||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||
implementation("org.crac:crac:$cracVersion")
|
||||
implementation(project(":cr-listener"))
|
||||
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
|
||||
appTestImplementation(project(":cr-smoke-test-support"))
|
||||
}
|
||||
|
||||
crSmokeTest {
|
||||
webApplication = true
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2023 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 com.example.security.webflux;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cr.smoketest.support.junit.ApplicationTest;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ApplicationTest
|
||||
class SecurityWebFluxApplicationCheckpointTests {
|
||||
|
||||
@Test
|
||||
void anonymousShouldBeAccessibleWithoutCredentials(WebTestClient client) {
|
||||
client.get()
|
||||
.uri("/rest/anonymous")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody()
|
||||
.consumeWith((result) -> assertThat(new String(result.getResponseBodyContent())).isEqualTo("anonymous"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizedShouldBeProtectedWithoutCredentials(WebTestClient client) {
|
||||
client.get().uri("/rest/authorized").exchange().expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizedShouldBeAccessibleWithCredentials(WebTestClient client) {
|
||||
client.get()
|
||||
.uri("/rest/authorized")
|
||||
.headers((header) -> header.setBasicAuth("user", "password"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody()
|
||||
.consumeWith(
|
||||
(result) -> assertThat(new String(result.getResponseBodyContent())).isEqualTo("authorized: user"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizedShouldBeProtectedWithWrongCredentials(WebTestClient client) {
|
||||
client.get()
|
||||
.uri("/rest/authorized")
|
||||
.headers((header) -> header.setBasicAuth("wrong-user", "wrong-password"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
void adminShouldBeProtectedWithoutCredentials(WebTestClient client) {
|
||||
client.get().uri("/rest/admin").exchange().expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
void adminShouldBeAccessibleWithCredentials(WebTestClient client) {
|
||||
client.get()
|
||||
.uri("/rest/admin")
|
||||
.headers((header) -> header.setBasicAuth("admin", "password"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody()
|
||||
.consumeWith((result) -> assertThat(new String(result.getResponseBodyContent())).isEqualTo("admin: admin"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void adminShouldBeProtectedWithWrongCredentials(WebTestClient client) {
|
||||
client.get()
|
||||
.uri("/rest/admin")
|
||||
.headers((header) -> header.setBasicAuth("wrong-admin", "wrong-password"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
void adminShouldBeProtectedWithWrongRole(WebTestClient client) {
|
||||
client.get()
|
||||
.uri("/rest/admin")
|
||||
.headers((header) -> header.setBasicAuth("user", "password"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isForbidden();
|
||||
}
|
||||
|
||||
@Test
|
||||
void staticResourcesShouldBeProtected(WebTestClient client) {
|
||||
client.get().uri("/foo.html").exchange().expectStatus().isUnauthorized();
|
||||
client.get().uri("/bar.html").exchange().expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.example.security.webflux;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SecurityWebFluxApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SecurityWebFluxApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.example.security.webflux;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(path = "/rest", produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
public class TestRestController {
|
||||
|
||||
@GetMapping("/anonymous")
|
||||
public Mono<String> anonymous() {
|
||||
return Mono.just("anonymous");
|
||||
}
|
||||
|
||||
@GetMapping("/authorized")
|
||||
public Mono<String> authorized(Principal principal) {
|
||||
return Mono.just("authorized: " + principal.getName());
|
||||
}
|
||||
|
||||
@GetMapping("/admin")
|
||||
public Mono<String> admin(Principal principal) {
|
||||
return Mono.just("admin: " + principal.getName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.example.security.webflux;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
public class WebSecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
|
||||
return http
|
||||
.authorizeExchange((exchanges) -> exchanges.pathMatchers("/rest/anonymous")
|
||||
.permitAll()
|
||||
.pathMatchers("/rest/admin")
|
||||
.hasRole("ADMIN")
|
||||
.anyExchange()
|
||||
.authenticated())
|
||||
.httpBasic(Customizer.withDefaults())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ReactiveUserDetailsService userDetailsService() {
|
||||
UserDetails user = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
UserDetails admin = User.withDefaultPasswordEncoder()
|
||||
.username("admin")
|
||||
.password("password")
|
||||
.roles("ADMIN")
|
||||
.build();
|
||||
|
||||
return new MapReactiveUserDetailsService(user, admin);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Bar
|
||||
@@ -0,0 +1 @@
|
||||
Foo
|
||||
1
security/security-webmvc/README.adoc
Normal file
1
security/security-webmvc/README.adoc
Normal file
@@ -0,0 +1 @@
|
||||
Tests the `SecurityFilterChain` from Spring Security together with Spring WebMVC.
|
||||
21
security/security-webmvc/build.gradle
Normal file
21
security/security-webmvc/build.gradle
Normal file
@@ -0,0 +1,21 @@
|
||||
plugins {
|
||||
id "java"
|
||||
id "org.springframework.boot"
|
||||
id "org.springframework.cr.smoke-test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||
implementation("org.crac:crac:$cracVersion")
|
||||
implementation(project(":cr-listener"))
|
||||
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
|
||||
appTestImplementation(project(":cr-smoke-test-support"))
|
||||
}
|
||||
|
||||
crSmokeTest {
|
||||
webApplication = true
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2023 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 com.example.security.webmvc;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cr.smoketest.support.junit.ApplicationTest;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ApplicationTest
|
||||
class SecurityWebMvcApplicationCheckpointTests {
|
||||
|
||||
@Test
|
||||
void anonymousShouldBeAccessibleWithoutCredentials(WebTestClient client) {
|
||||
client.get()
|
||||
.uri("/rest/anonymous")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody()
|
||||
.consumeWith((result) -> assertThat(new String(result.getResponseBodyContent())).isEqualTo("anonymous"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizedShouldBeProtectedWithoutCredentials(WebTestClient client) {
|
||||
client.get().uri("/rest/authorized").exchange().expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizedShouldBeAccessibleWithCredentials(WebTestClient client) {
|
||||
client.get()
|
||||
.uri("/rest/authorized")
|
||||
.headers((header) -> header.setBasicAuth("user", "password"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody()
|
||||
.consumeWith(
|
||||
(result) -> assertThat(new String(result.getResponseBodyContent())).isEqualTo("authorized: user"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizedShouldBeProtectedWithWrongCredentials(WebTestClient client) {
|
||||
client.get()
|
||||
.uri("/rest/authorized")
|
||||
.headers((header) -> header.setBasicAuth("wrong-user", "wrong-password"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
void adminShouldBeProtectedWithoutCredentials(WebTestClient client) {
|
||||
client.get().uri("/rest/admin").exchange().expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
void adminShouldBeAccessibleWithCredentials(WebTestClient client) {
|
||||
client.get()
|
||||
.uri("/rest/admin")
|
||||
.headers((header) -> header.setBasicAuth("admin", "password"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody()
|
||||
.consumeWith((result) -> assertThat(new String(result.getResponseBodyContent())).isEqualTo("admin: admin"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void adminShouldBeProtectedWithWrongCredentials(WebTestClient client) {
|
||||
client.get()
|
||||
.uri("/rest/admin")
|
||||
.headers((header) -> header.setBasicAuth("wrong-admin", "wrong-password"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
void adminShouldBeProtectedWithWrongRole(WebTestClient client) {
|
||||
client.get()
|
||||
.uri("/rest/admin")
|
||||
.headers((header) -> header.setBasicAuth("user", "password"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isForbidden();
|
||||
}
|
||||
|
||||
@Test
|
||||
void staticResourcesShouldBeProtected(WebTestClient client) {
|
||||
client.get().uri("/foo.html").exchange().expectStatus().isUnauthorized();
|
||||
client.get().uri("/bar.html").exchange().expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.example.security.webmvc;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SecurityWebMvcApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SecurityWebMvcApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.example.security.webmvc;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(path = "/rest", produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
public class TestRestController {
|
||||
|
||||
@GetMapping("/anonymous")
|
||||
public String anonymous() {
|
||||
return "anonymous";
|
||||
}
|
||||
|
||||
@GetMapping("/authorized")
|
||||
public String authorized(Principal principal) {
|
||||
return "authorized: " + principal.getName();
|
||||
}
|
||||
|
||||
@GetMapping("/admin")
|
||||
public String admin(Principal principal) {
|
||||
return "admin: " + principal.getName();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.example.security.webmvc;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class WebSecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
return http.authorizeHttpRequests(authorize -> authorize.forServletPattern("/",
|
||||
(r) -> r.requestMatchers("/rest/anonymous")
|
||||
.permitAll()
|
||||
.requestMatchers("/rest/admin")
|
||||
.hasRole("ADMIN")
|
||||
.anyRequest()
|
||||
.authenticated()))
|
||||
.httpBasic(Customizer.withDefaults())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() {
|
||||
UserDetails user = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
UserDetails admin = User.withDefaultPasswordEncoder()
|
||||
.username("admin")
|
||||
.password("password")
|
||||
.roles("ADMIN")
|
||||
.build();
|
||||
|
||||
return new InMemoryUserDetailsManager(user, admin);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Bar
|
||||
@@ -0,0 +1 @@
|
||||
Foo
|
||||
@@ -27,8 +27,8 @@ rootProject.name="spring-checkpoint-restore-smoke-tests"
|
||||
include "cr-smoke-test-support"
|
||||
include "cr-listener"
|
||||
|
||||
["boot", "cloud", "data", "framework", "integration"].each { group ->
|
||||
["boot", "cloud", "data", "framework", "integration", "security"].each { group ->
|
||||
file(group).eachDirMatch(~/[a-z].*/) { smokeTest ->
|
||||
include "$group:${smokeTest.name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user