Skip to content

Commit 8600bfd

Browse files
committed
Fixing bug in Authorization server
1 parent 7f34e20 commit 8600bfd

File tree

5 files changed

+192
-57
lines changed

5 files changed

+192
-57
lines changed

microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/configuration/SecurityConfig.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
1717
.pathMatchers("/openapi/**").permitAll()
1818
.pathMatchers("/webjars/**").permitAll()
1919
.pathMatchers("/actuator/**").permitAll()
20-
.pathMatchers(POST, "/course-composite/**").hasAuthority("SCOPE_product:write")
21-
.pathMatchers(DELETE, "/course-composite/**").hasAuthority("SCOPE_product:write")
22-
.pathMatchers(GET, "/course-composite/**").hasAuthority("SCOPE_product:read")
20+
.pathMatchers(POST, "/course-composite/**").hasAuthority("SCOPE_course:write")
21+
.pathMatchers(DELETE, "/course-composite/**").hasAuthority("SCOPE_course:write")
22+
.pathMatchers(GET, "/course-composite/**").hasAuthority("SCOPE_course:read")
2323
.anyExchange().authenticated()
2424
.and()
2525
.oauth2ResourceServer()
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,146 @@
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+
*/
117
package 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+
324
import com.nimbusds.jose.jwk.JWKSet;
425
import com.nimbusds.jose.jwk.RSAKey;
526
import com.nimbusds.jose.jwk.source.JWKSource;
627
import com.nimbusds.jose.proc.SecurityContext;
728
import io.javatab.springcloud.auth.jose.Jwks;
829
import org.slf4j.Logger;
930
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+
1042
import org.springframework.context.annotation.Bean;
1143
import org.springframework.context.annotation.Configuration;
1244
import org.springframework.core.Ordered;
1345
import org.springframework.core.annotation.Order;
14-
import org.springframework.security.config.Customizer;
1546
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
1647
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
1748
import org.springframework.security.oauth2.core.AuthorizationGrantType;
1849
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
1950
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;
2152
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
2253
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
2354
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;
2556
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
26-
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
2757
import org.springframework.security.web.SecurityFilterChain;
2858
import 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)
3465
public 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

spring-cloud/authorization-server/src/main/java/io/javatab/springcloud/auth/configuration/DefaultSecurityConfig.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,27 @@
1+
//CHECKSTYLE:OFF
2+
/*
3+
* Copyright 2020 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+
*/
117
package io.javatab.springcloud.auth.configuration;
218

19+
import static org.springframework.security.config.Customizer.withDefaults;
20+
321
import org.slf4j.Logger;
422
import org.slf4j.LoggerFactory;
523
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Configuration;
625
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
726
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
827
import org.springframework.security.core.userdetails.User;
@@ -11,12 +30,14 @@
1130
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
1231
import org.springframework.security.web.SecurityFilterChain;
1332

14-
import static org.springframework.security.config.Customizer.withDefaults;
15-
33+
/**
34+
* @author Joe Grandja
35+
* @since 0.1.0
36+
*/
37+
@Configuration
1638
@EnableWebSecurity
1739
public class DefaultSecurityConfig {
1840

19-
2041
private static final Logger LOG = LoggerFactory.getLogger(DefaultSecurityConfig.class);
2142

2243
// formatter:off

spring-cloud/authorization-server/src/main/java/io/javatab/springcloud/auth/jose/Jwks.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,22 @@
1313
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
16+
1617
*/
18+
1719
package io.javatab.springcloud.auth.jose;
1820

1921
import com.nimbusds.jose.jwk.Curve;
2022
import com.nimbusds.jose.jwk.ECKey;
2123
import com.nimbusds.jose.jwk.OctetSequenceKey;
2224
import com.nimbusds.jose.jwk.RSAKey;
23-
24-
import javax.crypto.SecretKey;
2525
import java.security.KeyPair;
2626
import java.security.interfaces.ECPrivateKey;
2727
import java.security.interfaces.ECPublicKey;
2828
import java.security.interfaces.RSAPrivateKey;
2929
import java.security.interfaces.RSAPublicKey;
3030
import java.util.UUID;
31+
import javax.crypto.SecretKey;
3132

3233
/**
3334
* @author Joe Grandja
@@ -44,9 +45,9 @@ public static RSAKey generateRsa() {
4445
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
4546
// @formatter:off
4647
return new RSAKey.Builder(publicKey)
47-
.privateKey(privateKey)
48-
.keyID(UUID.randomUUID().toString())
49-
.build();
48+
.privateKey(privateKey)
49+
.keyID(UUID.randomUUID().toString())
50+
.build();
5051
// @formatter:on
5152
}
5253

@@ -57,18 +58,18 @@ public static ECKey generateEc() {
5758
Curve curve = Curve.forECParameterSpec(publicKey.getParams());
5859
// @formatter:off
5960
return new ECKey.Builder(curve, publicKey)
60-
.privateKey(privateKey)
61-
.keyID(UUID.randomUUID().toString())
62-
.build();
61+
.privateKey(privateKey)
62+
.keyID(UUID.randomUUID().toString())
63+
.build();
6364
// @formatter:on
6465
}
6566

6667
public static OctetSequenceKey generateSecret() {
6768
SecretKey secretKey = KeyGeneratorUtils.generateSecretKey();
6869
// @formatter:off
6970
return new OctetSequenceKey.Builder(secretKey)
70-
.keyID(UUID.randomUUID().toString())
71-
.build();
71+
.keyID(UUID.randomUUID().toString())
72+
.build();
7273
// @formatter:on
7374
}
7475
}

0 commit comments

Comments
 (0)