1

I have following spring boot app with minimal configuration

application.properties

server.port=8081
spring.security.oauth2.resourceserver.jwt.issuer-uri = http://localhost:8080/auth/realms/master

pom.xml

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.nurgasemetey</groupId>
    <artifactId>springboot-keycloak</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-keycloak</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-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
        </dependency>

controller

@RestController
@RequestMapping("/users")
public class UsersController {
    @GetMapping("/status/check")
    public String status(@AuthenticationPrincipal Jwt principal) {
        return "working";
    }
}

It seems that Spring Boot Oauth2 doesn't use public key, as I see in code:

OAuth2ResourceServerProperties

/**
         * JSON Web Key URI to use to verify the JWT token.
         */
        private String jwkSetUri;

        /**
         * JSON Web Algorithm used for verifying the digital signatures.
         */
        private String jwsAlgorithm = "RS256";

        /**
         * URI that can either be an OpenID Connect discovery endpoint or an OAuth 2.0
         * Authorization Server Metadata endpoint defined by RFC 8414.
         */
        private String issuerUri;

        /**
         * Location of the file containing the public key used to verify a JWT.
         */
        private Resource publicKeyLocation;

But I didn't give publicKeyLocation, but app can verify without public key.

Under the hood it uses JwtIssuerValidator and JwtTimestampValidator validators.

On other hand, with express-jwt, it requires public key for offline verification

const express = require('express');
const jwt = require('express-jwt');
const app = express();
const secret = 'secret';
const fs = require('fs');
var publicKey = fs.readFileSync('public.pub');


app.get('/protected', jwt({ secret: publicKey, algorithms: ['RS256'] }), (req, res) => {
  res.send('protected');
})
app.listen(3000, () => console.log('server started'));

How the Spring Boot Oauth verifies without public key?

nurgasemetey
  • 752
  • 3
  • 15
  • 39
  • 2
    What does `http://localhost:8080/auth/realms/master` provide? Most token issuers should expose a `/.well-known/openid-configuration` endpoint which Spring Boot will attempt to find automatically. This endpoint tells Spring Boot how to find the JWKS endpoint, which is where the public key may be found. – Thomas Kåsene Oct 02 '20 at 15:06
  • Yes it exposes, the public key. Thanks for clarification – nurgasemetey Oct 02 '20 at 16:14

1 Answers1

1

Self answer.

Firstly, it seems that http://localhost:8080/auth/realms/master exposes public key. As said in this Generate JWT Token in Keycloak and get public key to verify the JWT token on a third party platform - Stack Overflow and in this comment to this question by @Thomas Kåsene

Secondly, I digged spring boot oauth2 code and stumbled to this code in

ReactiveOAuth2ResourceServerJwkConfiguration

@Bean
        @Conditional(IssuerUriCondition.class)
        ReactiveJwtDecoder jwtDecoderByIssuerUri() {
            return ReactiveJwtDecoders.fromIssuerLocation(this.properties.getIssuerUri());
        }

JwtDecoderProviderConfigurationUtils

private static Map<String, Object> getConfiguration(String issuer, URI... uris) {
        String errorMessage = "Unable to resolve the Configuration with the provided Issuer of " +
                "\"" + issuer + "\"";
        for (URI uri : uris) {
            try {
                RequestEntity<Void> request = RequestEntity.get(uri).build();
                ResponseEntity<Map<String, Object>> response = rest.exchange(request, typeReference);
                Map<String, Object> configuration = response.getBody();

                if (configuration.get("jwks_uri") == null) {
                    throw new IllegalArgumentException("The public JWK set URI must not be null");
                }

                return configuration;
            } catch (IllegalArgumentException e) {
                throw e;
            } catch (RuntimeException e) {
                if (!(e instanceof HttpClientErrorException &&
                        ((HttpClientErrorException) e).getStatusCode().is4xxClientError())) {
                    throw new IllegalArgumentException(errorMessage, e);
                }
                // else try another endpoint
            }
        }
        throw new IllegalArgumentException(errorMessage);
    }

which seems to fetch public key from issuer-uri given in application.properties. After it fetched it verifies jwt tokens with fetched public key.

To test

Close your jwt provider, keycloak in my case and run spring boot application, then it gives

Caused by: java.lang.IllegalArgumentException: Unable to resolve the Configuration with the provided Issuer of "http://localhost:8080/auth/realms/master"

nurgasemetey
  • 752
  • 3
  • 15
  • 39
  • 1
    Thanks for the question and answer too. Although I was keen to know if microservice fetches public key once or on every request ? – Sarang Apr 21 '21 at 03:07
  • Spring Boot fetches public key once on startup. – nurgasemetey Apr 21 '21 at 07:12
  • The token validation is happening in the spring boot with the Public key fetched from KeyCloak. So while validating the token it's not calling the KeyCloak. Say if a user is deleted from KeyCloak, even the token validation process gets success since it's not calling the KeyClock endpoint to validate the token, it's just happening in the Spring boot. How to handle this scenario? Is there a way to make the token validation happen against Auth Server (I.e KeyCloak) in spring boot? – ARods Jul 15 '21 at 07:31
  • @ARods I see your point. One way to solve it is that tokens have small expiration duration. That's way, it solves already. If it is not suitable for you, maybe Keycloak SDK nees to be integrated. – nurgasemetey Jul 16 '21 at 09:12