如何使用Spring Boot WebClient管理HTTPS双向认证,包括Bearer令牌?

huangapple go评论85阅读模式
英文:

How to manage HTTPS Mutual Authentication including Bearer Token with Spring boot WebClient?

问题

我的帖子目标是直接分享我关于以下主题的答案。我还分享了帮助我的链接 => 我正在开发一个基于Spring WebFlux的后端。一个前端Angular应用程序连接到我的后端。
我的后端还通过HTTPS连接到业务服务器,基于双向身份验证。
我必须在每个向业务服务器的头部请求中设置一个承载令牌。
我通过HTTPS通过OAuth 2.0客户端凭据授权流程从服务器检索此令牌。下面是我如何管理实施的答案。

英文:

The goal of my post is to directly share my answer regarding the following topic. I share also the links that helped me => I'm developping a backend based on Spring webflux. A frontEnd angular application is connected to my backend.
My backend is also connected to a business server through HTTPS based on mutual authentication.
I have to set a bearer token in each header request to my business server.
I retrieve this token from a server through OAuth 2.0 client credentials grant flow through HTTPS. My answer below shows how I managed the implementation.

答案1

得分: 0

以下是翻译好的部分:

感谢以下链接:

我找到了以下解决方案:

@Configuration
@EnableWebFluxSecurity
public class Oauth2ClientConfig {

    // 基于 OAuth 2.0 客户端凭证授权流的 Bearer Token 管理
    @Bean
    ReactiveClientRegistrationRepository getRegistration() {
        ClientRegistration registration = ClientRegistration
                .withRegistrationId("credentialProvider")
                .tokenUri("https://rhsso:8446/auth/token")
                .clientId("client_id").clientSecret("client_secret")
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .build();
        return new InMemoryReactiveClientRegistrationRepository(registration);
    }
    // 使用 HTTPS 进行 Bearer Token 管理
    @Bean
    public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
            ReactiveClientRegistrationRepository clientRegistrationRepository,
            ReactiveOAuth2AuthorizedClientService authorizedClientService) throws IOException, KeyStoreException,
            NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException {

        WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient = new WebClientReactiveClientCredentialsTokenResponseClient();

        final FileInputStream keyStoreFile = new FileInputStream("C:\\keystore\\client_keystore.p12"); // 客户端私钥和客户端证书
        final FileInputStream trustStoreFile = new FileInputStream("C:\\truststore\\server_truststore.p12");
        final KeyStore keyStore = KeyStore.getInstance("PKCS12");
        final KeyStore trustStore = KeyStore.getInstance("PKCS12");
        final KeyManagerFactory keyManagerFactory = KeyManagerFactory
                .getInstance(KeyManagerFactory.getDefaultAlgorithm());
        final TrustManagerFactory trustManagerFactory = TrustManagerFactory
                .getInstance(TrustManagerFactory.getDefaultAlgorithm());

        String keyStorePassword = "keyStorePassword";
        String trustStorePassword = "trustStorePassword";
        keyStore.load(keyStoreFile, keyStorePassword.toCharArray());
        keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
        trustStore.load(trustStoreFile, trustStorePassword.toCharArray());
        trustManagerFactory.init(trustStore);

        // 通过 sslContext 进行 HTTPS 管理
        SslContextBuilder sslContextBuilder = SslContextBuilder.forClient().keyManager(keyManagerFactory)
                .trustManager(trustManagerFactory);

        SslContext sslContext = sslContextBuilder.build();

        HttpClient httpSslClient = HttpClient.create().secure(ssl -> ssl.sslContext(sslContext));

        ClientHttpConnector httpConnector = new ReactorClientHttpConnector(httpSslClient);

        accessTokenResponseClient.setWebClient(WebClient.builder().clientConnector(httpConnector).build());

        ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder
                .builder().clientCredentials(c -> {
                    c.accessTokenResponseClient(accessTokenResponseClient);
                }).build();

        AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
                clientRegistrationRepository, authorizedClientService);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }

    @Bean("CustomWebClient") //Qualifier to be used by my dedicated ClientAPI service in order to use the right one among available WebClient beans
    public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) throws KeyStoreException,
            NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException {
        ServerOAuth2AuthorizedClientExchangeFilterFunction oauthFunction = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
                authorizedClientManager);
        oauthFunction.setDefaultClientRegistrationId("credentialProvider"); // 链接到上面管理的 Bearer token 的 ID

        final FileInputStream keyStoreFile = new FileInputStream("C:\\keystore\\business_client_keystore.p12"); // 客户端私钥和客户端证书
        final FileInputStream trustStoreFile = new FileInputStream("C:\\truststore\\business_server_truststore.p12");
        final KeyStore keyStore = KeyStore.getInstance("PKCS12");
        final KeyStore trustStore = KeyStore.getInstance("PKCS12");
        final KeyManagerFactory keyManagerFactory = KeyManagerFactory
                .getInstance(KeyManagerFactory.getDefaultAlgorithm());
        final TrustManagerFactory trustManagerFactory = TrustManagerFactory
                .getInstance(TrustManagerFactory.getDefaultAlgorithm());

        String keyStorePassword = "business_keyStorePassword";
        String trustStorePassword = "business_trustStorePassword";
        keyStore.load(keyStoreFile, keyStorePassword.toCharArray());
        keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
        trustStore.load(trustStoreFile, trustStorePassword.toCharArray());

        trustManagerFactory.init(trustStore);
        // 创建 Netty SslContext、HttpClient
        SslContextBuilder sslContextBuilder = SslContextBuilder.forClient().keyManager(keyManagerFactory)
                .trustManager(trustManagerFactory);

        SslContext sslContext = sslContextBuilder.build();

        HttpClient httpSslClient = HttpClient.create().secure(ssl -> ssl.sslContext(sslContext));

        // 创建 Reactive ClientHttpConnector & WebClient
        final ClientHttpConnector httpConnector = new ReactorClientHttpConnector(httpSslClient);

        return WebClient.builder().clientConnector(httpConnector).filter(oauthFunction).build();

    }

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        // 禁用前端界面的 CSRF,但通常不建议,只在特殊情况下使用
        // 跨域管理以接受安装在本地主机上的前端应用程序,仅允许 POST 方法
        http.csrf().disable().cors(corsCustomizer -> {
            CorsConfigurationSource configurationSource = request -> {
                CorsConfiguration corsConfiguration = new CorsConfiguration();
                corsConfiguration.setAllowedOrigins(List.of("localhost:8080"));
                corsConfiguration.setAllowedMethods(List.of("POST"));
                return corsConfiguration;
            };
            corsCustomizer.configurationSource(configurationSource);
        }).oauth2Client();

        return http.build();
    }

}

