403 when authentication in spring authentication server after /login in spring cloud gateway
14:04 27 Nov 2025

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 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:9000

and pom.xml for spring cloud gateway:



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        4.0.0
         
    
    com.forum
    auth2_cloud_gateway
    0.0.1-SNAPSHOT
    auth2_cloud_gateway
    auth2_cloud_gateway
    
    
        17
        2025.1.0-RC1
    
    
        
            org.springframework.boot
            spring-boot-starter-security
        
        
            org.springframework.boot
            spring-boot-starter-security-oauth2-client
        
        
            org.springframework.cloud
            spring-cloud-starter-gateway-server-webflux
        

        
            org.springframework.boot
            spring-boot-starter-security-oauth2-client-test
            test
        
        
            org.springframework.boot
            spring-boot-starter-security-test
            test
        
        
            io.projectreactor
            reactor-test
            test
        
        
            org.springframework.security
            spring-security-oauth2-resource-server
            
        
    
    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


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 jwkSource(JWKSet jwkSet) {


        return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
    }
    @Bean
    public JwtDecoder jwtDecoder(JWKSource 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> configureAuthenticationValidator() {
        return (authenticationProviders) ->
                authenticationProviders.forEach((authenticationProvider) -> {
                    if (authenticationProvider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider) {
                        Consumer 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 {

        @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 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}:9000

and pom.xml for spring authorization server:



    4.0.0

    
        org.springframework.boot
        spring-boot-starter-parent
        4.0.0 
         
    

    com.forum
    authorization-server
    0.0.1-SNAPSHOT
    authorization-server
    authorization-server

    
        17
        2025.0.0
        7.0.0
    

    
        
            org.springframework.boot
            spring-boot-starter-security
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.security
            spring-security-oauth2-authorization-server
            ${spring-auth-server.version}
        
        
            org.springframework.boot
            spring-boot-autoconfigure

        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
            org.springframework.security
            spring-security-test
            test
        
        
        
            org.projectlombok
            lombok
            1.18.42
            provided
        

        
            org.postgresql
            postgresql

        

        
        org.hibernate
        hibernate-core
               7.0.10.Final 
        
       
        
            io.jsonwebtoken
            jjwt-api
            0.12.6
                compile
         
        
            io.jsonwebtoken
            jjwt-impl
            0.12.6
            runtime
        
        
            io.jsonwebtoken
            jjwt-jackson
            0.12.6
            runtime
        

        
            org.springframework.security
            spring-security-config
        
        
            commons-logging
            commons-logging
            1.3.5
        
    
    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            

            

        
    


spring-boot