Add sample for self-signed certificate Mutual-TLS client authentication method
Issue gh-1559
This commit is contained in:
@@ -43,8 +43,8 @@ import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class RestTemplateConfig {
|
||||
|
||||
@Bean
|
||||
Supplier<ClientHttpRequestFactory> clientHttpRequestFactory(SslBundles sslBundles) {
|
||||
@Bean("default-client-http-request-factory")
|
||||
Supplier<ClientHttpRequestFactory> defaultClientHttpRequestFactory(SslBundles sslBundles) {
|
||||
return () -> {
|
||||
SslBundle sslBundle = sslBundles.getBundle("demo-client");
|
||||
final SSLContext sslContext = sslBundle.createSslContext();
|
||||
@@ -63,4 +63,23 @@ public class RestTemplateConfig {
|
||||
};
|
||||
}
|
||||
|
||||
@Bean("self-signed-demo-client-http-request-factory")
|
||||
Supplier<ClientHttpRequestFactory> selfSignedDemoClientHttpRequestFactory(SslBundles sslBundles) {
|
||||
return () -> {
|
||||
SslBundle sslBundle = sslBundles.getBundle("self-signed-demo-client");
|
||||
final SSLContext sslContext = sslBundle.createSslContext();
|
||||
final SSLConnectionSocketFactory sslConnectionSocketFactory =
|
||||
new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
|
||||
final Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
|
||||
.register("https", sslConnectionSocketFactory)
|
||||
.build();
|
||||
final BasicHttpClientConnectionManager connectionManager =
|
||||
new BasicHttpClientConnectionManager(socketFactoryRegistry);
|
||||
final CloseableHttpClient httpClient = HttpClients.custom()
|
||||
.setConnectionManager(connectionManager)
|
||||
.build();
|
||||
return new HttpComponentsClientHttpRequestFactory(httpClient);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -49,7 +49,7 @@ public class SecurityConfig {
|
||||
http
|
||||
.authorizeHttpRequests(authorize ->
|
||||
authorize
|
||||
.requestMatchers("/logged-out").permitAll()
|
||||
.requestMatchers("/jwks", "/logged-out").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2Login(oauth2Login ->
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.function.Supplier;
|
||||
|
||||
import sample.authorization.DeviceCodeOAuth2AuthorizedClientProvider;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -52,8 +53,47 @@ import org.springframework.web.reactive.function.client.WebClient;
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class WebClientConfig {
|
||||
|
||||
@Bean
|
||||
public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
|
||||
@Bean("default-client-web-client")
|
||||
public WebClient defaultClientWebClient(OAuth2AuthorizedClientManager authorizedClientManager) {
|
||||
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
|
||||
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
|
||||
// @formatter:off
|
||||
return WebClient.builder()
|
||||
.apply(oauth2Client.oauth2Configuration())
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean("self-signed-demo-client-web-client")
|
||||
public WebClient selfSignedDemoClientWebClient(
|
||||
ClientRegistrationRepository clientRegistrationRepository,
|
||||
OAuth2AuthorizedClientRepository authorizedClientRepository,
|
||||
RestTemplateBuilder restTemplateBuilder,
|
||||
@Qualifier("self-signed-demo-client-http-request-factory") Supplier<ClientHttpRequestFactory> clientHttpRequestFactory) {
|
||||
|
||||
// @formatter:off
|
||||
RestTemplate restTemplate = restTemplateBuilder
|
||||
.requestFactory(clientHttpRequestFactory)
|
||||
.messageConverters(Arrays.asList(
|
||||
new FormHttpMessageConverter(),
|
||||
new OAuth2AccessTokenResponseHttpMessageConverter()))
|
||||
.errorHandler(new OAuth2ErrorResponseErrorHandler())
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
// @formatter:off
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.clientCredentials(clientCredentials ->
|
||||
clientCredentials.accessTokenResponseClient(
|
||||
createClientCredentialsTokenResponseClient(restTemplate)))
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository);
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
|
||||
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
|
||||
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
|
||||
// @formatter:off
|
||||
@@ -68,7 +108,7 @@ public class WebClientConfig {
|
||||
ClientRegistrationRepository clientRegistrationRepository,
|
||||
OAuth2AuthorizedClientRepository authorizedClientRepository,
|
||||
RestTemplateBuilder restTemplateBuilder,
|
||||
Supplier<ClientHttpRequestFactory> clientHttpRequestFactory) {
|
||||
@Qualifier("default-client-http-request-factory") Supplier<ClientHttpRequestFactory> clientHttpRequestFactory) {
|
||||
|
||||
// @formatter:off
|
||||
RestTemplate restTemplate = restTemplateBuilder
|
||||
|
||||
@@ -17,6 +17,7 @@ package sample.web;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
|
||||
@@ -39,14 +40,18 @@ import static org.springframework.security.oauth2.client.web.reactive.function.c
|
||||
*/
|
||||
@Controller
|
||||
public class AuthorizationController {
|
||||
private final WebClient webClient;
|
||||
private final WebClient defaultClientWebClient;
|
||||
private final WebClient selfSignedDemoClientWebClient;
|
||||
private final String messagesBaseUri;
|
||||
private final String userMessagesBaseUri;
|
||||
|
||||
public AuthorizationController(WebClient webClient,
|
||||
public AuthorizationController(
|
||||
@Qualifier("default-client-web-client") WebClient defaultClientWebClient,
|
||||
@Qualifier("self-signed-demo-client-web-client") WebClient selfSignedDemoClientWebClient,
|
||||
@Value("${messages.base-uri}") String messagesBaseUri,
|
||||
@Value("${user-messages.base-uri}") String userMessagesBaseUri) {
|
||||
this.webClient = webClient;
|
||||
this.defaultClientWebClient = defaultClientWebClient;
|
||||
this.selfSignedDemoClientWebClient = selfSignedDemoClientWebClient;
|
||||
this.messagesBaseUri = messagesBaseUri;
|
||||
this.userMessagesBaseUri = userMessagesBaseUri;
|
||||
}
|
||||
@@ -56,7 +61,7 @@ public class AuthorizationController {
|
||||
@RegisteredOAuth2AuthorizedClient("messaging-client-authorization-code")
|
||||
OAuth2AuthorizedClient authorizedClient) {
|
||||
|
||||
String[] messages = this.webClient
|
||||
String[] messages = this.defaultClientWebClient
|
||||
.get()
|
||||
.uri(this.messagesBaseUri)
|
||||
.attributes(oauth2AuthorizedClient(authorizedClient))
|
||||
@@ -87,7 +92,7 @@ public class AuthorizationController {
|
||||
@GetMapping(value = "/authorize", params = {"grant_type=client_credentials", "client_auth=client_secret"})
|
||||
public String clientCredentialsGrantUsingClientSecret(Model model) {
|
||||
|
||||
String[] messages = this.webClient
|
||||
String[] messages = this.defaultClientWebClient
|
||||
.get()
|
||||
.uri(this.messagesBaseUri)
|
||||
.attributes(clientRegistrationId("messaging-client-client-credentials"))
|
||||
@@ -102,7 +107,7 @@ public class AuthorizationController {
|
||||
@GetMapping(value = "/authorize", params = {"grant_type=client_credentials", "client_auth=mtls"})
|
||||
public String clientCredentialsGrantUsingMutualTLS(Model model) {
|
||||
|
||||
String[] messages = this.webClient
|
||||
String[] messages = this.defaultClientWebClient
|
||||
.get()
|
||||
.uri(this.messagesBaseUri)
|
||||
.attributes(clientRegistrationId("mtls-demo-client-client-credentials"))
|
||||
@@ -114,10 +119,25 @@ public class AuthorizationController {
|
||||
return "index";
|
||||
}
|
||||
|
||||
@GetMapping(value = "/authorize", params = {"grant_type=client_credentials", "client_auth=self_signed_mtls"})
|
||||
public String clientCredentialsGrantUsingSelfSignedMutualTLS(Model model) {
|
||||
|
||||
String[] messages = this.selfSignedDemoClientWebClient
|
||||
.get()
|
||||
.uri(this.messagesBaseUri)
|
||||
.attributes(clientRegistrationId("mtls-self-signed-demo-client-client-credentials"))
|
||||
.retrieve()
|
||||
.bodyToMono(String[].class)
|
||||
.block();
|
||||
model.addAttribute("messages", messages);
|
||||
|
||||
return "index";
|
||||
}
|
||||
|
||||
@GetMapping(value = "/authorize", params = "grant_type=token_exchange")
|
||||
public String tokenExchangeGrant(Model model) {
|
||||
|
||||
String[] messages = this.webClient
|
||||
String[] messages = this.defaultClientWebClient
|
||||
.get()
|
||||
.uri(this.userMessagesBaseUri)
|
||||
.attributes(clientRegistrationId("user-client-authorization-code"))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2024 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.
|
||||
@@ -22,6 +22,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -72,7 +73,9 @@ public class DeviceController {
|
||||
|
||||
private final String messagesBaseUri;
|
||||
|
||||
public DeviceController(ClientRegistrationRepository clientRegistrationRepository, WebClient webClient,
|
||||
public DeviceController(
|
||||
ClientRegistrationRepository clientRegistrationRepository,
|
||||
@Qualifier("default-client-web-client") WebClient webClient,
|
||||
@Value("${messages.base-uri}") String messagesBaseUri) {
|
||||
|
||||
this.clientRegistrationRepository = clientRegistrationRepository;
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package sample.web;
|
||||
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.KeyUse;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import com.nimbusds.jose.util.Base64;
|
||||
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
* @since 1.3
|
||||
*/
|
||||
@RestController
|
||||
public class JwkSetController {
|
||||
private final JWKSet jwkSet;
|
||||
|
||||
public JwkSetController(SslBundles sslBundles) throws Exception {
|
||||
this.jwkSet = initJwkSet(sslBundles);
|
||||
}
|
||||
|
||||
@GetMapping("/jwks")
|
||||
public Map<String, Object> getJwkSet() {
|
||||
return this.jwkSet.toJSONObject();
|
||||
}
|
||||
|
||||
private static JWKSet initJwkSet(SslBundles sslBundles) throws Exception {
|
||||
SslBundle sslBundle = sslBundles.getBundle("self-signed-demo-client");
|
||||
KeyStore keyStore = sslBundle.getStores().getKeyStore();
|
||||
String alias = sslBundle.getKey().getAlias();
|
||||
|
||||
Certificate certificate = keyStore.getCertificate(alias);
|
||||
|
||||
RSAKey rsaKey = new RSAKey.Builder((RSAPublicKey) certificate.getPublicKey())
|
||||
.keyUse(KeyUse.SIGNATURE)
|
||||
.keyID(UUID.randomUUID().toString())
|
||||
.x509CertChain(Collections.singletonList(Base64.encode(certificate.getEncoded())))
|
||||
.build();
|
||||
|
||||
return new JWKSet(rsaKey);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user