基于上述配置的 Web 客户端:

@Service
public class ClientAPI {
    

    private WebClient webClient;
    private String businessServiceUrl;

    @Autowired
    public ClientAPI(@Qualifier("CustomWebClient") WebClient webClient) {
        this.businessServiceUrl = "https://hostServerName:8081/theService";
        this.webClient = webClient;
    }

    public Mono<Response>

<details>
<summary>英文:</summary>

Thanx to following links : 
- &lt;https://stackoverflow.com/questions/62935967/spring-boot-oauth2-clientreactive-mutual-tls-ssl-token-uri&gt;
- &lt;https://stackoverflow.com/questions/45418523/spring-5-webclient-using-ssl&gt;
- &lt;https://stackoverflow.com/questions/62935967/spring-boot-oauth2-clientreactive-mutual-tls-ssl-token-uri&gt;
- &lt;https://stackoverflow.com/questions/17173559/how-to-verify-if-java-sends-the-client-certificate-in-a-mutual-auth-scenario&gt; =&gt; useful to check Mutual authentication in debug mode


I found out the following solution :

```java
@Configuration
@EnableWebFluxSecurity
public class Oauth2ClientConfig {

	// Bearer Token management based on OAuth 2.0 client credentials grant flow
	@Bean
	ReactiveClientRegistrationRepository getRegistration() {
		ClientRegistration registration = ClientRegistration
				.withRegistrationId(&quot;credentialProvider&quot;)
				.tokenUri(&quot;https://rhsso:8446/auth/token&quot;)
				.clientId(&quot;client_id&quot;).clientSecret(&quot;client_secret&quot;)
				.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
				.build();
		return new InMemoryReactiveClientRegistrationRepository(registration);
	}
    // Bearer Token management using HTTPS
	@Bean
	public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
			ReactiveClientRegistrationRepository clientRegistrationRepository,
			ReactiveOAuth2AuthorizedClientService authorizedClientService) throws IOException, KeyStoreException,
			NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException {

		WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient = new WebClientReactiveClientCredentialsTokenResponseClient();
		      
		final FileInputStream keyStoreFile = new FileInputStream(&quot;C:\\keystore\\client_keystore.p12&quot;); // client private key and client certificate
		final FileInputStream trustStoreFile = new FileInputStream(&quot;C:\\truststore\\server_truststore.p12&quot;);
		final KeyStore keyStore = KeyStore.getInstance(&quot;PKCS12&quot;);
		final KeyStore trustStore = KeyStore.getInstance(&quot;PKCS12&quot;);
		final KeyManagerFactory keyManagerFactory = KeyManagerFactory
				.getInstance(KeyManagerFactory.getDefaultAlgorithm());
		final TrustManagerFactory trustManagerFactory = TrustManagerFactory
				.getInstance(TrustManagerFactory.getDefaultAlgorithm());

		String keyStorePassword = &quot;keyStorePassword&quot;;
		String trustStorePassword = &quot;trustStorePassword&quot;;
		keyStore.load(keyStoreFile, keyStorePassword.toCharArray());
		keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
		trustStore.load(trustStoreFile, trustStorePassword.toCharArray());
		trustManagerFactory.init(trustStore);
		
		// HTTPS management through sslContext
		SslContextBuilder sslContextBuilder = SslContextBuilder.forClient().keyManager(keyManagerFactory)
				.trustManager(trustManagerFactory);

		SslContext sslContext = sslContextBuilder.build();

		HttpClient httpSslClient = HttpClient.create().secure(ssl -&gt; ssl.sslContext(sslContext));

		ClientHttpConnector httpConnector = new ReactorClientHttpConnector(httpSslClient);

		accessTokenResponseClient.setWebClient(WebClient.builder().clientConnector(httpConnector).build()); 

		ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder
				.builder().clientCredentials(c -&gt; {
					c.accessTokenResponseClient(accessTokenResponseClient);
				}).build();

		
		AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
				clientRegistrationRepository, authorizedClientService);
		authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

		return authorizedClientManager;
	}

	@Bean(&quot;CustomWebClient&quot;) //Qualifier to be used by my dedicated ClientAPI service in order to use the right one among available WebClient beans
	public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) throws KeyStoreException,
			NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException {
		ServerOAuth2AuthorizedClientExchangeFilterFunction oauthFunction = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
				authorizedClientManager);
		oauthFunction.setDefaultClientRegistrationId(&quot;credentialProvider&quot;); // Link to Bearer token managed above through Its ID

		final FileInputStream keyStoreFile = new FileInputStream(&quot;C:\\keystore\\business_client_keystore.p12&quot;); // client private key and client certificate
		final FileInputStream trustStoreFile = new FileInputStream(&quot;C:\\truststore\\business_server_truststore.p12&quot;);
		final KeyStore keyStore = KeyStore.getInstance(&quot;PKCS12&quot;);
		final KeyStore trustStore = KeyStore.getInstance(&quot;PKCS12&quot;);
		final KeyManagerFactory keyManagerFactory = KeyManagerFactory
				.getInstance(KeyManagerFactory.getDefaultAlgorithm());
		final TrustManagerFactory trustManagerFactory = TrustManagerFactory
				.getInstance(TrustManagerFactory.getDefaultAlgorithm());

		String keyStorePassword = &quot;business_keyStorePassword&quot;;
		String trustStorePassword = &quot;business_trustStorePassword&quot;;
		keyStore.load(keyStoreFile, keyStorePassword.toCharArray());
		keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
		trustStore.load(trustStoreFile, trustStorePassword.toCharArray());

		trustManagerFactory.init(trustStore);
		// Create Netty SslContext, HttpClient
		SslContextBuilder sslContextBuilder = SslContextBuilder.forClient().keyManager(keyManagerFactory)
				.trustManager(trustManagerFactory);

		SslContext sslContext = sslContextBuilder.build();

		HttpClient httpSslClient = HttpClient.create().secure(ssl -&gt; ssl.sslContext(sslContext));

		// Create Reactive ClientHttpConnector &amp; WebClient
		final ClientHttpConnector httpConnector = new ReactorClientHttpConnector(httpSslClient);

		return WebClient.builder().clientConnector(httpConnector).filter(oauthFunction).build();

	}

	@Bean
	public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
		// disable csrf with frontend interface but generally not recommended otherwise only in special cases
		// cors management in order to accept FrontEnd application installed localhost and only POST method is allowed 
		http.csrf().disable().cors(corsCustomizer -&gt; {
			CorsConfigurationSource configurationSource = request-&gt;{
				CorsConfiguration corsConfiguration = new CorsConfiguration();
				corsConfiguration.setAllowedOrigins(List.of(&quot;localhost:8080&quot;));
				corsConfiguration.setAllowedMethods(List.of(&quot;POST&quot;));
				return corsConfiguration;
			};
			corsCustomizer.configurationSource(configurationSource);
		}).oauth2Client(); 
		
		return http.build();
	}

}

My webclient based on my beans above :

@Service
public class ClientAPI {
	

	private WebClient webClient;
	private String businessServiceUrl;

	@Autowired
	public ClientAPI(@Qualifier(&quot;CustomWebClient&quot;) WebClient webClient) {
		this.businessServiceUrl = &quot;https://hostServerName:8081/theService&quot;;
		this.webClient = webClient;
	}

	public Mono&lt;Response&gt; sendRequest(Request bodyRequest) {

		Mono&lt;Response&gt; result = this.webClient.post().uri(this.businessServiceUrl)
				.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
				.body(Mono.just(bodyRequest), Request.class).retrieve().bodyToMono(Response.class);

		return result;
	}

}

huangapple
  • 本文由 发表于 2023年7月12日 21:28:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/76671125.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定