1+ //CHECKSTYLE:OFF
2+ /*
3+ * Copyright 2020-2022 the original author or authors.
4+ *
5+ * Licensed under the Apache License, Version 2.0 (the "License");
6+ * you may not use this file except in compliance with the License.
7+ * You may obtain a copy of the License at
8+ *
9+ * https://www.apache.org/licenses/LICENSE-2.0
10+ *
11+ * Unless required by applicable law or agreed to in writing, software
12+ * distributed under the License is distributed on an "AS IS" BASIS,
13+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+ * See the License for the specific language governing permissions and
15+ * limitations under the License.
16+ */
117package io .javatab .springcloud .auth .configuration ;
218
19+ import java .time .Duration ;
20+ import java .util .List ;
21+ import java .util .UUID ;
22+ import java .util .function .Consumer ;
23+
324import com .nimbusds .jose .jwk .JWKSet ;
425import com .nimbusds .jose .jwk .RSAKey ;
526import com .nimbusds .jose .jwk .source .JWKSource ;
627import com .nimbusds .jose .proc .SecurityContext ;
728import io .javatab .springcloud .auth .jose .Jwks ;
829import org .slf4j .Logger ;
930import org .slf4j .LoggerFactory ;
31+ import org .springframework .security .authentication .AuthenticationProvider ;
32+ import org .springframework .security .config .Customizer ;
33+ import org .springframework .security .oauth2 .core .OAuth2Error ;
34+ import org .springframework .security .oauth2 .core .OAuth2ErrorCodes ;
35+ import org .springframework .security .oauth2 .server .authorization .authentication .*;
36+ import org .springframework .security .oauth2 .server .authorization .client .InMemoryRegisteredClientRepository ;
37+ import org .springframework .security .oauth2 .server .authorization .config .annotation .web .configurers .OAuth2AuthorizationServerConfigurer ;
38+ import org .springframework .security .oauth2 .server .authorization .settings .TokenSettings ;
39+ import org .springframework .security .web .util .matcher .RequestMatcher ;
40+
41+
1042import org .springframework .context .annotation .Bean ;
1143import org .springframework .context .annotation .Configuration ;
1244import org .springframework .core .Ordered ;
1345import org .springframework .core .annotation .Order ;
14- import org .springframework .security .config .Customizer ;
1546import org .springframework .security .config .annotation .web .builders .HttpSecurity ;
1647import org .springframework .security .config .annotation .web .configurers .oauth2 .server .resource .OAuth2ResourceServerConfigurer ;
1748import org .springframework .security .oauth2 .core .AuthorizationGrantType ;
1849import org .springframework .security .oauth2 .core .ClientAuthenticationMethod ;
1950import org .springframework .security .oauth2 .core .oidc .OidcScopes ;
20- import org .springframework .security .oauth2 .server . authorization . client . InMemoryRegisteredClientRepository ;
51+ import org .springframework .security .oauth2 .jwt . JwtDecoder ;
2152import org .springframework .security .oauth2 .server .authorization .client .RegisteredClient ;
2253import org .springframework .security .oauth2 .server .authorization .client .RegisteredClientRepository ;
2354import org .springframework .security .oauth2 .server .authorization .config .annotation .web .configuration .OAuth2AuthorizationServerConfiguration ;
24- import org .springframework .security .oauth2 .server .authorization .config . annotation . web . configurers . OAuth2AuthorizationServerConfigurer ;
55+ import org .springframework .security .oauth2 .server .authorization .settings . AuthorizationServerSettings ;
2556import org .springframework .security .oauth2 .server .authorization .settings .ClientSettings ;
26- import org .springframework .security .oauth2 .server .authorization .settings .TokenSettings ;
2757import org .springframework .security .web .SecurityFilterChain ;
2858import org .springframework .security .web .authentication .LoginUrlAuthenticationEntryPoint ;
2959
30- import java .time .Duration ;
31- import java .util .UUID ;
32-
60+ /**
61+ * @author Joe Grandja
62+ * @since 0.0.1
63+ */
3364@ Configuration (proxyBeanMethods = false )
3465public class AuthorizationServerConfig {
3566
3667 private static final Logger LOG = LoggerFactory .getLogger (AuthorizationServerConfig .class );
3768
38-
3969 @ Bean
4070 @ Order (Ordered .HIGHEST_PRECEDENCE )
4171 public SecurityFilterChain authorizationServerSecurityFilterChain (HttpSecurity http ) throws Exception {
42- OAuth2AuthorizationServerConfiguration .applyDefaultSecurity (http );
72+
73+ // Replaced this call with the implementation of applyDefaultSecurity() to be able to add a custom redirect_uri validator
74+ // OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
75+
76+ OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
77+ new OAuth2AuthorizationServerConfigurer ();
78+
79+ // Register a custom redirect_uri validator, that allows redirect uris based on https://localhost during development
80+ authorizationServerConfigurer
81+ .authorizationEndpoint (authorizationEndpoint ->
82+ authorizationEndpoint
83+ .authenticationProviders (configureAuthenticationValidator ())
84+ );
85+
86+ RequestMatcher endpointsMatcher = authorizationServerConfigurer
87+ .getEndpointsMatcher ();
88+
89+ http
90+ .securityMatcher (endpointsMatcher )
91+ .authorizeHttpRequests (authorize ->
92+ authorize .anyRequest ().authenticated ()
93+ )
94+ .csrf (csrf -> csrf .ignoringRequestMatchers (endpointsMatcher ))
95+ .apply (authorizationServerConfigurer );
96+
4397 http .getConfigurer (OAuth2AuthorizationServerConfigurer .class )
44- .oidc (Customizer .withDefaults ()); // Enable OpenID Connect 1.0
98+ .oidc (Customizer .withDefaults ()); // Enable OpenID Connect 1.0
4599
46- // @formatter:off
47100 http
48101 .exceptionHandling (exceptions ->
49102 exceptions .authenticationEntryPoint (new LoginUrlAuthenticationEntryPoint ("/login" ))
50103 )
51104 .oauth2ResourceServer (OAuth2ResourceServerConfigurer ::jwt );
52- // @formatter:on
105+
53106 return http .build ();
54107 }
55108
56- // @formatter:off
57109 @ Bean
58110 public RegisteredClientRepository registeredClientRepository () {
59-
60- LOG .info ("register OAUth client allowing all grant flows..." );
61111 RegisteredClient writerClient = RegisteredClient .withId (UUID .randomUUID ().toString ())
62112 .clientId ("writer" )
63- .clientSecret ("secret" )
113+ .clientSecret ("{noop} secret-writer " )
64114 .clientAuthenticationMethod (ClientAuthenticationMethod .CLIENT_SECRET_BASIC )
65115 .authorizationGrantType (AuthorizationGrantType .AUTHORIZATION_CODE )
66116 .authorizationGrantType (AuthorizationGrantType .REFRESH_TOKEN )
67117 .authorizationGrantType (AuthorizationGrantType .CLIENT_CREDENTIALS )
68118 .redirectUri ("https://my.redirect.uri" )
69- .redirectUri ("https://localhost:8443/webjars/swagger-ui/oauth2-redirect.html" )
119+ .redirectUri ("https://localhost:8443/openapi/ webjars/swagger-ui/oauth2-redirect.html" )
70120 .scope (OidcScopes .OPENID )
71- .scope ("product :read" )
72- .scope ("product :write" )
121+ .scope ("course :read" )
122+ .scope ("course :write" )
73123 .clientSettings (ClientSettings .builder ().requireAuthorizationConsent (true ).build ())
74124 .tokenSettings (TokenSettings .builder ().accessTokenTimeToLive (Duration .ofHours (1 )).build ())
75125 .build ();
76126
77127 RegisteredClient readerClient = RegisteredClient .withId (UUID .randomUUID ().toString ())
78128 .clientId ("reader" )
79- .clientSecret ("secret" )
129+ .clientSecret ("{noop} secret-reader " )
80130 .clientAuthenticationMethod (ClientAuthenticationMethod .CLIENT_SECRET_BASIC )
81131 .authorizationGrantType (AuthorizationGrantType .AUTHORIZATION_CODE )
82132 .authorizationGrantType (AuthorizationGrantType .REFRESH_TOKEN )
83133 .authorizationGrantType (AuthorizationGrantType .CLIENT_CREDENTIALS )
84134 .redirectUri ("https://my.redirect.uri" )
85- .redirectUri ("https://localhost:8443/webjars/swagger-ui/oauth2-redirect.html" )
135+ .redirectUri ("https://localhost:8443/openapi/ webjars/swagger-ui/oauth2-redirect.html" )
86136 .scope (OidcScopes .OPENID )
87- .scope ("product :read" )
137+ .scope ("course :read" )
88138 .clientSettings (ClientSettings .builder ().requireAuthorizationConsent (true ).build ())
89139 .tokenSettings (TokenSettings .builder ().accessTokenTimeToLive (Duration .ofHours (1 )).build ())
90140 .build ();
141+
91142 return new InMemoryRegisteredClientRepository (writerClient , readerClient );
143+
92144 }
93145
94146 @ Bean
@@ -98,8 +150,51 @@ public JWKSource<SecurityContext> jwkSource() {
98150 return (jwkSelector , securityContext ) -> jwkSelector .select (jwkSet );
99151 }
100152
101- /* @Bean
102- public ProviderSettings providerSettings() {
103- return new ProviderSettings().issuer("http://auth-server:9999");
104- }*/
105- }
153+ @ Bean
154+ public JwtDecoder jwtDecoder (JWKSource <SecurityContext > jwkSource ) {
155+ return OAuth2AuthorizationServerConfiguration .jwtDecoder (jwkSource );
156+ }
157+
158+ @ Bean
159+ public AuthorizationServerSettings authorizationServerSettings () {
160+ return AuthorizationServerSettings .builder ().issuer ("http://auth-server" ).build ();
161+ }
162+
163+ private Consumer <List <AuthenticationProvider >> configureAuthenticationValidator () {
164+ return (authenticationProviders ) ->
165+ authenticationProviders .forEach ((authenticationProvider ) -> {
166+ if (authenticationProvider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider ) {
167+ Consumer <OAuth2AuthorizationCodeRequestAuthenticationContext > authenticationValidator =
168+ // Override default redirect_uri validator
169+ new CustomRedirectUriValidator ()
170+ // Reuse default scope validator
171+ .andThen (OAuth2AuthorizationCodeRequestAuthenticationValidator .DEFAULT_SCOPE_VALIDATOR );
172+
173+ ((OAuth2AuthorizationCodeRequestAuthenticationProvider ) authenticationProvider )
174+ .setAuthenticationValidator (authenticationValidator );
175+ }
176+ });
177+ }
178+
179+ static class CustomRedirectUriValidator implements Consumer <OAuth2AuthorizationCodeRequestAuthenticationContext > {
180+
181+ @ Override
182+ public void accept (OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext ) {
183+ OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
184+ authenticationContext .getAuthentication ();
185+ RegisteredClient registeredClient = authenticationContext .getRegisteredClient ();
186+ String requestedRedirectUri = authorizationCodeRequestAuthentication .getRedirectUri ();
187+
188+ LOG .trace ("Will validate the redirect uri {}" , requestedRedirectUri );
189+
190+ // Use exact string matching when comparing client redirect URIs against pre-registered URIs
191+ if (!registeredClient .getRedirectUris ().contains (requestedRedirectUri )) {
192+ LOG .trace ("Redirect uri is invalid!" );
193+ OAuth2Error error = new OAuth2Error (OAuth2ErrorCodes .INVALID_REQUEST );
194+ throw new OAuth2AuthorizationCodeRequestAuthenticationException (error , null );
195+ }
196+ LOG .trace ("Redirect uri is OK!" );
197+ }
198+ }
199+ }
200+ //CHECKSTYLE:ON
0 commit comments