3

In my design I have an API gateway (spring cloud api gateway), authorization server (Oauth2) and a resource server (microservice). (I have also a CAS server but now it can be ignored for simplicity)

I just want to use API gateway to redirect client requests.

  • If the user is not authenticated, request should be sent to authorization server and after authentication and authorization process is finished, authorization server should return JSESSION and JWT in access_token header. After that API gateway should return JSESSION AND JWT to client.
  • If the user is authenticated request should be sent to resource server.

So API gateway would be stateless and used just for redirection of requests. But when I successfully login, JSESSION and SESSION cookies are set and JWT is not sent to client. (As far as understand from logs, API Gateway has JWT but instead of simply sending it to client, stores it with SESSION key and send SESSION key)

How can I make my API gateway stateless and send JWT in access_token header to client?

Here is my code:

Authorization Server:

@EnableWebSecurity
@Order(1)
public class MySecurityConfigurationDev extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/login/**", "/oauth/**", "/userinfo")
                .permitAll()
                .and()
                .formLogin().permitAll().and()
                .authorizeRequests().anyRequest().authenticated();
        http.csrf().disable();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("user").password("{noop}pass").roles("ADMIN");
    }
}

@Configuration
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Bean
    public KeyPair keyPair() {
        return new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), "mystorepass".toCharArray()).getKeyPair("mytestkey");
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setKeyPair(keyPair());
        return converter;
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager).accessTokenConverter(accessTokenConverter());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.tokenKeyAccess("permitAll()").
                checkTokenAccess("permitAll()").
                allowFormAuthenticationForClients();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("first-client").secret("{noop}noonewilleverguess")
                .authorities("ADMIN")
                .authorizedGrantTypes("client_credentials", "password", "authorization_code", "refresh_token")
                .scopes("custom_mod")
                .accessTokenValiditySeconds(60*30)
                .refreshTokenValiditySeconds(60*60)
                .autoApprove(true)
                .redirectUris("http://localhost:8085/login/oauth2/code/login-client");
    }
}

API Gateway:

@EnableWebFluxSecurity
public class MySecurityConfiguration {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http, ReactiveClientRegistrationRepository clientRegistrationRepository) {
        http.authorizeExchange().anyExchange().authenticated().and().oauth2Login();
        return http.build();
    }
}

logging:
  level:
    root: TRACE
    org.springframework.cloud.gateway: DEBUG
    reactor.netty.http.client: DEBUG
    com.netflix.discovery.DiscoveryClient: error
    org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator: error
    com.netflix.discovery.shared.resolver.aws.ConfigClusterResolver: error

server:
  port: 8085

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8010/eureka

spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      default-filters:
      - RemoveRequestHeader=Cookie
      - TokenRelay=
      routes:
      - id: user_route
        predicates:
          - Path=/gateway-user/**
        filters:
          - RewritePath=/gateway-user/(?<segment>.*), /user/$\{segment}
          - RemoveRequestHeader=Cookie
          - name: Hystrix
            args:
              name: fallbackCommand
              fallbackUri: forward:/fallback/error
        uri: "lb://USER-RESOURCE-SERVER"
      - id: address_route
        predicates:
          - Path=/gateway-address/**
        filters:
          - RewritePath=/gateway-address/(?<segment>.*), /address/$\{segment}
        uri: "lb://ADDRESS-RESOURCE-SERVER"
  security:
    oauth2:
      client:
        registration:
          login-client:
            provider: uaa
            client-id: first-client
            client-secret: noonewilleverguess

            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            scope: custom_mod
        provider:
          uaa:
            authorization-uri: http://localhost:8094/oauth/authorize
            token-uri: http://localhost:8094/oauth/token
            user-info-uri: http://localhost:8094/userinfo
            user-name-attribute: user_name
            user-info-authentication-method: form

**Spring Boot version is 2.1.6-RELEASE for both servers.

H.Ç.T
  • 3,335
  • 1
  • 18
  • 37
  • The gateway itself is stateless. It must be something you are doing that introduces state. – spencergibb Mar 17 '20 at 15:11
  • @spencergibb hey, thnx for response. i implement this blog (https://spring.io/blog/2019/08/16/securing-services-with-spring-cloud-gateway) and codes (https://github.com/benwilcock/spring-cloud-gateway-demo/tree/master/security-gateway) exactly with no change. but it seems stateful. jwt is still stored as session. i think it is bug. if it is not, can you provide some demo? thanks – denizg Mar 18 '20 at 12:49
  • I can't say anything about jwt as that is not from gateway – spencergibb Mar 18 '20 at 15:10
  • 2
    @spencergibb, I think OP is talking about `WebSessionServerOAuth2AuthorizedClientRepository` which makes gateway stateful by linking authentication with HttpSession. – Ashay Thorat Sep 19 '20 at 14:50
  • @denizg i was trying to setup the same way with google-oauth2 just changes the google auth2 provider url in secured-gateway and change jwk-set-uri : https://www.googleapis.com/oauth2/v3/certs but got `description contains invalid ASCII characters, it must conform to RFC 6750` – user1693371 Jun 26 '22 at 19:47

0 Answers0