Search code examples
javaspring-bootspring-securityoauth-2.0spring-security-oauth2

<Spring Security> Access token not included in header when client calls resource server


Hi I am learning about Spring Security. I was trying to produce an OAuth2 client and resource server setup, basically following the guildelines on https://dzone.com/articles/implement-oauth-20-easily-with-spring-boot-and-spr .

When attempting to call the endpoint on the resource server from the client, it gives a HTTP 401.

2021-03-04 21:38:33.355 ERROR 99501 --- [ctor-http-nio-2] reactor.core.publisher.Operators         : Operator called default onErrorDropped

reactor.core.Exceptions$ErrorCallbackNotImplemented: org.springframework.web.reactive.function.client.WebClientResponseException$Unauthorized: 401 Unauthorized from GET http://localhost:8081/api/resource/hello
Caused by: org.springframework.web.reactive.function.client.WebClientResponseException$Unauthorized: 401 Unauthorized from GET http://localhost:8081/api/resource/hello
    at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:181) ~[spring-webflux-5.3.4.jar:5.3.4]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    |_ checkpoint ⇢ 401 from GET http://localhost:8081/api/resource/hello [DefaultWebClient]
Stack trace:
        at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:181) ~[spring-webflux-5.3.4.jar:5.3.4]
        at 

The endpoint on the resource server is secured by OAuth2. Supppose after providing credentials (for okta in this case), in the back channel, the client application would get the grant code, then exchange this for access token, and afterwards include access token in the header when calling the endpoint on the resource server (Kindly correct if I misunderstood).

The above are done in the back channel, and I am not sure how the client called the resource server.

I tried to remove the spring security dependecies to disable OAuth2 on the resource server. The call to the resource server had no issue in this case.

I have put the source on GitHub.

And the key files for the resource server are as below

OAuth2 resource server HTTP security config

package com.somecompany.configuration;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors()
            .and()
                .authorizeRequests()
                    .antMatchers("/", "/error", "/webjars/**", "/actuator/**")
                        .permitAll()
                    .anyRequest()
                        .authenticated()
            .and()
                .oauth2ResourceServer()
                    .jwt();
    }
}

OAuth2 resource server controller

package com.somecompany.controller;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/resource")
public class Oauth2DemoResourceServerController {

    @GetMapping("/hello")
    @CrossOrigin
    public String api() {
        return "Made it to protected api on resource server!";
    }
}

OAuth2 resource server properties

management.endpoints.web.exposure.include: httptrace

spring:
security:
    oauth2:
    resourceserver:
        jwt:
        jwk-set-uri: https://dev-27548664.okta.com/oauth2/default/v1/keys

OAuth2 resource server pom.xml

<?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>2.4.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.somecompany</groupId>
    <artifactId>oauth2-demo-resource-server</artifactId>
    <version>1.0.0</version>
    <name>oauth2-demo-resource-server</name>
    <description>oauth2-demo-resource-server</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
        </dependency>
        <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.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-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-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

[Update 31 Mar 2021]

I have added a filter on the resource server, and it showed that the request to the resource server does not include the authentication header, hence a 401 is expected. I therefore suspect it is an issue on the WebClient configuration. Please see if there is any ideas.


Solution

  • I have somehow got the setup working by changing the WebClient configuration with a servlet approach.
    In summary the issue is on the client side, but not on the resource server.

    (1) In the client's WebClient configuration, use ClientRegistrationRepository instead of ReactiveClientRegistrationRepository (i.e. just follow what the tutorial described in https://dzone.com/articles/implement-oauth-20-easily-with-spring-boot-and-spr).

    (2) Add the "spring-boot-starter-web" dependency. This step is important because without it, the ClientRegistrationRepository bean cannot be found.

    (3) Update the pom.xml to refresh the dependencies.

    (4) Amend the use of Webclient in the RestController, use block() to synchronously wait for a response from the resource server.


    Though the setup is working, I am not sure how to configure the setup to work with an async approach using ReactiveClientRegistrationRepository. If there is anyone who has any idea please feel free to share, thanks.

    P.S. All the changes have been updated to the Git repo mentioned above for your reference