1010import java .util .Optional ;
1111import lombok .RequiredArgsConstructor ;
1212import lombok .extern .slf4j .Slf4j ;
13- import org .jetbrains .annotations .Nullable ;
1413import org .springframework .boot .autoconfigure .condition .ConditionalOnProperty ;
1514import org .springframework .boot .autoconfigure .security .oauth2 .client .OAuth2ClientProperties ;
1615import org .springframework .boot .autoconfigure .security .oauth2 .client .OAuth2ClientPropertiesMapper ;
1716import org .springframework .boot .autoconfigure .security .oauth2 .resource .OAuth2ResourceServerProperties ;
1817import org .springframework .boot .context .properties .EnableConfigurationProperties ;
1918import org .springframework .context .annotation .Bean ;
2019import org .springframework .context .annotation .Configuration ;
20+ import org .springframework .context .annotation .Primary ;
2121import org .springframework .http .client .reactive .ReactorClientHttpConnector ;
2222import org .springframework .security .config .Customizer ;
2323import org .springframework .security .config .annotation .method .configuration .EnableReactiveMethodSecurity ;
2424import org .springframework .security .config .annotation .web .reactive .EnableWebFluxSecurity ;
2525import org .springframework .security .config .web .server .SecurityWebFiltersOrder ;
2626import org .springframework .security .config .web .server .ServerHttpSecurity ;
27+ import org .springframework .security .oauth2 .client .endpoint .OAuth2AuthorizationCodeGrantRequest ;
28+ import org .springframework .security .oauth2 .client .endpoint .ReactiveOAuth2AccessTokenResponseClient ;
29+ import org .springframework .security .oauth2 .client .endpoint .WebClientReactiveAuthorizationCodeTokenResponseClient ;
30+ import org .springframework .security .oauth2 .client .oidc .authentication .OidcAuthorizationCodeReactiveAuthenticationManager ;
2731import org .springframework .security .oauth2 .client .oidc .userinfo .OidcReactiveOAuth2UserService ;
2832import org .springframework .security .oauth2 .client .oidc .userinfo .OidcUserRequest ;
2933import org .springframework .security .oauth2 .client .oidc .web .server .logout .OidcClientInitiatedServerLogoutSuccessHandler ;
3539import org .springframework .security .oauth2 .client .userinfo .ReactiveOAuth2UserService ;
3640import org .springframework .security .oauth2 .core .oidc .user .OidcUser ;
3741import org .springframework .security .oauth2 .core .user .OAuth2User ;
42+ import org .springframework .security .oauth2 .jwt .NimbusReactiveJwtDecoder ;
43+ import org .springframework .security .oauth2 .jwt .ReactiveJwtDecoder ;
44+ import org .springframework .security .oauth2 .server .resource .introspection .ReactiveOpaqueTokenIntrospector ;
45+ import org .springframework .security .oauth2 .server .resource .introspection .SpringReactiveOpaqueTokenIntrospector ;
3846import org .springframework .security .web .server .SecurityWebFilterChain ;
3947import org .springframework .security .web .server .authentication .logout .ServerLogoutSuccessHandler ;
4048import org .springframework .web .reactive .function .client .WebClient ;
@@ -52,40 +60,80 @@ public class OAuthSecurityConfig extends AbstractAuthSecurityConfig {
5260
5361 private final OAuthProperties properties ;
5462
63+ /**
64+ * WebClient configured to use system proxy properties (-Dhttps.proxyHost, -Dhttps.proxyPort).
65+ */
66+ private final WebClient proxyAwareWebClient = WebClient .builder ()
67+ .clientConnector (new ReactorClientHttpConnector (HttpClient .create ().proxyWithSystemProperties ()))
68+ .build ();
69+
5570 @ Bean
56- public SecurityWebFilterChain configure (ServerHttpSecurity http , OAuthLogoutSuccessHandler logoutHandler ) {
71+ public SecurityWebFilterChain configure (
72+ ServerHttpSecurity http ,
73+ OAuthLogoutSuccessHandler logoutHandler ,
74+ ReactiveOAuth2AccessTokenResponseClient <OAuth2AuthorizationCodeGrantRequest > tokenResponseClient ,
75+ ReactiveOAuth2UserService <OidcUserRequest , OidcUser > oidcUserService
76+ ) {
5777 log .info ("Configuring OAUTH2 authentication." );
5878
79+ var oidcAuthManager =
80+ new OidcAuthorizationCodeReactiveAuthenticationManager (tokenResponseClient , oidcUserService );
81+
5982 var builder = http .authorizeExchange (spec -> spec
6083 .pathMatchers (AUTH_WHITELIST )
6184 .permitAll ()
6285 .anyExchange ()
6386 .authenticated ()
6487 )
65- .oauth2Login (Customizer . withDefaults ( ))
88+ .oauth2Login (oauth2 -> oauth2 . authenticationManager ( oidcAuthManager ))
6689 .logout (spec -> spec .logoutSuccessHandler (logoutHandler ))
6790 .csrf (ServerHttpSecurity .CsrfSpec ::disable );
6891
69- if (properties .getResourceServer () != null ) {
70- OAuth2ResourceServerProperties resourceServer = properties .getResourceServer ();
71- if (resourceServer .getJwt () != null ) {
72- builder .oauth2ResourceServer ((c ) -> c .jwt ((j ) -> j .jwkSetUri (resourceServer .getJwt ().getJwkSetUri ())));
73- } else if (resourceServer .getOpaquetoken () != null ) {
74- OAuth2ResourceServerProperties .Opaquetoken opaquetoken = resourceServer .getOpaquetoken ();
75- builder .oauth2ResourceServer (
76- (c ) -> c .opaqueToken (
77- (o ) -> o .introspectionUri (opaquetoken .getIntrospectionUri ())
78- .introspectionClientCredentials (opaquetoken .getClientId (), opaquetoken .getClientSecret ())
79- )
80- );
81- }
92+ if (getJwkSetUri () != null ) {
93+ builder .oauth2ResourceServer (c -> c .jwt (Customizer .withDefaults ()));
94+ } else if (getOpaqueTokenConfig () != null ) {
95+ builder .oauth2ResourceServer (c -> c .opaqueToken (Customizer .withDefaults ()));
8296 }
8397
8498 builder .addFilterAt (new StaticFileWebFilter (), SecurityWebFiltersOrder .LOGIN_PAGE_GENERATING );
8599
86100 return builder .build ();
87101 }
88102
103+ @ Bean
104+ public ReactiveOAuth2AccessTokenResponseClient <OAuth2AuthorizationCodeGrantRequest >
105+ authorizationCodeTokenResponseClient () {
106+ var client = new WebClientReactiveAuthorizationCodeTokenResponseClient ();
107+ client .setWebClient (proxyAwareWebClient );
108+ return client ;
109+ }
110+
111+ @ Bean
112+ @ Primary
113+ public ReactiveJwtDecoder jwtDecoder () {
114+ String jwkSetUri = getJwkSetUri ();
115+ if (jwkSetUri == null ) {
116+ return token -> Mono .error (new IllegalStateException ("JWT decoder not configured" ));
117+ }
118+ log .info ("Configuring JWT decoder with JWKS URI: {}" , jwkSetUri );
119+ return NimbusReactiveJwtDecoder .withJwkSetUri (jwkSetUri ).webClient (proxyAwareWebClient ).build ();
120+ }
121+
122+ @ Bean
123+ @ Primary
124+ public ReactiveOpaqueTokenIntrospector opaqueTokenIntrospector () {
125+ var config = getOpaqueTokenConfig ();
126+ if (config == null ) {
127+ return token -> Mono .error (new IllegalStateException ("Opaque token introspector not configured" ));
128+ }
129+ log .info ("Configuring opaque token introspector with URI: {}" , config .getIntrospectionUri ());
130+ return new SpringReactiveOpaqueTokenIntrospector (
131+ config .getIntrospectionUri (),
132+ proxyAwareWebClient .mutate ()
133+ .defaultHeaders (h -> h .setBasicAuth (config .getClientId (), config .getClientSecret ()))
134+ .build ());
135+ }
136+
89137 @ Bean
90138 public ReactiveOAuth2UserService <OidcUserRequest , OidcUser > customOidcUserService (
91139 AccessControlService acs ,
@@ -110,13 +158,7 @@ public ReactiveOAuth2UserService<OidcUserRequest, OidcUser> customOidcUserServic
110158 @ Bean
111159 public ReactiveOAuth2UserService <OAuth2UserRequest , OAuth2User > customOauth2UserService (AccessControlService acs ) {
112160 final DefaultReactiveOAuth2UserService delegate = new DefaultReactiveOAuth2UserService ();
113-
114- // Configure WebClient to use system proxy properties (if set)
115- delegate .setWebClient (
116- WebClient .builder ()
117- .clientConnector (new ReactorClientHttpConnector (
118- HttpClient .create ().proxyWithSystemProperties ()))
119- .build ());
161+ delegate .setWebClient (proxyAwareWebClient );
120162
121163 return request -> delegate .loadUser (request )
122164 .flatMap (user -> {
@@ -147,7 +189,19 @@ public ServerLogoutSuccessHandler defaultOidcLogoutHandler(final ReactiveClientR
147189 return new OidcClientInitiatedServerLogoutSuccessHandler (repository );
148190 }
149191
150- @ Nullable
192+ private String getJwkSetUri () {
193+ var rs = properties .getResourceServer ();
194+ return rs != null && rs .getJwt () != null ? rs .getJwt ().getJwkSetUri () : null ;
195+ }
196+
197+ private OAuth2ResourceServerProperties .Opaquetoken getOpaqueTokenConfig () {
198+ var rs = properties .getResourceServer ();
199+ if (rs == null || rs .getOpaquetoken () == null ) {
200+ return null ;
201+ }
202+ return rs .getOpaquetoken ().getIntrospectionUri () != null ? rs .getOpaquetoken () : null ;
203+ }
204+
151205 private ProviderAuthorityExtractor getExtractor (final OAuthProperties .OAuth2Provider provider ,
152206 AccessControlService acs ) {
153207 Optional <ProviderAuthorityExtractor > extractor = acs .getOauthExtractors ()
0 commit comments