mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-25 13:32:30 +00:00
Update x509 Reference
- Use include-code - Demo how to customize SubjectX500PrincipalExtractor
This commit is contained in:
parent
7bf2730a53
commit
e3add59550
@ -18,3 +18,4 @@ asciidoc:
|
||||
gh-url: "https://github.com/spring-projects/spring-security/tree/{gh-tag}"
|
||||
include-java: 'example$docs-src/test/java/org/springframework/security/docs'
|
||||
include-kotlin: 'example$docs-src/test/kotlin/org/springframework/security/kt/docs'
|
||||
include-xml: 'example$docs-src/test/resources/org/springframework/security/docs'
|
||||
|
@ -5,98 +5,16 @@ Similar to xref:servlet/authentication/x509.adoc#servlet-x509[Servlet X.509 auth
|
||||
|
||||
The following example shows a reactive x509 security configuration:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
.x509(withDefaults())
|
||||
.authorizeExchange(exchanges -> exchanges
|
||||
.anyExchange().permitAll()
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
include-code::./DefaultX509Configuration[tag=springSecurity,indent=0]
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
x509 { }
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In the preceding configuration, when neither `principalExtractor` nor `authenticationManager` is provided, defaults are used. The default principal extractor is `SubjectDnX509PrincipalExtractor`, which extracts the CN (common name) field from a certificate provided by a client. The default authentication manager is `ReactivePreAuthenticatedAuthenticationManager`, which performs user account validation, checking that a user account with a name extracted by `principalExtractor` exists and that it is not locked, disabled, or expired.
|
||||
In the preceding configuration, when neither `principalExtractor` nor `authenticationManager` is provided, defaults are used.
|
||||
The default principal extractor is `SubjectX500PrincipalExtractor`, which extracts the CN (common name) field from a certificate provided by a client.
|
||||
The default authentication manager is `ReactivePreAuthenticatedAuthenticationManager`, which performs user account validation, checking that a user account with a name extracted by `principalExtractor` exists and that it is not locked, disabled, or expired.
|
||||
|
||||
The following example demonstrates how these defaults can be overridden:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
||||
SubjectDnX509PrincipalExtractor principalExtractor =
|
||||
new SubjectDnX509PrincipalExtractor();
|
||||
include-code::./CustomX509Configuration[tag=springSecurity,indent=0]
|
||||
|
||||
principalExtractor.setSubjectDnRegex("OU=(.*?)(?:,|$)");
|
||||
|
||||
ReactiveAuthenticationManager authenticationManager = authentication -> {
|
||||
authentication.setAuthenticated("Trusted Org Unit".equals(authentication.getName()));
|
||||
return Mono.just(authentication);
|
||||
};
|
||||
|
||||
http
|
||||
.x509(x509 -> x509
|
||||
.principalExtractor(principalExtractor)
|
||||
.authenticationManager(authenticationManager)
|
||||
)
|
||||
.authorizeExchange(exchanges -> exchanges
|
||||
.anyExchange().authenticated()
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? {
|
||||
val customPrincipalExtractor = SubjectDnX509PrincipalExtractor()
|
||||
customPrincipalExtractor.setSubjectDnRegex("OU=(.*?)(?:,|$)")
|
||||
val customAuthenticationManager = ReactiveAuthenticationManager { authentication: Authentication ->
|
||||
authentication.isAuthenticated = "Trusted Org Unit" == authentication.name
|
||||
Mono.just(authentication)
|
||||
}
|
||||
return http {
|
||||
x509 {
|
||||
principalExtractor = customPrincipalExtractor
|
||||
authenticationManager = customAuthenticationManager
|
||||
}
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In the previous example, a username is extracted from the OU field of a client certificate instead of CN, and account lookup using `ReactiveUserDetailsService` is not performed at all. Instead, if the provided certificate issued to an OU named "`Trusted Org Unit`", a request is authenticated.
|
||||
In the previous example, a username is extracted from the `emailAddress` field of a client certificate instead of CN, and account lookup uses a custom `ReactiveAuthenticationManager` instance.
|
||||
|
||||
For an example of configuring Netty and `WebClient` or `curl` command-line tool to use mutual TLS and enable X.509 authentication, see https://github.com/spring-projects/spring-security-samples/tree/main/servlet/java-configuration/authentication/x509.
|
||||
|
@ -14,37 +14,27 @@ You should get this working before trying it out with Spring Security.
|
||||
The Spring Security X.509 module extracts the certificate by using a filter.
|
||||
It maps the certificate to an application user and loads that user's set of granted authorities for use with the standard Spring Security infrastructure.
|
||||
|
||||
|
||||
[[servlet-x509-config]]
|
||||
== Adding X.509 Authentication to Your Web Application
|
||||
Enabling X.509 client authentication is very straightforward.
|
||||
To do so, add the `<x509/>` element to your http security namespace configuration:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
...
|
||||
<x509 subject-principal-regex="CN=(.*?)," user-service-ref="userService"/>;
|
||||
</http>
|
||||
----
|
||||
Similar to xref:reactive/authentication/x509.adoc[Reactive X.509 authentication], the servlet x509 authentication filter allows extracting an authentication token from a certificate provided by a client.
|
||||
|
||||
The element has two optional attributes:
|
||||
The following example shows a reactive x509 security configuration:
|
||||
|
||||
* `subject-principal-regex`.
|
||||
The regular expression used to extract a username from the certificate's subject name.
|
||||
The default value is shown in the preceding listing.
|
||||
This is the username that is passed to the `UserDetailsService` to load the authorities for the user.
|
||||
* `user-service-ref`.
|
||||
This is the bean ID of the `UserDetailsService` to be used with X.509.
|
||||
It is not needed if there is only one defined in your application context.
|
||||
include-code::./DefaultX509Configuration[tag=springSecurity,indent=0]
|
||||
|
||||
In the preceding configuration, when neither `principalExtractor` nor `authenticationManager` is provided, defaults are used.
|
||||
The default principal extractor is `SubjectX500PrincipalExtractor`, which extracts the CN (common name) field from a certificate provided by a client.
|
||||
The default authentication manager is `ReactivePreAuthenticatedAuthenticationManager`, which performs user account validation, checking that a user account with a name extracted by `principalExtractor` exists and that it is not locked, disabled, or expired.
|
||||
|
||||
The following example demonstrates how these defaults can be overridden:
|
||||
|
||||
include-code::./CustomX509Configuration[tag=springSecurity,indent=0]
|
||||
|
||||
In the previous example, a username is extracted from the `emailAddress` field of a client certificate instead of CN, and account lookup uses a custom `ReactiveAuthenticationManager` instance.
|
||||
|
||||
For an example of configuring Netty and `WebClient` or `curl` command-line tool to use mutual TLS and enable X.509 authentication, see https://github.com/spring-projects/spring-security-samples/tree/main/servlet/java-configuration/authentication/x509.
|
||||
|
||||
The `subject-principal-regex` should contain a single group.
|
||||
For example, the default expression (`CN=(.*?)`) matches the common name field.
|
||||
So, if the subject name in the certificate is "CN=Jimi Hendrix, OU=...", this gives a user name of "Jimi Hendrix".
|
||||
The matches are case insensitive.
|
||||
So "emailAddress=(+.*?+)," matches "EMAILADDRESS=jimi@hendrix.org,CN=...", giving a user name "jimi@hendrix.org".
|
||||
If the client presents a certificate and a valid username is successfully extracted, there should be a valid `Authentication` object in the security context.
|
||||
If no certificate is found or no corresponding user could be found, the security context remains empty.
|
||||
This means that you can use X.509 authentication with other options, such as a form-based login.
|
||||
|
||||
[[x509-ssl-config]]
|
||||
== Setting up SSL in Tomcat
|
||||
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.docs.reactive.authentication.reactivex509;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
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.authentication.preauth.x509.SubjectX500PrincipalExtractor;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager;
|
||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||
|
||||
/**
|
||||
* Demonstrates custom configuration for x509 reactive configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
public class CustomX509Configuration {
|
||||
|
||||
// tag::springSecurity[]
|
||||
@Bean
|
||||
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
|
||||
SubjectX500PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor();
|
||||
principalExtractor.setExtractPrincipalNameFromEmail(true);
|
||||
|
||||
// @formatter:off
|
||||
UserDetails user = User
|
||||
.withUsername("luke@monkeymachine")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
ReactiveUserDetailsService users = new MapReactiveUserDetailsService(user);
|
||||
ReactiveAuthenticationManager authenticationManager = new ReactivePreAuthenticatedAuthenticationManager(users);
|
||||
|
||||
// @formatter:off
|
||||
http
|
||||
.x509(x509 -> x509
|
||||
.principalExtractor(principalExtractor)
|
||||
.authenticationManager(authenticationManager)
|
||||
)
|
||||
.authorizeExchange(exchanges -> exchanges
|
||||
.anyExchange().authenticated()
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
// end::springSecurity[]
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.docs.reactive.authentication.reactivex509;
|
||||
|
||||
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;
|
||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||
|
||||
/**
|
||||
* Demonstrates custom configuration for x509 reactive configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
public class DefaultX509Configuration {
|
||||
|
||||
// tag::springSecurity[]
|
||||
@Bean
|
||||
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
|
||||
// @formatter:off
|
||||
http
|
||||
.x509(Customizer.withDefaults())
|
||||
.authorizeExchange(exchanges -> exchanges
|
||||
.anyExchange().authenticated()
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
// end::springSecurity[]
|
||||
|
||||
@Bean
|
||||
ReactiveUserDetailsService userDetailsService() {
|
||||
// @formatter:off
|
||||
UserDetails user = User
|
||||
.withUsername("rod")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
return new MapReactiveUserDetailsService(user);
|
||||
}
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.docs.reactive.authentication.reactivex509;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.SslInfo;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509TestUtils;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.test.web.reactive.server.WebTestClientConfigurer;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
|
||||
/**
|
||||
* Tests {@link CustomX509Configuration}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class X509ConfigurationTests {
|
||||
|
||||
public final SpringTestContext spring = new SpringTestContext(this);
|
||||
|
||||
WebTestClient client;
|
||||
|
||||
@Autowired
|
||||
void setSpringSecurityFilterChain(WebFilter springSecurityFilterChain) {
|
||||
this.client = WebTestClient.bindToController(WebTestClientBuilder.Http200RestController.class)
|
||||
.webFilter(springSecurityFilterChain)
|
||||
.apply(springSecurity())
|
||||
.configureClient()
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void x509WhenDefaultX509Configuration() throws Exception {
|
||||
this.spring.register(DefaultX509Configuration.class).autowire();
|
||||
X509Certificate certificate = loadCert("rod.cer");
|
||||
// @formatter:off
|
||||
this.client
|
||||
.mutateWith(x509(certificate))
|
||||
.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isOk();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void x509WhenCustomX509Configuration() throws Exception {
|
||||
this.spring.register(CustomX509Configuration.class).autowire();
|
||||
X509Certificate certificate = X509TestUtils.buildTestCertificate();
|
||||
// @formatter:off
|
||||
this.client
|
||||
.mutateWith(x509(certificate))
|
||||
.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isOk();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private static @NotNull WebTestClientConfigurer x509(X509Certificate certificate) {
|
||||
return (builder, httpHandlerBuilder, connector) -> {
|
||||
builder.apply(new WebTestClientConfigurer() {
|
||||
@Override
|
||||
public void afterConfigurerAdded(WebTestClient.Builder builder,
|
||||
@Nullable WebHttpHandlerBuilder httpHandlerBuilder,
|
||||
@Nullable ClientHttpConnector connector) {
|
||||
SslInfo sslInfo = new SslInfo() {
|
||||
@Override
|
||||
public @Nullable String getSessionId() {
|
||||
return "sessionId";
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate @Nullable [] getPeerCertificates() {
|
||||
return new X509Certificate[] { certificate };
|
||||
}
|
||||
};
|
||||
httpHandlerBuilder.filters((filters) -> filters.add(0, new SslInfoOverrideWebFilter(sslInfo)));
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private static class SslInfoOverrideWebFilter implements WebFilter {
|
||||
private final SslInfo sslInfo;
|
||||
|
||||
private SslInfoOverrideWebFilter(SslInfo sslInfo) {
|
||||
this.sslInfo = sslInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
ServerHttpRequest sslInfoRequest = exchange.getRequest().mutate().sslInfo(sslInfo)
|
||||
.build();
|
||||
ServerWebExchange sslInfoExchange = exchange.mutate().request(sslInfoRequest).build();
|
||||
return chain.filter(sslInfoExchange);
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends Certificate> T loadCert(String location) {
|
||||
try (InputStream is = new ClassPathResource(location).getInputStream()) {
|
||||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||
return (T) certFactory.generateCertificate(is);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalArgumentException(ex);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.docs.servlet.authentication.servletx509config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
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.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.DefaultSecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
/**
|
||||
* Demonstrates custom configuration for x509 reactive configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@EnableWebMvc
|
||||
@EnableWebSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class CustomX509Configuration {
|
||||
|
||||
// tag::springSecurity[]
|
||||
@Bean
|
||||
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
||||
SubjectX500PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor();
|
||||
principalExtractor.setExtractPrincipalNameFromEmail(true);
|
||||
|
||||
|
||||
// @formatter:off
|
||||
http
|
||||
.x509((x509) -> x509
|
||||
.x509PrincipalExtractor(principalExtractor)
|
||||
)
|
||||
.authorizeHttpRequests((exchanges) -> exchanges
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
// end::springSecurity[]
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
// @formatter:off
|
||||
UserDetails user = User
|
||||
.withUsername("luke@monkeymachine")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
return new InMemoryUserDetailsManager(user);
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.docs.servlet.authentication.servletx509config;
|
||||
|
||||
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.DefaultSecurityFilterChain;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
/**
|
||||
* Demonstrates custom configuration for x509 reactive configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@EnableWebMvc
|
||||
@EnableWebSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class DefaultX509Configuration {
|
||||
|
||||
// tag::springSecurity[]
|
||||
@Bean
|
||||
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.x509(Customizer.withDefaults())
|
||||
.authorizeHttpRequests(exchanges -> exchanges
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
// end::springSecurity[]
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
// @formatter:off
|
||||
UserDetails user = User
|
||||
.withUsername("rod")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
return new InMemoryUserDetailsManager(user);
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.docs.servlet.authentication.servletx509config;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.SslInfo;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509TestUtils;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.test.web.reactive.server.WebTestClientConfigurer;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509;
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Tests {@link CustomX509Configuration}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class X509ConfigurationTests {
|
||||
|
||||
public final SpringTestContext spring = new SpringTestContext(this);
|
||||
|
||||
@Autowired
|
||||
MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
void x509WhenDefaultX509Configuration() throws Exception {
|
||||
this.spring.register(DefaultX509Configuration.class, Http200Controller.class).autowire();
|
||||
// @formatter:off
|
||||
this.mockMvc.perform(get("/").with(x509("rod.cer")))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(authenticated().withUsername("rod"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void x509WhenDefaultX509ConfigurationXml() throws Exception {
|
||||
this.spring.testConfigLocations("DefaultX509Configuration.xml").autowire();
|
||||
// @formatter:off
|
||||
this.mockMvc.perform(get("/").with(x509("rod.cer")))
|
||||
.andExpect(authenticated().withUsername("rod"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void x509WhenCustomX509Configuration() throws Exception {
|
||||
this.spring.register(CustomX509Configuration.class, Http200Controller.class).autowire();
|
||||
X509Certificate certificate = X509TestUtils.buildTestCertificate();
|
||||
// @formatter:off
|
||||
this.mockMvc.perform(get("/").with(x509(certificate)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(authenticated().withUsername("luke@monkeymachine"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class Http200Controller {
|
||||
@GetMapping("/**")
|
||||
String ok() {
|
||||
return "ok";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.kt.docs.reactive.authentication.reactivex509
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity.http
|
||||
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.web.authentication.preauth.x509.SubjectX500PrincipalExtractor
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
|
||||
/**
|
||||
* Demonstrates custom configuration for x509 reactive configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class CustomX509Configuration {
|
||||
|
||||
// tag::springSecurity[]
|
||||
@Bean
|
||||
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
val extractor = SubjectX500PrincipalExtractor()
|
||||
extractor.setExtractPrincipalNameFromEmail(true)
|
||||
|
||||
// @formatter:off
|
||||
val user = User
|
||||
.withUsername("luke@monkeymachine")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
// @formatter:on
|
||||
|
||||
val users: ReactiveUserDetailsService = MapReactiveUserDetailsService(user)
|
||||
val authentication: ReactiveAuthenticationManager = ReactivePreAuthenticatedAuthenticationManager(users)
|
||||
|
||||
return http {
|
||||
x509 {
|
||||
principalExtractor = extractor
|
||||
authenticationManager = authentication
|
||||
}
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
}
|
||||
}
|
||||
// end::springSecurity[]
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.kt.docs.reactive.authentication.reactivex509
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
|
||||
/**
|
||||
* Demonstrates custom configuration for x509 reactive configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class DefaultX509Configuration {
|
||||
|
||||
// tag::springSecurity[]
|
||||
@Bean
|
||||
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
x509 { }
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
}
|
||||
}
|
||||
// end::springSecurity[]
|
||||
|
||||
@Bean
|
||||
fun userDetailsService(): MapReactiveUserDetailsService {
|
||||
// @formatter:off
|
||||
val user = User
|
||||
.withUsername("rod")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
// @formatter:on
|
||||
|
||||
return MapReactiveUserDetailsService(user)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.kt.docs.reactive.authentication.reactivex509
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.core.io.ClassPathResource
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector
|
||||
import org.springframework.http.server.reactive.SslInfo
|
||||
import org.springframework.security.config.test.SpringTestContext
|
||||
import org.springframework.security.config.test.SpringTestContextExtension
|
||||
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers
|
||||
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder.Http200RestController
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509TestUtils
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.test.web.reactive.server.WebTestClientConfigurer
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import org.springframework.web.server.WebFilter
|
||||
import org.springframework.web.server.WebFilterChain
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder
|
||||
import reactor.core.publisher.Mono
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.function.Consumer
|
||||
|
||||
/**
|
||||
* Tests [CustomX509Configuration].
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension::class)
|
||||
class X509ConfigurationTests {
|
||||
@JvmField
|
||||
val spring: SpringTestContext = SpringTestContext(this)
|
||||
|
||||
var client: WebTestClient? = null
|
||||
|
||||
@Autowired
|
||||
fun setSpringSecurityFilterChain(springSecurityFilterChain: WebFilter) {
|
||||
this.client = WebTestClient
|
||||
.bindToController(Http200RestController::class.java)
|
||||
.webFilter<WebTestClient.ControllerSpec>(springSecurityFilterChain)
|
||||
.apply<WebTestClient.ControllerSpec>(SecurityMockServerConfigurers.springSecurity())
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun x509WhenDefaultX509Configuration() {
|
||||
this.spring.register(DefaultX509Configuration::class.java).autowire()
|
||||
val certificate = loadCert<X509Certificate>("rod.cer")
|
||||
// @formatter:off
|
||||
this.client!!.mutateWith(x509(certificate))
|
||||
.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
fun x509WhenCustomX509Configuration() {
|
||||
this.spring.register(CustomX509Configuration::class.java).autowire()
|
||||
val certificate = X509TestUtils.buildTestCertificate()
|
||||
// @formatter:off
|
||||
this.client!!.mutateWith(x509(certificate))
|
||||
.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private class SslInfoOverrideWebFilter(private val sslInfo: SslInfo) : WebFilter {
|
||||
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
|
||||
val sslInfoRequest = exchange.getRequest().mutate().sslInfo(sslInfo)
|
||||
.build()
|
||||
val sslInfoExchange = exchange.mutate().request(sslInfoRequest).build()
|
||||
return chain.filter(sslInfoExchange)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T : Certificate?> loadCert(location: String): T {
|
||||
try {
|
||||
ClassPathResource(location).getInputStream().use { `is` ->
|
||||
val certFactory = CertificateFactory.getInstance("X.509")
|
||||
return certFactory.generateCertificate(`is`) as T
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
throw IllegalArgumentException(ex)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun x509(certificate: X509Certificate): WebTestClientConfigurer {
|
||||
return WebTestClientConfigurer { builder: WebTestClient.Builder, httpHandlerBuilder: WebHttpHandlerBuilder, connector: ClientHttpConnector? ->
|
||||
|
||||
val sslInfo: SslInfo = object : SslInfo {
|
||||
override fun getSessionId(): String {
|
||||
return "sessionId"
|
||||
}
|
||||
|
||||
override fun getPeerCertificates(): Array<X509Certificate?> {
|
||||
return arrayOf(certificate)
|
||||
}
|
||||
}
|
||||
httpHandlerBuilder.filters(Consumer { filters: MutableList<WebFilter> ->
|
||||
filters.add(
|
||||
0,
|
||||
SslInfoOverrideWebFilter(sslInfo)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.kt.docs.servlet.authentication.servlet509config
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager
|
||||
import org.springframework.security.web.DefaultSecurityFilterChain
|
||||
import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc
|
||||
|
||||
/**
|
||||
* Demonstrates custom configuration for x509 reactive configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@EnableWebMvc
|
||||
@EnableWebSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class CustomX509Configuration {
|
||||
// tag::springSecurity[]
|
||||
@Bean
|
||||
fun springSecurity(http: HttpSecurity): DefaultSecurityFilterChain? {
|
||||
val principalExtractor = SubjectX500PrincipalExtractor()
|
||||
principalExtractor.setExtractPrincipalNameFromEmail(true)
|
||||
|
||||
// @formatter:off
|
||||
http {
|
||||
authorizeHttpRequests {
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
x509 {
|
||||
x509PrincipalExtractor = principalExtractor
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
// end::springSecurity[]
|
||||
|
||||
@Bean
|
||||
fun userDetailsService(): UserDetailsService {
|
||||
// @formatter:off
|
||||
val user = User
|
||||
.withUsername("luke@monkeymachine")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
// @formatter:on
|
||||
return InMemoryUserDetailsManager(user)
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.kt.docs.servlet.authentication.servlet509config
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager
|
||||
import org.springframework.security.web.DefaultSecurityFilterChain
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc
|
||||
|
||||
/**
|
||||
* Demonstrates custom configuration for x509 reactive configuration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@EnableWebMvc
|
||||
@EnableWebSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class DefaultX509Configuration {
|
||||
// tag::springSecurity[]
|
||||
@Bean
|
||||
fun springSecurity(http: HttpSecurity): DefaultSecurityFilterChain? {
|
||||
// @formatter:off
|
||||
http {
|
||||
authorizeHttpRequests {
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
x509 { }
|
||||
}
|
||||
// @formatter:on
|
||||
return http.build()
|
||||
}
|
||||
// end::springSecurity[]
|
||||
|
||||
@Bean
|
||||
fun userDetailsService(): UserDetailsService {
|
||||
// @formatter:off
|
||||
val user = User
|
||||
.withUsername("rod")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
// @formatter:on
|
||||
return InMemoryUserDetailsManager(user)
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.kt.docs.servlet.authentication.servlet509config
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.security.config.test.SpringTestContext
|
||||
import org.springframework.security.config.test.SpringTestContextExtension
|
||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors
|
||||
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509TestUtils
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
/**
|
||||
* Tests [CustomX509Configuration].
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension::class)
|
||||
class X509ConfigurationTests {
|
||||
@JvmField
|
||||
val spring: SpringTestContext = SpringTestContext(this)
|
||||
|
||||
@Autowired
|
||||
var mockMvc: MockMvc? = null
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun x509WhenDefaultX509Configuration() {
|
||||
this.spring.register(DefaultX509Configuration::class.java, Http200Controller::class.java).autowire()
|
||||
// @formatter:off
|
||||
this.mockMvc!!.perform(get("/").with(SecurityMockMvcRequestPostProcessors.x509("rod.cer")))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(authenticated().withUsername("rod"))
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun x509WhenCustomX509Configuration() {
|
||||
this.spring.register(CustomX509Configuration::class.java, Http200Controller::class.java).autowire()
|
||||
val certificate = X509TestUtils.buildTestCertificate()
|
||||
// @formatter:off
|
||||
this.mockMvc!!.perform(get("/").with(SecurityMockMvcRequestPostProcessors.x509(certificate)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(authenticated().withUsername("luke@monkeymachine"))
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@RestController
|
||||
internal class Http200Controller {
|
||||
@GetMapping("/**")
|
||||
fun ok(): String {
|
||||
return "ok"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2002-2018 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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/security
|
||||
https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<!-- tag::springSecurity[] -->
|
||||
<http>
|
||||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
<x509 principal-extractor-ref="principalExtractor"/>
|
||||
</http>
|
||||
<b:bean id="principalExtractor"
|
||||
class="org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor"
|
||||
p:extractPrincipalNameFromEmail="true"/>
|
||||
<!-- end::springSecurity[] -->
|
||||
|
||||
<user-service id="us">
|
||||
<user name="luke@monkeymachine" password="{noop}password" authorities="ROLE_USER"/>
|
||||
</user-service>
|
||||
|
||||
|
||||
|
||||
<b:import resource="MiscHttpConfigTests-controllers.xml"/>
|
||||
</b:beans>
|
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2002-2018 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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/security
|
||||
https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<!-- tag::springSecurity[] -->
|
||||
<http>
|
||||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
<x509 />
|
||||
</http>
|
||||
<!-- end::springSecurity[] -->
|
||||
|
||||
<user-service>
|
||||
<user name="rod" password="{noop}password" authorities="ROLE_USER"/>
|
||||
</user-service>
|
||||
|
||||
</b:beans>
|
Loading…
x
Reference in New Issue
Block a user