ARTICLE AD BOX
I trying to setup cooperation of gateway and authorization server.
help me to find error in the program, when i authenticate me and enter to web authentication form username=admin and password=1 i getting http error code 403 - forbidden and browser shows web page with words 'Access denied'. The project consists two maven modules: spring cloud gateway and spring authorization server
authorization server returns the authorization code when i put link http://localhost:9000/oauth2/authorize?response_type=code&client_id=gateway&redirect_uri=http://localhost:8080/ to the browser. And returns access_token when i execute code:
curl -X POST 'http://localhost:9000/oauth2/token' \ --header "Authorization: Basic Z2F0ZXdheTpzZWNyZXQ=" \ --header "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=gateway" \ -d "redirect_uri=http://localhost:8080/" \ -d "grant_type=authorization_code" \ -d "code=EHZl1xZY7WNF0hNzcWbA8jgBYzlAoQWphmBUEaZLUE2VAw0hDIDB4A-E6emLZl_rK3tRm8HCEYRb8vvAKvc9NVACQnFJ6RO3Yeo19O9ibyFtU2gYmn3BJKgM0zep6CF9"but when i trying to authenticate through localhost:8080/login i get 403 in Network. Authorizations server has empty log and gateway shows :
No RouteDefinition found for [Exchange: GET http://localhost:8080/favicon.ico]
Another glitch - when i follow the link with localhost:8080 - http://localhost:8080/oauth2/authorize?response_type=code&client_id=gateway&redirect_uri=http://localhost:8080/ browser redirects to localhost:9000/login
//Auth2CloudGatewayApplication.java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Auth2CloudGatewayApplication { public static void main(String[] args) { SpringApplication.run(Auth2CloudGatewayApplication.class, args); } } //GatewaySecurityConfig.java import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtValidators; import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository; import static org.springframework.security.config.Customizer.withDefaults; @Configuration @EnableWebFluxSecurity public class GatewaySecurityConfig { @Value("${provider.issuer-uri}/oauth2/jwks") private String jwkSetUri; @Value("${provider.issuer-uri}") private String issuerUri; @Bean SecurityWebFilterChain defaultSecurityFilterChain(ServerHttpSecurity http) throws Exception { http.securityContextRepository(NoOpServerSecurityContextRepository.getInstance()) .authorizeExchange(exchanges -> { exchanges.pathMatchers("/login").permitAll(); exchanges.pathMatchers("/oauth2/**").permitAll(); exchanges.pathMatchers("/index.html").permitAll(); exchanges.pathMatchers("/favicon.ico").permitAll(); exchanges.pathMatchers("/assets/**").permitAll(); exchanges.pathMatchers("/**").permitAll(); exchanges.anyExchange().authenticated(); }) .oauth2ResourceServer(oauth2 -> oauth2 .jwt( withDefaults()) ); return http.build(); } @Bean public ReactiveJwtDecoder jwtDecoder() { NimbusReactiveJwtDecoder jwtDecoder= NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build(); OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri); jwtDecoder.setJwtValidator( new DelegatingOAuth2TokenValidator<>(withIssuer));//, withOperation) ); return jwtDecoder; } } //GatewayRoutesConfig.java import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class GatewayRoutesConfig { @Value("${provider.issuer-uri}") String authorization_server; @Bean public RouteLocator gatewayRoutes(RouteLocatorBuilder routeLocatorBuilder) { return routeLocatorBuilder.routes() .route("login_page", rt -> rt.path("/login/**") .filters(f -> f.removeRequestHeader("Cookie") .removeRequestHeader("Set-Cookie")) .uri(authorization_server)) .route("/oauth2_path", rt -> rt.path("/oauth2/**") .uri(authorization_server)) .route("tiktok_path", rt -> rt.path("/tiktok/**") .uri(authorization_server)) .build(); } } #application.yml server: port: 8080 spring: web: resources: static-locations: classpath:/static/ security: oauth2: client: registration: gateway: provider: spring client-id: gateway client-secret: secret authorization-grant-type: authorization_code redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" scope: openid,resource.read provider: spring: issuer-uri: ${provider.issuer-uri} resourceserver: jwt: issuer-uri: ${app.auth-server} logging: level: root: INFO org.springframework.security: trace org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator: INFO org.springframework.cloud.gateway: trace org.springframework.security.oauth2: trace provider: issuer-uri: http://localhost:9000 eureka: defaultZoneUrl: http://u:p@localhost:8761/eureka/ app: auth-server: http://localhost:9000and pom.xml for spring cloud gateway:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>4.0.0</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.forum</groupId> <artifactId>auth2_cloud_gateway</artifactId> <version>0.0.1-SNAPSHOT</version> <name>auth2_cloud_gateway</name> <description>auth2_cloud_gateway</description> <properties> <java.version>17</java.version> <spring-cloud.version>2025.1.0-RC1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security-oauth2-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway-server-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security-oauth2-client-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-resource-server</artifactId> <!--<version>7.0.0</version>--> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>and spring authorization server sources:
//AuthorizationServerApplication.java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class AuthorizationServerApplication { public static void main(String[] args) { SpringApplication.run(AuthorizationServerApplication.class, args); } } //AuthorizationServerConfig.java import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.List; import java.util.UUID; import java.util.function.Consumer; import static org.springframework.security.config.Customizer.withDefaults; @Configuration(proxyBeanMethods = false) @EnableWebSecurity public class AuthorizationServerConfig { private static final Logger LOG = LoggerFactory.getLogger(AuthorizationServerConfig.class); @Value("${auth-provider.issuer-uri}") private String issuer_uri; @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer(); authorizationServerConfigurer .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint .authenticationProviders(configureAuthenticationValidator()) ); RequestMatcher endpointsMatcher = authorizationServerConfigurer .getEndpointsMatcher(); http.securityMatcher(endpointsMatcher) .authorizeHttpRequests(authorize ->{ authorize.requestMatchers("/oauth2").permitAll(); authorize.requestMatchers("/login").permitAll(); authorize.anyRequest().authenticated(); }) .apply(authorizationServerConfigurer); http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) .oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0 http .exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")) ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(withDefaults())); WebMvcAutoConfiguration a=null; return http.build(); } @Bean public RegisteredClientRepository registeredClientRepository() { RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("gateway") .clientSecret("{noop}secret") .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) // it was not heer .redirectUri("http://localhost:8080/swagger-ui/index.html") .scope(OidcScopes.OPENID) .scope("resource.write") .scope("resource.read") .clientSettings(ClientSettings.builder().requireProofKey(false).build()) .tokenSettings(TokenSettings.builder() .accessTokenTimeToLive(Duration.of(60*24*140, ChronoUnit.MINUTES)) .refreshTokenTimeToLive(Duration.of(120*24*140, ChronoUnit.MINUTES)) .build()) .build(); return new InMemoryRegisteredClientRepository(registeredClient); }@Bean public UserDetailsService users() { UserDetails user = User.withDefaultPasswordEncoder() .username("admin") .password("1") .roles("ADMIN") .build(); return new InMemoryUserDetailsManager(user); } @Bean public JWKSet getJwkSet(){ RSAKey rsaKey = generateRsa(); JWKSet jwkSet = new JWKSet(rsaKey); return jwkSet; } @Bean public JWKSource<SecurityContext> jwkSource(JWKSet jwkSet) { return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); } @Bean public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); } private static RSAKey generateRsa() { KeyPair keyPair = generateRsaKey(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // delete next line if you can System.out.println("Oper RSA key "+keyPair.getPublic() ); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); return new RSAKey.Builder(publicKey) .privateKey(privateKey) .keyID(UUID.randomUUID().toString()) .build(); } private static KeyPair generateRsaKey() { KeyPair keyPair; try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); keyPair = keyPairGenerator.generateKeyPair(); } catch (Exception ex) { throw new IllegalStateException(ex); } return keyPair; } @Bean public AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().issuer(issuer_uri).build(); } /* @Bean public ProviderSettings providerSettings() { return ProviderSettings.builder() .issuer(issuer_uri) //"http://localhost:9000") //// .build(); }*/ private Consumer<List<AuthenticationProvider>> configureAuthenticationValidator() { return (authenticationProviders) -> authenticationProviders.forEach((authenticationProvider) -> { if (authenticationProvider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider) { Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator = // Override default redirect_uri validator new CustomRedirectUriValidator() // Reuse default scope validator .andThen(OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_SCOPE_VALIDATOR); ((OAuth2AuthorizationCodeRequestAuthenticationProvider) authenticationProvider) .setAuthenticationValidator(authenticationValidator); } }); } static class CustomRedirectUriValidator implements Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> { @Override public void accept(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) { OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = authenticationContext.getAuthentication(); RegisteredClient registeredClient = authenticationContext.getRegisteredClient(); String requestedRedirectUri = authorizationCodeRequestAuthentication.getRedirectUri(); LOG.trace("Will validate the redirect uri {}", requestedRedirectUri); // Use exact string matching when comparing client redirect URIs against pre-registered URIs if (!registeredClient.getRedirectUris().contains(requestedRedirectUri)) { LOG.trace("Redirect uri is invalid!"); OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST); //ithing i dont need it throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null); //i think i dont need IT } LOG.trace("Redirect uri is OK!"); } } @Bean //https://stackoverflow.com/questions/36809528/spring-boot-cors-filter-cors-preflight-channel-did-not-succeed public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("*")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); configuration.setAllowCredentials(true); configuration.setAllowedHeaders(Arrays.asList("*")); configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token")); configuration.setExposedHeaders(Arrays.asList("x-auth-token")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); LOG.error("CorsConfigurationSource corsConfigurationSource"); return source; } @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**").allowedOrigins("http://localhost:3000"); LOG.error("Cors Mapping from configurer"); } }; } } //DefaultSecurityConfig.java @Configuration @EnableWebSecurity public class DefaultSecurityConfig { @Bean SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http .csrf(CsrfConfigurer::disable) .authorizeHttpRequests(authorizeRequests -> { authorizeRequests.requestMatchers("/index.html").permitAll(); authorizeRequests.requestMatchers("/favicon.ico").permitAll(); authorizeRequests.requestMatchers("/assets/**").permitAll(); authorizeRequests.requestMatchers("/jwks").permitAll(); authorizeRequests.requestMatchers("/oauth2").permitAll(); authorizeRequests.requestMatchers("/oauth2/**").permitAll(); authorizeRequests.requestMatchers("/login").permitAll(); authorizeRequests.requestMatchers("/**").permitAll(); authorizeRequests .anyRequest() .authenticated(); } ); http .formLogin(withDefaults()); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { PasswordEncoder PASSWORD_ENCODER = PasswordEncoderFactories.createDelegatingPasswordEncoder(); return PASSWORD_ENCODER; } } //JwkKeyEndpoint.java import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWKSet; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import java.util.*; @RestController public class JwkKeyEndpoint { @Autowired private JWKSet jwkSet; @GetMapping("/jwks") @ResponseBody public String getKey() { jwkSet.toPublicJWKSet().getKeys(); List<JWK> jwkValueList = jwkSet.toPublicJWKSet().getKeys(); return jwkValueList.get(0).toString(); } } #application.yml server: port: 9000 logging: level: #root: TRACE org.springframework.security: trace application.localhost-path: localhost auth-provider: issuer-uri: "http://localhost:9000" auth-server: get-jwk-link: "http://localhost:9000/jwks" spring: security: oauth2: resourceserver: jwt: issuer-uri: http://${application.localhost-path}:9000and pom.xml for spring authorization server:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>4.0.0</version> <!-- was 3.0.4 --> <relativePath/> <!-- lookup parent from repository 2.7.5 --> </parent> <groupId>com.forum</groupId> <artifactId>authorization-server</artifactId> <version>0.0.1-SNAPSHOT</version> <name>authorization-server</name> <description>authorization-server</description> <properties> <java.version>17</java.version> <spring-cloud.version>2025.0.0</spring-cloud.version><!-- 2022.0.1 --> <spring-auth-server.version>7.0.0</spring-auth-server.version><!-- 1.0.0 but was 0.3.1 --> </properties> <dependencies> <!-- --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-authorization-server</artifactId> <version>${spring-auth-server.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency><!-- --><!-- added in boot 4.0.0 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <!--<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.42</version><!-- was 1.18.30 --> <scope>provided</scope> </dependency> <!-- <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> <version>1.4.199</version> </dependency>--> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <!-- <version>42.7.5</version> --> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>7.0.10.Final</version> <!--was 6.5.3.Final; but before it was empty version --> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.12.6</version> <!-- --> <scope>compile</scope> </dependency> <!-- --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.12.6</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.12.6</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.3.5</version> </dependency><!-- because of boot 4.0.0 --> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <!--<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>3.3.1</version> <configuration> <nonFilteredFileExtensions> <nonFilteredFileExtension>esbuild</nonFilteredFileExtension> <nonFilteredFileExtension>swf</nonFilteredFileExtension> </nonFilteredFileExtensions> </configuration> <executions> <execution> <id>Copy my VueJS app into my Spring Boot target static folder </id> <phase>process-resources</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>target/classes/static</outputDirectory> <resources> <resource> <directory>/home/user/Downloads/vscode/BuildingRealWorldVue3/vue-3-pinia-jwt-authentication-example-master/dist</directory> <filtering>true</filtering> </resource> </resources> </configuration> </execution> </executions> </plugin>--> </plugins> </build> </project>