Cia Kazemi
Cia Kazemi

Reputation: 13

Spring Cloud Gateway cannot authorize jwt token when request sends by Browser

I am using spring boot 3.0.2 and spring cloud gateway. also, I using spring-boot-starter-oauth2-resource-server to obtain and check jwt token on Keycloak server for security. my build.gradle file like this:

plugins {
  id 'java'
  id 'org.springframework.boot' version '3.0.2'
  id 'io.spring.dependency-management' version '1.1.3'
}

dependencies {
  implementation 'org.springframework.cloud:spring-cloud-starter'
  implementation 'org.springframework.boot:spring-boot-starter-webflux'      
  implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
  implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
  implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
  implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
  implementation 'org.springframework.boot:spring-boot-starter-actuator'
  implementation 'com.shahrtech.service.libs:service-common-libs:0.0.61'
  implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j'
  implementation 'org.springframework.cloud:spring-cloud-starter-loadbalancer'
}

and config file on application.yml is :

spring :
 cloud:
   gateway:
    default-filters:
     - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-  Origin
   globalcors:
     corsConfigurations:
      '[/**]':
        allowedOrigins: "*"
        allowedMethods: "*"
        allowedHeaders: "*"
       
   routes:        
    - id: auth-service
      uri: http://x.x.x.x:8085/
      predicates:
        - Path=/auth-service/v1.0/**
      filters:
        - RewritePath=/auth-service/v1.0/(?<path>.*),/auth-service/v1.0/$\{path}
        - RequestHashing=SHA-256

    
    - id: customer-service
      uri: http://x.x.x.x:8090/
      predicates:
        - Path=/customer-service/v1.0/**
      filters:
        - RewritePath=/customer-service/v1.0/(?<path>.*),/customer-service/v1.0/$\{path}
        - RequestHashing=SHA-256
        - TokenRelay= 
  security:
oauth2:
  client:
    registration:
      keycloak:
        client-id: login-app
        authorization-grant-type: authorization_code
        scope: openid


    provider:
      keycloak:
        issuer-uri: http://x.x.x.x:9002/auth/realms/app
        user-name-attribute: preferred_username

  resourceserver:
    jwt:
      issuer-uri: http://x.x.x.x:9002/auth/realms/app

and ApiGatewayServiceApplication.java is :

@SpringBootApplication
@EnableDiscoveryClient
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class ApiGatewayServiceApplication {


@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
    http.cors().and().csrf()
            .csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
            .and()
            .authorizeExchange()
            .anyExchange().authenticated()
            .and()
            .oauth2ResourceServer()
            .jwt();
    return http.build();
}

@Bean
CorsConfigurationSource corsConfiguration() {
    CorsConfiguration corsConfig = new CorsConfiguration();
    corsConfig.applyPermitDefaultValues();
    corsConfig.addAllowedMethod(HttpMethod.PUT);
    corsConfig.addAllowedMethod(HttpMethod.DELETE);
    corsConfig.setAllowedOrigins(Arrays.asList("*", "**"));
    corsConfig.setAllowCredentials(true);

    UrlBasedCorsConfigurationSource source =
            new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", corsConfig);
    return source;
}
public static void main(String[] args) {
    SpringApplication.run(ApiGatewayServiceApplication.class, args);
}

}

I also using this Bean to change security config:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {


    http
            .authorizeRequests()
            .requestMatchers(new AntPathRequestMatcher("/auth-service/**")).permitAll()
            .anyRequest().permitAll()
            .and()
            .httpBasic()
            .and()
            .csrf().disable()
            .cors().disable();
    return http.build();
}

everythins is OK.When calling api with token in header in Postman everything works fine. But we try to develop a sample JS and ReactJs code to obtain API we receive error 401 and console log and trace it shows me that request headers authorization not accepted. We research and find this post: Spring Gateway and Auth0: IllegalArgumentException: Unable to find GatewayFilterFactory with name TokenRelay that say adding TokenRelay on config. I Added TokenRelay on config and try again but error exists.

everythins is OK.When calling api with token in header in Postman everything works fine. But we try to develop a sample JS and ReactJs code to obtain API we receive error 401 and console log and trace it shows me that request headers authorization not accepted. We research and find this post: Spring Gateway and Auth0: IllegalArgumentException: Unable to find GatewayFilterFactory with name TokenRelay that say adding TokenRelay on config. I Added TokenRelay on config and try again but error exists.

Upvotes: 0

Views: 652

Answers (2)

Marc
Marc

Reputation: 113

TokenRelay filter has a bug that is fixed in version 4.1.1 of spring-cloud-gateway https://github.com/spring-cloud/spring-cloud-gateway/releases/tag/v4.1.1

Upvotes: -1

ch4mp
ch4mp

Reputation: 12754

The TokenRelay filter replaces a session cookie with the access token in session. To have this access token in session, it of course needs sessions, but also oauth2Login() (Spring OAuth2 client implementation with an authorization_code registration).

Do not mix oauth2Login() and oauth2ResourceServer() in a single SecurityFilterChain, security needs are too different:

  • security is based on
    • tokens for resource servers
    • session on clients with authorization_code
  • when trying to access a protected resource without a valid authorization, you should be answered
    • 401 Unauthorized from a resource server (missing or invalid token)
    • 302 Redirect to login (the session does not contain an authorized client yet)
  • about CSRF protection, it is:
    • mandatory on clients with login (because security relies on sessions)
    • useless on stateless resource servers

According to latest recommendations, we should avoid using "public" OAuth2 clients, and anything running in a browser or a mobile application can only be a "public" OAuth2 client => the code in your browser (plain Javascript or SPA framework) should not fetch tokens from the authorization server.

You should probably:

  • configure your gateway with oauth2Login() and the TokenRelay filter (minimal dependencies are spring-boot-starter-oauth2-client and spring-cloud-gateway)
  • configure downstream REST APIs with oauth2ResourceServer() (minimal dependencies are spring-boot-starter-oauth2-resource-server and spring-boot-starter-web or spring-boot-starter-webflux)

The requests from the browser to the Gateway will be authorized with session cookie (and should be protected against CSRF attacks).

The requests from the Gateway (or Postman) to the REST APIs will be authorized with access token.

P.S.

In the case of a framework with a server-side rendering part, like next.js offers for instance, it is OK to use a lib implementing the OAuth2 client in that server-side part: this server side lib can implement the same important features regarding OAuth2 and requests authorization:

  • be a "confidential" client (query the authorization server token endpoint with a client ID and a client secret)
  • keep tokens in server memory (do not leak tokens to the user device)
  • replace session cookie with access tokens before forwarding a request from device part of the app to the resource server(s) (as the TokenRelay does on spring-cloud-gateway)

Upvotes: 0

Related Questions