Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring security oAuth 2.0 (Invalid token does not contain resource id (oauth2-resource))

I have written a simple REST API backend using spring-boot and Spring REST. The endpoints would be consumed by various third party applications & are working as expected.

Now I would like to secure these endpoints using spring security and OAUTH 2.0.

I am planning on using an external OAuth 2.0 authorization server such that my rest-api-backend (the spring-boot app) would act as a resource server and verify the access tokens by calling the external authorization server's token introspection endpoint.

Whenever the client applications call my api-end-points using the access token (opaque bearer tokens obtained from the external authz server using client_credentials grant type in machine to machine flow) in the Authorization Header, Spring always throws the following error :

{
    "error": "access_denied",
    "error_description": "Invalid token does not contain resource id (oauth2-resource)"
}

After doing some research, the issue was resolved by setting the calling application's client ID as a resource ID in the following way:

 public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenServices(tokenService());
         resources.resourceId("5ght-6117-fdc6-8787-9017-20c784a9c03a");
        //  super.configure(resources);
    }

It seems that Spring validates the aud claim from the token introspection response against the value set in resource ID.

I am not able to fully understand this, Why Spring does this?

As a resource server, it should simply check whether the access token is valid, have the right scopes etc.

Is it that Spring maintains a white-list of allowed clients?

In my case, there are multiple client applications which would be accessing the rest-api-backend, so how to store their multiple client IDs?

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.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>spring-security-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-security-test</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>2.2.2.RELEASE</version>
        </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.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </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>
            </plugin>
        </plugins>
    </build>

</project>

PS: I have already checked many other similar questions on the topic but couldn't find the answer to the question I have asked here.

like image 210
Surendra Singh Khatana Avatar asked Nov 28 '25 20:11

Surendra Singh Khatana


1 Answers

When a client authenticates with an identity provider, it has the option to include a resource identifier. When configuring the client on the identity provider, usually you have the option to require the resource identifier, which will fail authentication if the client does not provide one, or an option to specify a default value for that client.

You are building a resource server and can assign it any identifier as long as it is unique to the identity provider. I usually use my service's URL as the resource identifier. Sometimes I create a URN (e.g., urn:uuid:01234567-89ab-cdef-0123-456789abcdef) when I wish to use the same resource identifier across different URLs (like I have between my dev, QA and production environments) but only have a single identity provider.

It sounds like your identity provider is using the audience when one is not provided and this is a reasonable default given that, in your case, your resource service is both the audience and the resource. This is not always the case however.

Consider the case where Facebook wants access to your Gmail contacts. In this scenario, Gmail is the resource and Facebook is the audience. Google, being the identity provider, will ask the user if he wishes to grant Facebook access to his contacts. If the user grants access (this only happens once), Google will provide a token. Otherwise, authentication fails.

Scope is a different concept. The resource provider may decide to break the available data in a resource into subsections; each subsection is given a name (or scope). In our example above, Gmail could break a contact's data into name, home address, phone number, email address, twitter handle, etc. When the user grants Facebook access to his contact list, he could limit Facebooks's access to just name and email address thus prohibiting access to the contact's home address, phone numbers and twitter feed. It is up to the resource provider (i.e., your service) to enforce this constraint.

It is possible to disable Spring's validation of the resource identifier by setting the value to null in your code above. This is what I use when Azure Active Directory is my identity provider.

resources.resourceId(null); // Disabled since Microsoft does not support resource ids.
like image 102
Faron Avatar answered Dec 01 '25 10:12

Faron



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!