Search code examples
spring-securityspring-webfluxspring-security-oauth2spring-cloud-gateway

Pass user id as a header in downstream request. Spring Cloud Gateway + Oauth2 Resource Server


I want to implement security on my Spring cloud gateway server by making it an oAuth2 resource server. The requests are getting authenticated against my spring security authorization server. For some requests I want to pass the userId of the authenticated user as a request header to my downstream services.

Here's my route:

.route("create-deck", routeSpec -> routeSpec
                    .path("/decks")
                    .and()
                    .method(HttpMethod.POST)
                    .filters(
                            fs -> fs.addRequestHeader("userId", "ADD_USER_ID_HEADER_HERE"))
                    .uri("http://localhost:8082/"))

I have configured my authorization server to return userId as principal name:

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    User user = ur.findByUsername(username);
    if (user == null) {
      log.error("User: {} not found", username);
      throw new UsernameNotFoundException("User: " + username + " not found");
    }

    return new org.springframework.security.core.userdetails.User(String.valueOf(user.getId()), user.getPassword(), authorities);
}

Note: I have a custom domain object User that I store in my database.

The route is getting successfully authenticated and the token contains userId as subject. I just need help on how to get the subject and pass that to my downstream request as a request header.

I am using the following dependencies:

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-oauth2-jose</artifactId>
        <version>5.6.2</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>

Solution

  • You could create custom filter that will be applied to all requests. Here is an example when user is a part of the jwt token.

    public class UserHeaderFilter implements GlobalFilter {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            return ReactiveSecurityContextHolder.getContext()
                    .filter(c -> c.getAuthentication() != null)
                    .flatMap(c -> {
                        JwtAuthenticationToken jwt = (JwtAuthenticationToken) c.getAuthentication();
    
                        String user = (String) jwt.getTokenAttributes().get("user");
                        if (Strings.isNullOrEmpty(user)) {
                            return Mono.error(
                                    new AccessDeniedException("Invalid token. User is not present in token.")
                            );
                        }
    
                        ServerHttpRequest request = exchange.getRequest().mutate()
                                .header("x-user", user).build();
    
                        return chain.filter(exchange.mutate().request(request).build());
                    })
                    .switchIfEmpty(chain.filter(exchange));
        }
    }