Skip to content

Commit ff4b542

Browse files
committed
Configure sample with Mutual-TLS client certificate-bound access tokens
Issue gh-1560
1 parent f1e6ec1 commit ff4b542

File tree

6 files changed

+115
-4
lines changed

6 files changed

+115
-4
lines changed

samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java

+6
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
5151
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
5252
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
53+
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
5354
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
5455
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
5556
import org.springframework.security.web.SecurityFilterChain;
@@ -179,6 +180,11 @@ public JdbcRegisteredClientRepository registeredClientRepository(JdbcTemplate jd
179180
.jwkSetUrl("http://127.0.0.1:8080/jwks")
180181
.build()
181182
)
183+
.tokenSettings(
184+
TokenSettings.builder()
185+
.x509CertificateBoundAccessTokens(true)
186+
.build()
187+
)
182188
.build();
183189

184190
// Save registered client's in db as if in-memory

samples/demo-client/src/main/java/sample/config/WebClientConfig.java

+35-2
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,24 @@
1818
import java.util.Arrays;
1919
import java.util.function.Supplier;
2020

21+
import javax.net.ssl.KeyManagerFactory;
22+
import javax.net.ssl.TrustManagerFactory;
23+
24+
import io.netty.handler.ssl.SslContext;
25+
import io.netty.handler.ssl.SslContextBuilder;
26+
import reactor.netty.http.client.HttpClient;
27+
import reactor.netty.tcp.SslProvider;
2128
import sample.authorization.DeviceCodeOAuth2AuthorizedClientProvider;
2229

