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
+ */
1
17
package io .javatab .springcloud .auth .configuration ;
2
18
19
+ import java .time .Duration ;
20
+ import java .util .List ;
21
+ import java .util .UUID ;
22
+ import java .util .function .Consumer ;
23
+
3
24
import com .nimbusds .jose .jwk .JWKSet ;
4
25
import com .nimbusds .jose .jwk .RSAKey ;
5
26
import com .nimbusds .jose .jwk .source .JWKSource ;
6
27
import com .nimbusds .jose .proc .SecurityContext ;
7
28
import io .javatab .springcloud .auth .jose .Jwks ;
8
29
import org .slf4j .Logger ;
9
30
import 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
+
10
42
import org .springframework .context .annotation .Bean ;
11
43
import org .springframework .context .annotation .Configuration ;
12
44
import org .springframework .core .Ordered ;
13
45
import org .springframework .core .annotation .Order ;
14
- import org .springframework .security .config .Customizer ;
15
46
import org .springframework .security .config .annotation .web .builders .HttpSecurity ;
16
47
import org .springframework .security .config .annotation .web .configurers .oauth2 .server .resource .OAuth2ResourceServerConfigurer ;
17
48
import org .springframework .security .oauth2 .core .AuthorizationGrantType ;
18
49
import org .springframework .security .oauth2 .core .ClientAuthenticationMethod ;
19
50
import 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 ;
21
52
import org .springframework .security .oauth2 .server .authorization .client .RegisteredClient ;
22
53
import org .springframework .security .oauth2 .server .authorization .client .RegisteredClientRepository ;
23
54
import 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 ;
25
56
import org .springframework .security .oauth2 .server .authorization .settings .ClientSettings ;
26
- import org .springframework .security .oauth2 .server .authorization .settings .TokenSettings ;
27
57
import org .springframework .security .web .SecurityFilterChain ;
28
58
import org .springframework .security .web .authentication .LoginUrlAuthenticationEntryPoint ;
29
59
30
- import java .time .Duration ;
31
- import java .util .UUID ;
32
-
60
+ /**
61
+ * @author Joe Grandja
62
+ * @since 0.0.1
63
+ */
33
64
@ Configuration (proxyBeanMethods = false )
34
65
public class AuthorizationServerConfig {
35
66
36
67
private static final Logger LOG = LoggerFactory .getLogger (AuthorizationServerConfig .class );
37
68
38
-
39
69
@ Bean
40
70
@ Order (Ordered .HIGHEST_PRECEDENCE )
41
71
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
+
43
97
http .getConfigurer (OAuth2AuthorizationServerConfigurer .class )
44
- .oidc (Customizer .withDefaults ()); // Enable OpenID Connect 1.0
98
+ .oidc (Customizer .withDefaults ()); // Enable OpenID Connect 1.0
45
99
46
- // @formatter:off
47
100
http
48
101
.exceptionHandling (exceptions ->
49
102
exceptions .authenticationEntryPoint (new LoginUrlAuthenticationEntryPoint ("/login" ))
50
103
)
51
104
.oauth2ResourceServer (OAuth2ResourceServerConfigurer ::jwt );
52
- // @formatter:on
105
+
53
106
return http .build ();
54
107
}
55
108
56
- // @formatter:off
57
109
@ Bean
58
110
public RegisteredClientRepository registeredClientRepository () {
59
-
60
- LOG .info ("register OAUth client allowing all grant flows..." );
61
111
RegisteredClient writerClient = RegisteredClient .withId (UUID .randomUUID ().toString ())
62
112
.clientId ("writer" )
63
- .clientSecret ("secret" )
113
+ .clientSecret ("{noop} secret-writer " )
64
114
.clientAuthenticationMethod (ClientAuthenticationMethod .CLIENT_SECRET_BASIC )
65
115
.authorizationGrantType (AuthorizationGrantType .AUTHORIZATION_CODE )
66
116
.authorizationGrantType (AuthorizationGrantType .REFRESH_TOKEN )
67
117
.authorizationGrantType (AuthorizationGrantType .CLIENT_CREDENTIALS )
68
118
.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" )
70
120
.scope (OidcScopes .OPENID )
71
- .scope ("product :read" )
72
- .scope ("product :write" )
121
+ .scope ("course :read" )
122
+ .scope ("course :write" )
73
123
.clientSettings (ClientSettings .builder ().requireAuthorizationConsent (true ).build ())
74
124
.tokenSettings (TokenSettings .builder ().accessTokenTimeToLive (Duration .ofHours (1 )).build ())
75
125
.build ();
76
126
77
127
RegisteredClient readerClient = RegisteredClient .withId (UUID .randomUUID ().toString ())
78
128
.clientId ("reader" )
79
- .clientSecret ("secret" )
129
+ .clientSecret ("{noop} secret-reader " )
80
130
.clientAuthenticationMethod (ClientAuthenticationMethod .CLIENT_SECRET_BASIC )
81
131
.authorizationGrantType (AuthorizationGrantType .AUTHORIZATION_CODE )
82
132
.authorizationGrantType (AuthorizationGrantType .REFRESH_TOKEN )
83
133
.authorizationGrantType (AuthorizationGrantType .CLIENT_CREDENTIALS )
84
134
.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" )
86
136
.scope (OidcScopes .OPENID )
87
- .scope ("product :read" )
137
+ .scope ("course :read" )
88
138
.clientSettings (ClientSettings .builder ().requireAuthorizationConsent (true ).build ())
89
139
.tokenSettings (TokenSettings .builder ().accessTokenTimeToLive (Duration .ofHours (1 )).build ())
90
140
.build ();
141
+
91
142
return new InMemoryRegisteredClientRepository (writerClient , readerClient );
143
+
92
144
}
93
145
94
146
@ Bean
@@ -98,8 +150,51 @@ public JWKSource<SecurityContext> jwkSource() {
98
150
return (jwkSelector , securityContext ) -> jwkSelector .select (jwkSet );
99
151
}
100
152
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