2330
import org.springframework.beans.factory.annotation.Qualifier;
31+
import org.springframework.boot.ssl.SslBundle;
32+
import org.springframework.boot.ssl.SslBundles;
2433
import org.springframework.boot.web.client.RestTemplateBuilder;
2534
import org.springframework.context.annotation.Bean;
2635
import org.springframework.context.annotation.Configuration;
2736
import org.springframework.http.client.ClientHttpRequestFactory;
37+
import org.springframework.http.client.reactive.ClientHttpConnector;
38+
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
2839
import org.springframework.http.converter.FormHttpMessageConverter;
2940
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
3041
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
@@ -54,11 +65,15 @@
5465
public class WebClientConfig {
5566

5667
@Bean("default-client-web-client")
57-
public WebClient defaultClientWebClient(OAuth2AuthorizedClientManager authorizedClientManager) {
68+
public WebClient defaultClientWebClient(
69+
OAuth2AuthorizedClientManager authorizedClientManager,
70+
SslBundles sslBundles) throws Exception {
71+
5872
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
5973
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
6074
// @formatter:off
6175
return WebClient.builder()
76+
.clientConnector(createClientConnector(sslBundles.getBundle("demo-client")))
6277
.apply(oauth2Client.oauth2Configuration())
6378
.build();
6479
// @formatter:on
@@ -69,7 +84,8 @@ public WebClient selfSignedDemoClientWebClient(
6984
ClientRegistrationRepository clientRegistrationRepository,
7085
OAuth2AuthorizedClientRepository authorizedClientRepository,
7186
RestTemplateBuilder restTemplateBuilder,
72-
@Qualifier("self-signed-demo-client-http-request-factory") Supplier<ClientHttpRequestFactory> clientHttpRequestFactory) {
87+
@Qualifier("self-signed-demo-client-http-request-factory") Supplier<ClientHttpRequestFactory> clientHttpRequestFactory,
88+
SslBundles sslBundles) throws Exception {
7389

7490
// @formatter:off
7591
RestTemplate restTemplate = restTemplateBuilder
@@ -98,6 +114,7 @@ public WebClient selfSignedDemoClientWebClient(
98114
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
99115
// @formatter:off
100116
return WebClient.builder()
117+
.clientConnector(createClientConnector(sslBundles.getBundle("self-signed-demo-client")))
101118
.apply(oauth2Client.oauth2Configuration())
102119
.build();
103120
// @formatter:on
@@ -143,6 +160,22 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
143160
return authorizedClientManager;
144161
}
145162

163+
private static ClientHttpConnector createClientConnector(SslBundle sslBundle) throws Exception {
164+
KeyManagerFactory keyManagerFactory = sslBundle.getManagers().getKeyManagerFactory();
165+
TrustManagerFactory trustManagerFactory = sslBundle.getManagers().getTrustManagerFactory();
166+
167+
// @formatter:off
168+
SslContext sslContext = SslContextBuilder.forClient()
169+
.keyManager(keyManagerFactory)
170+
.trustManager(trustManagerFactory)
171+
.build();
172+
// @formatter:on
173+
174+
SslProvider sslProvider = SslProvider.builder().sslContext(sslContext).build();
175+
HttpClient httpClient = HttpClient.create().secure(sslProvider);
176+
return new ReactorClientHttpConnector(httpClient);
177+
}
178+
146179
private static OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> createClientCredentialsTokenResponseClient(
147180
RestTemplate restTemplate) {
148181
DefaultClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient =

samples/demo-client/src/main/resources/application.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ spring:
101101
token-uri: https://localhost:9443/oauth2/token
102102

103103
messages:
104-
base-uri: http://127.0.0.1:8090/messages
104+
base-uri: https://127.0.0.1:8443/messages
105105

106106
user-messages:
107107
base-uri: http://127.0.0.1:8091/user/messages

samples/messages-resource/src/main/java/sample/config/ResourceServerConfig.java

+8
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@
3030
@Configuration(proxyBeanMethods = false)
3131
public class ResourceServerConfig {
3232

33+
/*
34+
NOTE:
35+
The `NimbusJwtDecoder` `@Bean` autoconfigured by Spring Boot will contain
36+
an `OAuth2TokenValidator<Jwt>` of type `X509CertificateThumbprintValidator`.
37+
This is the validator responsible for validating the `x5t#S256` claim (if available)
38+
in the `Jwt` against the SHA-256 Thumbprint of the supplied `X509Certificate`.
39+
*/
40+
3341
// @formatter:off
3442
@Bean
3543
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2020-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package sample.config;
17+
18+
import org.apache.catalina.connector.Connector;
19+
20+
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
21+
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
22+
import org.springframework.context.annotation.Bean;
23+
import org.springframework.context.annotation.Configuration;
24+
25+
/**
26+
* @author Joe Grandja
27+
* @since 1.3
28+
*/
29+
@Configuration(proxyBeanMethods = false)
30+
public class TomcatServerConfig {
31+
32+
@Bean
33+
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> connectorCustomizer() {
34+
return (tomcat) -> tomcat.addAdditionalTomcatConnectors(createHttpConnector());
35+
}
36+
37+
private Connector createHttpConnector() {
38+
Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
39+
connector.setScheme("http");
40+
connector.setPort(8090);
41+
connector.setSecure(false);
42+
connector.setRedirectPort(8443);
43+
return connector;
44+
}
45+
46+
}

samples/messages-resource/src/main/resources/application.yml

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
server:
2-
port: 8090
2+
port: 8443
3+
ssl:
4+
bundle: messages-resource
5+
client-auth: need
36

47
logging:
58
level:
@@ -10,6 +13,21 @@ logging:
1013
# org.springframework.boot.autoconfigure: DEBUG
1114

1215
spring:
16+
ssl:
17+
bundle:
18+
jks:
19+
messages-resource:
20+
key:
21+
alias: messages-resource-sample
22+
password: password
23+
keystore:
24+
location: classpath:keystore.p12
25+
password: password
26+
type: PKCS12
27+
truststore:
28+
location: classpath:keystore.p12
29+
password: password
30+
type: PKCS12
1331
security:
1432
oauth2:
1533
resourceserver:

0 commit comments

Comments
 (0)