Giter Club home page Giter Club logo

navikt / mock-oauth2-server Goto Github PK

View Code? Open in Web Editor NEW
217.0 10.0 52.0 3.98 MB

A scriptable/customizable web server for testing HTTP clients using OAuth2/OpenID Connect or applications with a dependency to a running OAuth2 server (i.e. APIs requiring signed JWTs from a known issuer)

License: MIT License

Kotlin 93.11% FreeMarker 2.45% CSS 0.52% Java 3.50% HTML 0.42% JavaScript 0.01%
oauth2 mock mock-oauth2-server kotlin java docker openid-connect oidc jwt security

mock-oauth2-server's Introduction

Build Maven Central

mock-oauth2-server

A scriptable/customizable web server for testing HTTP clients using OAuth2/OpenID Connect or applications with a dependency to a running OAuth2 server (i.e. APIs requiring signed JWTs from a known issuer). The server also provides the necessary endpoints for token validation (endpoint for JWKS) and ID Provider metadata discovery ("well-known" endpoints providing server metadata)

mock-oauth2-server is written in Kotlin using the great OkHttp MockWebServer as the underlying server library and can be used in unit/integration tests in both Java and Kotlin or in any language as a standalone server in e.g. docker-compose.

Even though the server aims to be compliant with regards to the supported OAuth2/OpenID Connect specifications, you should never use it for anything else than tests. That being said, when developing OAuth2 clients you should always verify that the expected requests are being made in your tests.

Motivation

The motivation behind this library is to provide a setup such that application developers don't feel the need to disable security in their apps when running tests! If you have any issues with regards to OAuth2 and tokens et. al. and consider to disable "security" when running tests please submit an issue or a PR so that we can all help developers and security to live in harmony once again (if ever..)!

Features

  • Multi-issuer/Multi-tenancy support: the server can represent as many different Identity Providers/Token Issuers as you need (with different token issuer names) WITHOUT any setup!
  • Implements OAuth2/OpenID Connect grants/flows
    • OpenID Connect Authorization Code Flow
    • OAuth2 Client Credentials Grant
    • OAuth2 JWT Bearer Grant (On-Behalf-Of flow)
    • OAuth2 Token Exchange Grant
    • OAuth2 Refresh Token Grant
    • OAuth2 Resource Owner Password Credentials (Password Grant)
      • usage should be avoided if possible as this grant is considered insecure and removed in its entirety from OAuth 2.1
  • Issued JWT tokens are verifiable through standard mechanisms with OpenID Connect Discovery / OAuth2 Authorization Server Metadata
  • Unit/Integration test support
    • Start and stop server for each test
    • Sane defaults with minimal setup if you don't need token customization
    • Enqueue expected tokens if you need to customize token claims
    • Enqueue expected responses
    • Verify expected requests made to the server
    • Customizable through exposure of underlying OkHttp MockWebServer
  • Standalone support - i.e. run as application in IDE, run inside your app, or as a Docker image (provided)
  • OAuth2 Client Debugger - e.g. support for triggering OIDC Auth Code Flow and receiving callback in debugger app, view token response from server (intended for standalone support)

API Documentation

mock-oauth2-server

📦 Install

Gradle Kotlin DSL

Latest version Maven Central

testImplementation("no.nav.security:mock-oauth2-server:$mockOAuth2ServerVersion")

Maven

Latest version Maven Central

<dependency>
  <groupId>no.nav.security</groupId>
  <artifactId>mock-oauth2-server</artifactId>
  <version>${mock-oauth2-server.version}</version>
  <scope>test</scope>
</dependency>

Docker

Latest version GitHub release (latest SemVer including pre-releases)

docker pull ghcr.io/navikt/mock-oauth2-server:$MOCK_OAUTH2_SERVER_VERSION

⌨️ Usage

Well-Known Configuration

The mock-oauth2-server will supply different configurations depending on the url used against the server, more specifically the first path (or context root) element in your request url will specify the issuerId.

A request to http://localhost:8080/default/.well-known/openid-configuration will yield an issuerId of default with the following configuration:

{
   "issuer":"http://localhost:8080/default",
   "authorization_endpoint":"http://localhost:8080/default/authorize",
   "end_session_endpoint" : "http://localhost:8080/default/endsession",
   "revocation_endpoint" : "http://localhost:8080/default/revoke",
   "token_endpoint":"http://localhost:8080/default/token",
   "userinfo_endpoint":"http://localhost:8080/default/userinfo",
   "jwks_uri":"http://localhost:8080/default/jwks",
   "introspection_endpoint":"http://localhost:8080/default/introspect",
   "response_types_supported":[
      "query",
      "fragment",
      "form_post"
   ],
   "subject_types_supported":[
      "public"
   ],
   "id_token_signing_alg_values_supported":[
     "ES256",
     "ES384",
     "RS256",
     "RS384",
     "RS512",
     "PS256",
     "PS384",
     "PS512"
   ],
  "code_challenge_methods_supported":[
      "plain", 
      "S256" 
  ]
}

The actual issuer value in a JWT will be iss: "http://localhost:8080/default"

To use another issuer with id anotherissuer simply make a request to http://localhost:8080/anotherissuer/.well-known/openid-configuration and the configuration will change accordingly.

Unit tests

Setup test
  • Start the server at a random port
  • Get url for server metadata/configuration
  • Setup your app to use the OAuth2 server metadata and conduct your tests
  • Shutdown the server
val server = MockOAuth2Server()
server.start()
// Can be anything you choose - should uniquely identify your issuer if you have several
val issuerId = "default"
// Discovery url to authorization server metadata
val wellKnownUrl = server.wellKnownUrl(issuerId).toString()
// ......
// Setup your app with metadata from wellKnownUrl and do your testing here
// ......
server.shutdown()
Testing an app requiring user login with OpenID Connect Authorization Code Flow
  • Setup test like above
  • Make your test HTTP client follow redirects
  • Your callback (redirect_uri) endpoint should receive the callback request as required and be able to retrieve a token from the token endpoint.

If you need to get a login for a specific user you can use the OAuth2TokenCallback interface to provide your own or set values in the DefaultOAuth2TokenCallback

@Test
fun loginWithIdTokenForSubjectFoo() {
    server.enqueueCallback(
        DefaultOAuth2TokenCallback(
            issuerId = issuerId,
            subject = "foo"
        )
    )
  // Invoke your app here and assert user foo is logged in
}

If you need specific claims in the resulting id_token - e.g. acr or a custom claim you can also use the OAuth2TokenCallback:

@Test
fun loginWithIdTokenForAcrClaimEqualsLevel4() {
    server.enqueueCallback(
        DefaultOAuth2TokenCallback(
            issuerId = issuerId,
            claims = mapOf("acr" to "Level4")
        )
    )
  // Invoke your app here and assert acr=Level4 is present in id_token
}
Testing an API requiring access_token (e.g. a signed JWT)
val token: SignedJWT = oAuth2Server.issueToken(issuerId, "someclientid", DefaultOAuth2TokenCallback())
//use your favourite HTTP client to invoke your API and attach the serialized token
val request = // ....
request.addHeader("Authorization", "Bearer ${token.serialize()}")

If you for some reason need to manipulate the system time/clock you can configure the OAuth2TokenProvider to use a specific time, resulting in the iat claim being set to that time:

@Test
fun testWithSpecificTime() {
    val server = MockOAuth2Server(
        config = OAuth2Config(
            tokenProvider = OAuth2TokenProvider(systemTime = Instant.parse("2020-01-21T00:00:00Z")
        )
    )
    val token = server.issueToken(issuerId = "issuer1")
    // do whatever token testing you need to do here and assert the token has iat=2020-01-21T00:00:00Z
}
More examples

Have a look at some examples in both Java and Kotlin in the src/test directory:

API

Server URLs

You can retrieve URLs from the server with the correct port and issuerId etc. by invoking one of the fun *Url(issuerId: String): HttpUrl functions/methods:

val server = MockOAuth2Server()
server.start()
val wellKnownUrl = server.wellKnownUrl("yourissuer")
// will result in the following url:
// http://localhost:<a random port>/yourissuer/.well-known/openid-configuration

Standalone server

The standalone server will default to port 8080 and can be started by invoking main() in StandaloneMockOAuth2Server.kt (in kotlin) or StandaloneMockOAuth2ServerKt (in Java)

On Windows, it's easier to run the server in docker while specifying the host as localhost, e.g. docker run -p 8080:8080 -h localhost $IMAGE_NAME

Note If you want to check if the server is up and running you can visit /isalive and see if you get a 200 in return.

Configuration

The standalone server supports the following configuration by ENV variables:

Variable Description
SERVER_HOSTNAME Lets the standalone server bind to a specific hostname, by default it binds to 0.0.0.0
SERVER_PORT or PORT The port that the standalone server will listen to, defaults to 8080. The PORT environment variable may be used to run the Docker image on Heroku as per the documentation here.
JSON_CONFIG_PATH The absolute path to a json file containing configuration about the OAuth2 part of the server (OAuth2Config). More details on the format below.
JSON_CONFIG The actual JSON content of OAuth2Config, this ENV var takes precedence over the JSON_CONFIG_PATH var. More details on the format below.
LOG_LEVEL How verbose the root logging output is, defaults to INFO
LOGBACK_CONFIG You can override the default logging config in logback-standalone.xml with a path to your own logback xml file.
JSON_CONFIG

The JSON_CONFIG lets you configure the contents of the OAuth2Config class using JSON.

Example:

{
    "interactiveLogin": true,
    "httpServer": "NettyWrapper",
    "tokenCallbacks": [
        {
            "issuerId": "issuer1",
            "tokenExpiry": 120,
            "requestMappings": [
                {
                    "requestParam": "scope",
                    "match": "scope1",
                    "claims": {
                        "sub": "subByScope",
                        "aud": [
                            "audByScope"
                        ]
                    }
                }
            ]
        },
        {
            "issuerId": "issuer2",
            "requestMappings": [
                {
                    "requestParam": "someparam",
                    "match": "somevalue",
                    "claims": {
                        "sub": "subBySomeParam",
                        "aud": [
                            "audBySomeParam"
                        ]
                    }
                }
            ]
        }
    ]
}

A token provider can support different signing algorithms. Configure your token provider and add this to your config with preferred JWS algorithm:

{
  "tokenProvider" : {
    "keyProvider" : {
      "algorithm" : "ES256"
    }
  }
}

A token provider can also support a static "systemTime", i.e. the time for when the token is issued (iat claim) if you have tests that require a specific time. The following configuration will set the system time to 2020-01-21T00:00:00Z:

{
  "tokenProvider" : {
    "systemTime" : "2020-01-21T00:00:00Z"
  }
}
Property Description
interactiveLogin true or false, enables login screen when redirecting to server /authorize endpoint
loginPagePath An optional string refering to a html file that is served as login page. This page needs to contain a form that posts a username and optionally a claims field. See src/test/resource/login.example.html as an example.
staticAssetsPath The path to a directory containing static resources/assets. Lets you serve your own static resources from the server. Resources are served under the /static URL path. E.g. http://localhost:8080/static/myimage.svg or by reference /static/myimage.svg from the login page.
rotateRefreshToken true or false, setting to true will generate a new unique refresh token when using the refresh_token grant.
httpServer A string identifying the httpserver to use. Must match one of the following enum values: MockWebServerWrapper or NettyWrapper
tokenCallbacks A list of RequestMappingTokenCallback that lets you specify which token claims to return when a token request matches the specified condition.

From the first JSON example above:

A token request to http://localhost:8080/issuer1/token with parameter scope equal to scope1 will match the first tokenCallback:

{
    "issuerId": "issuer1",
    "tokenExpiry": 120,
    "requestMappings": [
        {
            "requestParam": "scope",
            "match": "scope1",
            "claims": {
                "sub": "subByScope",
                "aud": [
                    "audByScope"
                ]
            }
        }
    ]
}

and return a token response containing a token with the following claims:

{
  "sub": "subByScope",
  "aud": "audByScope",
  "nbf": 1616416942,
  "iss": "http://localhost:54905/issuer1",
  "exp": 1616417062,
  "iat": 1616416942,
  "jti": "28697333-6f25-4b1f-b2c2-409ce010933a"
}

Use variable clientId to set sub claim for Client Credentials Grant dynamically.

A token request with client credentials where clientId = myClientId and tokenCallback:

{
    "issuerId": "issuer1",
    "tokenExpiry": 120,
    "requestMappings": [
        {
            "requestParam": "scope",
            "match": "scope1",
            "claims": {
                "sub": "${clientId}",
                "aud": [
                    "audByScope"
                ]
            }
        }
    ]
}

will return a token response containing a token with the following claims:

{
  "sub": "myClientId",
  "aud": "audByScope",
  "nbf": 1616416942,
  "iss": "http://localhost:54905/issuer1",
  "exp": 1616417062,
  "iat": 1616416942,
  "jti": "28697333-6f25-4b1f-b2c2-409ce010933a"
}

Docker

Build to local docker daemon

./gradlew -Djib.from.platforms=linux/amd64 jibDockerBuild # or alternatively -Djib.from.platforms=linux/arm64 for ARM64

Run container

docker run -p 8080:8080 $IMAGE_NAME

Docker-Compose

In order to get container-to-container networking to work smoothly alongside browser interaction you must specify a host entry in your hosts file, 127.0.0.1 host.docker.internal and set hostname in the mock-oauth2-server service in your docker-compose.yaml file:

version: '3.7'
services:
  your_app:
    build: .
    ports:
      - 8080:8080
  mock-oauth2-server:
    image: ghcr.io/navikt/mock-oauth2-server:$MOCK_OAUTH2_SERVER_VERSION
    ports:
      - 8080:8080
    hostname: host.docker.internal

Debugger

The debugger is a OAuth2 client implementing the authorization_code flow with a UI for debugging (e.g. request parameters). Point your browser to http://localhost:8080/default/debugger to check it out.

Enabling HTTPS

In order to enable HTTPS you can either provide your own keystore or let the server generate one for you.

Unit tests

You need to supply the server with an SSL config, in order to do that you must specify your chosen server type in OAuth2Config and pass in the SSL config to your server.

Generate keystore:

val ssl = Ssl()
val server = MockOAuth2Server(
    OAuth2Config(httpServer = MockWebServerWrapper(ssl))
)

This will generate a SSL certificate for localhost and can be added to your client's truststore by getting the ssl config: ssl.sslKeystore.keyStore

Bring your own:

val ssl = Ssl(
    SslKeystore(
        keyPassword = "",
        keystoreFile = File("src/test/resources/localhost.p12"),
        keystorePassword = "",
        keystoreType = SslKeystore.KeyStoreType.PKCS12
    )
)
val server = MockOAuth2Server(
    OAuth2Config(httpServer = MockWebServerWrapper(ssl))
)

Docker / Standalone mode - JSON_CONFIG

In order to enable HTTPS for the server in Docker or standalone mode you can either make the server generate the keystore or bring your own.

Generate keystore:

{
  "httpServer" : {
    "type" : "NettyWrapper",
    "ssl" : {}
  }
}

Bring your own:

{
    "httpServer" : {
        "type" : "NettyWrapper",
        "ssl" : {
            "keyPassword" : "",
            "keystoreFile" : "src/test/resources/localhost.p12",
            "keystoreType" : "PKCS12",
            "keystorePassword" : "" 
        }
    }
}

Upgrading the gradle wrapper

Find the newest version of gradle here: https://gradle.org/releases/ Then run this command: ./gradlew wrapper --gradle-version $gradleVersion

Remeber to also update the gradle version in the build.gradle.kts file gradleVersion = "$gradleVersion"

👥 Contact

This project is currently maintained by the organisation @navikt.

If you need to raise an issue or question about this library, please create an issue here and tag it with the appropriate label.

For contact requests within the @navikt org, you can use the Slack channel #pig_sikkerhet

If you need to contact anyone directly, please see contributors.

✏️ Contributing

To get started, please fork the repo and checkout a new branch. You can then build the library with the Gradle wrapper

./gradlew build

See more info in CONTRIBUTING.md

⚖️ License

This library is licensed under the MIT License

mock-oauth2-server's People

Contributors

antoineauger avatar bethesque avatar candrews avatar dependabot[bot] avatar fgstewart avatar fstewart-aexp avatar jksolbakken avatar jonalu avatar jp7677 avatar kvokacka avatar mikaojk avatar nathaniel-tomsett avatar oddsund avatar oyvind-wedoe avatar ptrsd avatar runelind avatar sascha-eisenmann avatar tanettrimas avatar tommytroen avatar tronghn avatar valdemon avatar ybelmekk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mock-oauth2-server's Issues

Issue when used with Spring Boot log4j2 starter

When using this project together with org.springframework.boot:spring-boot-starter-log4j2 on Spring Boot 2.4.3, an exception is thrown during startup:

java.lang.ExceptionInInitializerError
	[...]
Caused by: org.apache.logging.log4j.LoggingException: log4j-slf4j-impl cannot be present with log4j-to-slf4j
	at org.apache.logging.slf4j.Log4jLoggerFactory.validateContext(Log4jLoggerFactory.java:49)
	at org.apache.logging.slf4j.Log4jLoggerFactory.newLogger(Log4jLoggerFactory.java:39)

I created a test project here

Probably it would be good to not include logback, but perhaps only rely on slf4j?

Implicit flow not supported

I am trying to use the standalone version provided via docker-compose (exposed on port 8082) and have pointed my locally running app against the mock-server. The app uses an implicit flow triggered by SwaggerUI and produces the following request:

Request:
http://localhost:8082/cola/authorize?response_type=token&client_id=myapp&redirect_uri=http%3A%2F%2Flocalhost%3A8081%2Fswagger-ui%2Foauth2-redirect.html&scope=openid&state=VGh1IEp1bCAxNSAyMDIxIDEyOjExOjM1IEdNVCswMjAwIChDZW50cmFsIEV1cm9wZWFuIFN1bW1lciBUaW1lKQ%3D%3D&realm=myrealm&nonce=dummy

However this fails with the following response:
{ "error_description" : "unsupported response type: unsupported response_type parameter: the openid connect response type cannot have token as the only value", "error" : "unsupported_response_type" }

From the mock-server logs I additionally have this:
2021-07-15 10:14:23 [nioEventLoopGroup-3-5] ERROR no.nav.security.mock.oauth2.http.OAuth2HttpRequestHandler - received exception when handling request. com.nimbusds.oauth2.sdk.ParseException: Unsupported response_type parameter: The OpenID Connect response type cannot have token as the only value at com.nimbusds.openid.connect.sdk.AuthenticationRequest.parse(AuthenticationRequest.java:1712) at com.nimbusds.openid.connect.sdk.AuthenticationRequest.parse(AuthenticationRequest.java:1975) at no.nav.security.mock.oauth2.http.OAuth2HttpRequest.asAuthenticationRequest(OAuth2HttpRequest.kt:76) at no.nav.security.mock.oauth2.http.OAuth2HttpRequestHandler.handleAuthenticationRequest(OAuth2HttpRequestHandler.kt:104) at no.nav.security.mock.oauth2.http.OAuth2HttpRequestHandler.handleRequest(OAuth2HttpRequestHandler.kt:70) at no.nav.security.mock.oauth2.MockOAuth2Server$router$1.invoke(MockOAuth2Server.kt:52) at no.nav.security.mock.oauth2.MockOAuth2Server$router$1.invoke(MockOAuth2Server.kt:41) at no.nav.security.mock.oauth2.http.OAuth2HttpRouterKt$routeFromPathAndMethod$1.invoke(OAuth2HttpRouter.kt:58) at no.nav.security.mock.oauth2.http.OAuth2HttpRouterKt$routeFromPathAndMethod$1.invoke(OAuth2HttpRouter.kt:50) at no.nav.security.mock.oauth2.http.OAuth2HttpRouter.match(OAuth2HttpRouter.kt:27) at no.nav.security.mock.oauth2.http.OAuth2HttpRouter.invoke(OAuth2HttpRouter.kt:20) at no.nav.security.mock.oauth2.http.OAuth2HttpRouter.invoke(OAuth2HttpRouter.kt:14) at no.nav.security.mock.oauth2.http.NettyWrapper$RouterChannelHandler.channelRead0(OAuth2HttpServer.kt:170) at no.nav.security.mock.oauth2.http.NettyWrapper$RouterChannelHandler.channelRead0(OAuth2HttpServer.kt:164) at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99) [...]

I don't really understand the error message. A single response type should be fine from my understanding. It also works that way against our real authentication server (Keycloak). Any idea what I am doing wrong?

Edit: Just realized that implicit flow isn't listed as being supported in the Readme. If thats the case, is that something considered to be added anytime soon?

Docker Imange on hub.docker.com

Hi,

Thanks a lot for mock server, that is exactly what i need :-)

I am working in a corporate environment so the docker images get downloaded to our Artifactory. As you need a login for docker.pkg.github.com, this makes it a but un-handy.... for our Infrastructure-Team. It would be cool the Image would be on hub.docker.com, this is the most common place to store docker Images.

This i just a wish :-)

Cheers Tom

Support other claims in login form

Hi there,

Thanks a lot for creating this project. I'm currently playing with the stand-alone docker image as a lightweight alternative to a full OAuth2 server.

The current request mapping lets you specify different claims to be returned based on request parameters, but I could not find a way to define different claims based on the information in the "login" form, subject or acr. (Please let me know if I missed something).
I've looked a bit through the source code and from what I saw this isn't easy to implement since the focus of this project is unit testing and the stand-alone server was build around it.

That said, since the login form already offers an easy way to set sub and acr, having a way to specify more claims there, would work perfectly for me. I've played a bit with the code and came up with this on the UI side:

Screenshot

and this change here https://github.com/navikt/mock-oauth2-server/blob/master/src/main/kotlin/no/nav/security/mock/oauth2/grant/AuthorizationCodeGrantHandler.kt#L93 for the LoginOAuth2TokenCallback

override fun addClaims(tokenRequest: TokenRequest): Map<String, Any> =
    OAuth2TokenCallback.addClaims(tokenRequest).toMutableMap().apply {
      login.claims?.let { // Renamed login.acr to login.claims
          jsonMapper.readTree(it)
              .fields()
              .forEach { field -> put(field.key, jsonMapper.readValue(field.value.toString())) }
          }
    }

What do you think about this? Would you consider this direction an improvement?
If yes, I could go forward with this and open a PR for detailed feedback.

Make "maxLifetimeSeconds" of token configurable

Right now "maxLifetimeSeconds" is hardcoded for 120 seconds in OAuth2HttpRequest in call of requirePrivateKeyJwt.

Example:

mockOAuth2Server.getConfig().setMaxTokenLifetime(MOCK_AUTH_SERVER_MAX_TOKEN_LIFETIME_SECONDS);

Motivation: Testing refreshing token expiry

Add alg parameter to initial keys

Some clients (google-oauth-client) seem to expect the alg parameter when parsing jwks key endpoints (even though they are optional). Can we add those to the initial keys resources to expand compatibility?

Java 17 support

Running the server with java 17 produces an error

java.lang.UnsupportedOperationException: Reflective setAccessible(true) disabled at io.netty.util.internal.ReflectionUtil.trySetAccessible(ReflectionUtil.java:31) at io.netty.util.internal.PlatformDependent0$4.run(PlatformDependent0.java:253) at java.base/java.security.AccessController.doPrivileged(AccessController.java:682) at io.netty.util.internal.PlatformDependent0.<clinit>(PlatformDependent0.java:247) at io.netty.util.internal.PlatformDependent.isAndroid(PlatformDependent.java:294) at io.netty.util.internal.PlatformDependent.<clinit>(PlatformDependent.java:88) at io.netty.util.AsciiString.<init>(AsciiString.java:223) at io.netty.util.AsciiString.<init>(AsciiString.java:210) at io.netty.util.AsciiString.cached(AsciiString.java:1401) at io.netty.util.AsciiString.<clinit>(AsciiString.java:48) at io.netty.handler.codec.http.HttpHeaderNames.<clinit>(HttpHeaderNames.java:31) at no.nav.security.mock.oauth2.http.OAuth2HttpResponseKt.json(OAuth2HttpResponse.kt:62) at no.nav.security.mock.oauth2.http.OAuth2HttpRequestHandler.handleRequest(OAuth2HttpRequestHandler.kt:72) at no.nav.security.mock.oauth2.MockOAuth2Server$router$1.invoke(MockOAuth2Server.kt:53) at no.nav.security.mock.oauth2.MockOAuth2Server$router$1.invoke(MockOAuth2Server.kt:52) at no.nav.security.mock.oauth2.http.OAuth2HttpRouterKt$routeFromPathAndMethod$1.invoke(OAuth2HttpRouter.kt:61) at no.nav.security.mock.oauth2.http.OAuth2HttpRouterKt$routeFromPathAndMethod$1.invoke(OAuth2HttpRouter.kt:53) at no.nav.security.mock.oauth2.http.OAuth2HttpRouter.match(OAuth2HttpRouter.kt:27) at no.nav.security.mock.oauth2.http.OAuth2HttpRouter.invoke(OAuth2HttpRouter.kt:20) at no.nav.security.mock.oauth2.http.OAuth2HttpRouter.invoke(OAuth2HttpRouter.kt:14) at no.nav.security.mock.oauth2.http.MockWebServerWrapper$MockWebServerDispatcher.dispatch(OAuth2HttpServer.kt:90) at okhttp3.mockwebserver.MockWebServer$SocketHandler.processOneRequest(MockWebServer.kt:600) at okhttp3.mockwebserver.MockWebServer$SocketHandler.handle(MockWebServer.kt:551) at okhttp3.mockwebserver.MockWebServer$serveConnection$$inlined$execute$1.runOnce(TaskQueue.kt:220) at okhttp3.internal.concurrent.TaskRunner.runTask(TaskRunner.kt:116) at okhttp3.internal.concurrent.TaskRunner.access$runTask(TaskRunner.kt:42) at okhttp3.internal.concurrent.TaskRunner$runnable$1.run(TaskRunner.kt:65) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) at java.base/java.lang.Thread.run(Thread.java:853)

and then

java.lang.IllegalAccessException: class io.netty.util.internal.PlatformDependent0$6 cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @9e7c92f3 at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:385) at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:687) at java.base/java.lang.reflect.Method.invoke(Method.java:559) at io.netty.util.internal.PlatformDependent0$6.run(PlatformDependent0.java:375) at java.base/java.security.AccessController.doPrivileged(AccessController.java:682) at io.netty.util.internal.PlatformDependent0.<clinit>(PlatformDependent0.java:366) at io.netty.util.internal.PlatformDependent.isAndroid(PlatformDependent.java:294) at io.netty.util.internal.PlatformDependent.<clinit>(PlatformDependent.java:88) at io.netty.util.AsciiString.<init>(AsciiString.java:223) at io.netty.util.AsciiString.<init>(AsciiString.java:210) at io.netty.util.AsciiString.cached(AsciiString.java:1401) at io.netty.util.AsciiString.<clinit>(AsciiString.java:48) at io.netty.handler.codec.http.HttpHeaderNames.<clinit>(HttpHeaderNames.java:31) at no.nav.security.mock.oauth2.http.OAuth2HttpResponseKt.json(OAuth2HttpResponse.kt:62) at no.nav.security.mock.oauth2.http.OAuth2HttpRequestHandler.handleRequest(OAuth2HttpRequestHandler.kt:72) at no.nav.security.mock.oauth2.MockOAuth2Server$router$1.invoke(MockOAuth2Server.kt:53) at no.nav.security.mock.oauth2.MockOAuth2Server$router$1.invoke(MockOAuth2Server.kt:52) at no.nav.security.mock.oauth2.http.OAuth2HttpRouterKt$routeFromPathAndMethod$1.invoke(OAuth2HttpRouter.kt:61) at no.nav.security.mock.oauth2.http.OAuth2HttpRouterKt$routeFromPathAndMethod$1.invoke(OAuth2HttpRouter.kt:53) at no.nav.security.mock.oauth2.http.OAuth2HttpRouter.match(OAuth2HttpRouter.kt:27) at no.nav.security.mock.oauth2.http.OAuth2HttpRouter.invoke(OAuth2HttpRouter.kt:20) at no.nav.security.mock.oauth2.http.OAuth2HttpRouter.invoke(OAuth2HttpRouter.kt:14) at no.nav.security.mock.oauth2.http.MockWebServerWrapper$MockWebServerDispatcher.dispatch(OAuth2HttpServer.kt:90) at okhttp3.mockwebserver.MockWebServer$SocketHandler.processOneRequest(MockWebServer.kt:600) at okhttp3.mockwebserver.MockWebServer$SocketHandler.handle(MockWebServer.kt:551) at okhttp3.mockwebserver.MockWebServer$serveConnection$$inlined$execute$1.runOnce(TaskQueue.kt:220) at okhttp3.internal.concurrent.TaskRunner.runTask(TaskRunner.kt:116) at okhttp3.internal.concurrent.TaskRunner.access$runTask(TaskRunner.kt:42) at okhttp3.internal.concurrent.TaskRunner$runnable$1.run(TaskRunner.kt:65)

question: How to set the logging severity for the stand-alone server

Hi there,

When running mock-oauth2-server as a docker container, its logging is very verbose, the debug logging shows a lot of lines like

mock-oauth2-server_1  | 2022-06-08 13:31:01 [nioEventLoopGroup-3-23] DEBUG no.nav.security.mock.oauth2.http.CorsInterceptor - intercept response if request origin header is set: null

Is there a way to set the logging severity from the outside using an environment variable so that it can be applied to the stand alone docker image? I tried several of the "standard" ways like setting _JAVA_OPTIONS=-Dlogging.level.root=WARN or JAVA_OPTS -Dlogging.level.root=WARN, but none of those showed effect.

scope issue in standalone server with spring boot application

Hi everyone,

I'm trying to set up mock oauth2 as standalone server to test a spring boot application as resource server, however, the application doesn't recognize any of the scopes I set on mock oauth2. This is how I currently configure the user

image

As can be seen this user has the scopes openid, profile, email, and read:teams. The application has the following configuration

spring:
  security:
      oauth2:
        resourceserver:
          jwt:
            #issuer-uri: http://localhost:8090/default/jwks
            jwk-set-uri: http://localhost:8090/default/jwks
http.cors()
                .and()
                .csrf()
                .and()
                .authorizeRequests()
                .antMatchers("/team").hasAnyAuthority("SCOPE_read:teams") // .hasAnyAuthority("read:teams")
                .anyRequest()
                .authenticated()
                .and()
                .oauth2ResourceServer()
                .jwt();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

The response I'm getting from the application when sending a request to GET /team is

HTTP/1.1 403 
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
WWW-Authenticate: Bearer error="insufficient_scope", error_description="The request requires higher privileges than provided by the access token.", error_uri="https://tools.ietf.org/html/rfc6750#section-3.1"
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Length: 0
Date: Thu, 24 Mar 2022 16:25:09 GMT
Keep-Alive: timeout=60
Connection: keep-alive

The error from the app is

2022-03-24 16:29:57.964 DEBUG [app,,,] 26556 --- [nio-8080-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/team'; against '/team'
2022-03-24 16:29:57.964 DEBUG [app,,,] 26556 --- [nio-8080-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor    : Secure object: FilterInvocation: URL: /team; Attributes: [hasAnyAuthority('read:teams')]
2022-03-24 16:29:57.964 DEBUG [app,,,] 26556 --- [nio-8080-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor    : Previously Authenticated: org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken@ffff6aba: Principal: org.springframework.security.oauth2.jwt.Jwt@f310fa13; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Not granted any authorities
2022-03-24 16:29:57.968 DEBUG [app,,,] 26556 --- [nio-8080-exec-1] o.s.s.access.vote.AffirmativeBased       : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@469e4961, returned: -1
2022-03-24 16:29:57.968 DEBUG [app,,,] 26556 --- [nio-8080-exec-1] o.s.s.w.a.ExceptionTranslationFilter     : Access is denied (user is not anonymous); delegating to AccessDeniedHandler

org.springframework.security.access.AccessDeniedException: Access is denied
	at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84)

In the other hand, if I don't set any require scope at the spring security configuration, the token works as expected. I know this is probably a mistake I'm making with the mock server and also don't know much about this kind of authorization so thanks in advance for any help or comment.

doc: Add KDoc and dokka

No JavaDocs on any of the methods in MockOAuth2Server, which unfortunately makes this library nearly unusable, unless one study its source code @arcuri82

support configurable docker image

i.e. be able to do some configuration (especially tokencallbacks) when running in standalone mode from docker

  • one possible solution could be the presence of a file in e.g. HOCON format to support nested config etc.

Keyclock-js client library expects refresh tokens being a JWT

This is a follow up from #199 (comment)

The keycloak-js client library (https://github.com/keycloak/keycloak/tree/main/adapters/oidc/js) needs some changes in the refresh token. it expects a kind-of proper token where it wants to decode the second part of a string separated by a . and wants to check the nonce in it. My evil hack, which does work, for generating the refresh token looks like the following, whereas the nonce is passed down from AuthorizationCodeHandler.tokenResponse.

val jti = UUID.randomUUID().toString()
val refreshToken = if (nonce != null) "." + Base64.getEncoder().encodeToString("{\"nonce\":\"$nonce\",\"jti\":\"$jti\"}".toByteArray()) else jti

Response from @tommytroen :

That sounds really strange - the refresh token is supposed to be an opaque string containing whatever. Do you mean that keycloak requires the refresh token to be a JWT? Maybe I havent understood the use case right - could you elaborate a bit more on what you send, what you receive and what errors keycloak-js is reporting?

Response from @jp7677 :

The refresh token being an opaque string is also my understanding, but seems that unfortunately keycloak enhanced the specs for themself. See here https://github.com/keycloak/keycloak/blob/main/adapters/oidc/js/src/keycloak.js#L952 where it wants to decode the refresh token like an id-token and here https://github.com/keycloak/keycloak/blob/main/adapters/oidc/js/src/keycloak.js#L765 where it validates the nonce element in the refresh token.

Any ideas how to handle this case? Being compatible with keycloak-js would obviously be nice, though I would understand the hesitation considering the "re-interpretation" of the specifications by Keycloak server and client.

[Question] What is the easiest way to support https?

Cannot see this question answered in docs or other issues. And I don't have access to project slack space, so therefore a new issue.

I need to specifically test an application's ability to talk HTTPS with the OAuth server, and trust custom SSL certificates. So I started with this project and got it setup quite easily with http. But now I am struggling to make the server listen over https with a custom SSL cert. I hoped the project would either just generate some certs and let me fetch the PEM or have some constructor that takes a cert and private-key and then use that. But turns out I need to write a new wrapper (MockHttpServerHttpsWrapper) that sets up SSLContext and everything.

I'm almost there, after fighting some dependency version clashes, I'm currently stuck in translating some of the Kotlin extension code to Java (my project uses Java).

So my question is - have you tried this? What is the recommended route? Is an official Https wrapper on the roadmap? Would it be easier with Netty?

Testcontainer failes to verify mock-oauth2-server startup

The following code snippet starts a container, but can't check that container listens on internal port.

private static final DockerImageName MOCK_OAUTH_NAME = DockerImageName
        .parse("ghcr.io/navikt/mock-oauth2-server:0.3.4");
@Container
public static GenericContainer mockAuthServer = new GenericContainer(MOCK_OAUTH_NAME)
        .withExposedPorts(8080)
        .waitingFor(Wait.forListeningPort());

Probably the issue is related to testcontainers/testcontainers-java#3317

Check demo app which reproduces the problem: demo.zip

Workaround: use log message Wait:

private static final DockerImageName MOCK_OAUTH_NAME = DockerImageName
        .parse("ghcr.io/navikt/mock-oauth2-server:0.3.4");
@Container
public static GenericContainer mockAuthServer = new GenericContainer(MOCK_OAUTH_NAME)
        .withExposedPorts(8080)
        .waitingFor(Wait.forLogMessage(".*started server.*", 1));

Inconsistent IssuerUrl when setting the Host header

I'm seeing some strange behaviour affecting the issues url. When setting the host name to a name that does not contain a port, the issuer url seems to be set to the IP. When any port is included in the header, it uses the port on the supplied host (as you would expect).

Using:

docker network create localnet
docker run --rm --env SERVER_PORT=80 -p80:80 --expose 80 ghcr.io/navikt/mock-oauth2-server:0.5.1
docker run --network localnet --rm --env SERVER_PORT=80 -p80:80 --expose 80 --hostname oauth2 ghcr.io/navikt/mock-oauth2-server:0.5.1

Using oauth2:80, the issuer url is resolved to oauth2:

> docker run --rm --network localnet curlimages/curl:7.84.0 curl -H 'Host: oauth2:80' oauth2/default/.well-known/openid-configuration -s
{
  "issuer" : "http://oauth2/default",
  "authorization_endpoint" : "http://oauth2/default/authorize",
  "end_session_endpoint" : "http://oauth2/default/endsession",
  "token_endpoint" : "http://oauth2/default/token",
  "userinfo_endpoint" : "http://oauth2/default/userinfo",
  "jwks_uri" : "http://oauth2/default/jwks",
  "introspection_endpoint" : "http://oauth2/default/introspect",
  "response_types_supported" : [ "query", "fragment", "form_post" ],
  "subject_types_supported" : [ "public" ],
  "id_token_signing_alg_values_supported" : [ "ES256", "ES384", "RS256", "RS384", "RS512", "PS256", "PS384", "PS512" ]
}

Using the host of the container oauth2, the issuer url is the host name of the client (in this case, the curl container):

> docker run --rm --network localnet curlimages/curl:7.84.0 curl -H 'Host: oauth2' oauth2/default/.well-known/openid-configuration -s
{
  "issuer" : "http://25a6c016521e.localnet/default",
  "authorization_endpoint" : "http://25a6c016521e.localnet/default/authorize",
  "end_session_endpoint" : "http://25a6c016521e.localnet/default/endsession",
  "token_endpoint" : "http://25a6c016521e.localnet/default/token",
  "userinfo_endpoint" : "http://25a6c016521e.localnet/default/userinfo",
  "jwks_uri" : "http://25a6c016521e.localnet/default/jwks",
  "introspection_endpoint" : "http://25a6c016521e.localnet/default/introspect",
  "response_types_supported" : [ "query", "fragment", "form_post" ],
  "subject_types_supported" : [ "public" ],
  "id_token_signing_alg_values_supported" : [ "ES256", "ES384", "RS256", "RS384", "RS512", "PS256", "PS384", "PS512" ]
}

I would expect that specifying the host with no port would produce an Issuer Url of that host!

Turn off Interactive login

I am trying to use the docker image in my test environment.

The application makes a request as
http://oauthServer:8080/default/authorize?prompt=login&state=00000001fm5ccpbb470sgc1fe07mh66t&response_type=code&client_id=test&redirect_uri=https%3A%2F%2Fapp.test.com%3A8443%2Ftest%2Fwebservices%2Frest%2Foauth%2Freturn&scope=openid%20scope1%20scope2

my json config I have interactive set as false.

But the return of invoking the above url I still get back an HTML page.

How can I just invoke the above url and get redirected back to return endpoint where I then make requests for tokens?
config.txt

Support different key for each tenant

I would find it convenient if each tenant uses a different key for signing the JWTs, so that I can use the mock-oauth2-server to use it for unit-testing authentication against different tenants.
Would this be something that makes sense also for others?

Any idea why mock-oauth2-server doesn't build locally for me?

I cloned the repo, then ran ./gradlew build, and got:

> Task :compileKotlin FAILED
e: .../mock-oauth2-server/src/main/kotlin/no/nav/security/mock/oauth2/debugger/DebuggerRequestHandler.kt: (173, 66): Type mismatch: inferred type is Charset! but String! was expected

When I look at the code, that error seems to be right. The line is:

private fun String.urlEncode(): String = URLEncoder.encode(this, StandardCharsets.UTF_8)

java.nio.charset.StandardCharsets.UTF_8 is a Charset, and java.net.URLEncoder.encode() expects a String.

JWKS returnere også feltet "use" som er satt til "sig"

Vi i FP trenger å få returnert ett JWKS som – i tillegg til de feltene som allerede eksistere – også returner feltet "use = sig" (https://tools.ietf.org/html/rfc7517#section-4.2 ).

Slik er responsen nå:

{
"keys": [

    {
        "kty": "RSA",
        "e": "AQAB",
        "kid": "mock-oauth2-server-key",
        "n": "hUlfUty3aw...."
    }
]

}

Det vi ønsker er en respons på denne formen:

{
"keys": [

    {
        "kty": "RSA",
        "use": "sig",
        "e": "AQAB",
        "kid": "mock-oauth2-server-key",
        "n": "hUlfUty3aw...."
    }
]

}

feat: unchecked exceptions for start and stop functions

Hi,
thanks for writing this library. I tried to use it because I needed this functionality, but had to give up and look at alternatives. But I can provide you some feedback:

  1. in MockOAuth2Server, both start and shutdown throw checked exceptions. This is not a problem in Kotlin, but it is a major PITA in Java. having to wrap in try/catch all their use is not so great when writing tests

  2. no JavaDocs on any of the methods in MockOAuth2Server, which unfortunately makes this library nearly unusable, unless one study its source code

Support PORT environment variable

Thanks for your work on this server! It has been very useful to our project.

I'd love to be able to run your mock server on Heroku. To do this, it needs to be able to honour the PORT environment variable. I've tried building my own docker image that extends your image and maps the PORT to SERVER_PORT, but it doesn't seem there's a shell environment installed on the image, and I can't find your source Dockerfile. Is this something you'd consider supporting? I would be happy to do a PR, but as mentioned, I can't find the source.

Access OAuth request body

Hello,

I am trying to access the request body but it is always empty.
I believe the OAuth2 server consumes the requesty body when doing its internal handling so when I retrive it usingtakeRequest, I get an empty buffer.

Is there another way to access it?

Thanks!

support for CORS preflight requests

2020-10-29 10:46:09 [MockWebServer /172.17.0.1:47340] ERROR no.nav.security.mock.oauth2.http.OAuth2HttpRequestHandler - received exception when handling request.
no.nav.security.mock.oauth2.OAuth2Exception: Unsupported request method OPTIONS
	at no.nav.security.mock.oauth2.OAuth2ExceptionKt.invalidRequest(OAuth2Exception.kt:17)
	at no.nav.security.mock.oauth2.http.OAuth2HttpRequestHandler.handleAuthenticationRequest(OAuth2HttpRequestHandler.kt:107)
	at no.nav.security.mock.oauth2.http.OAuth2HttpRequestHandler.handleRequest(OAuth2HttpRequestHandler.kt:68)
	at no.nav.security.mock.oauth2.MockOAuth2Dispatcher.dispatch(MockOAuth2Server.kt:112)

Docker and https

Hi,
I'm trying to start a docker server with https but I cannot get it to work. I'm getting parser errors from the sample json rpovided in the readme.

This is the command I'm using:

docker run -p 8080:8080 -h localhost --env JSON_CONFIG='{"httpServer": {"type":"NettyWrapper","ssl":{}}}'  ghcr.io/navikt/mock-oauth2-server/mock-oauth2-server:0.3.2

This is the error I'm getting:

Exception in thread "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `no.nav.security.mock.oauth2.OAuth2Config$OAuth2HttpServerDeserializer$ServerType` from Object value (token `JsonToken.START_OBJECT`)
 at [Source: (String)"{"httpServer": {"type":"NettyWrapper","ssl":{}}}"; line: 1, column: 16] (through reference chain: no.nav.security.mock.oauth2.OAuth2Config["httpServer"])
        at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
        at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1601)
        at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1375)
        at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1280)
        at com.fasterxml.jackson.databind.DeserializationContext.extractScalarFromObject(DeserializationContext.java:872)
        at com.fasterxml.jackson.databind.deser.std.EnumDeserializer.deserialize(EnumDeserializer.java:199)
        at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
        at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:4569)
        at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2823)
        at com.fasterxml.jackson.core.JsonParser.readValueAs(JsonParser.java:2256)
        at no.nav.security.mock.oauth2.OAuth2Config$OAuth2HttpServerDeserializer.deserialize(OAuth2Config.kt:33)
        at no.nav.security.mock.oauth2.OAuth2Config$OAuth2HttpServerDeserializer.deserialize(OAuth2Config.kt:26)
        at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:542)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:565)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:449)
        at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1405)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:362)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:195)
        at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
        at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4593)
        at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3548)
        at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3531)
        at no.nav.security.mock.oauth2.OAuth2Config$Companion.fromJson(OAuth2Config.kt:48)
        at no.nav.security.mock.oauth2.StandaloneConfig.oauth2Config(StandaloneMockOAuth2Server.kt:27)
        at no.nav.security.mock.oauth2.StandaloneMockOAuth2ServerKt.main(StandaloneMockOAuth2Server.kt:48)
        at no.nav.security.mock.oauth2.StandaloneMockOAuth2ServerKt.main(StandaloneMockOAuth2Server.kt)

support OIDC single/global/central logout endpoint

Ref:

  • OpenID Connect Session Management v1.0 Spec
  • OpenID Connect Front-Channel Logout v1.0 Spec
  • OpenID Connect Back-Channel Logout v1.0 Spec

Should be simple as this is afterall a mock, however it should validate that the correct params are received at the endsession endpoint and do proper redirects as per spec

multi-segment issuer

Hi,
I am trying to use this library to test a code that uses Keycloak client.
The problem is that Keycloak issuers are made of 2 URL segments (e.g. /realms/master), so the keycloak client will query for tokens at /realms/master/protocol/openid-connect/token.
Is it possible to make this work? Thanks!

Well-known response doesn't contain user info endpoint url

Hi,

I've just started using your mock server - it looks promising! I realized that a well-known endpoint doesn't contain a user info endpoint URL.

Actual response:

{
  "issuer" : "http://localhost:8080/default",
  "authorization_endpoint" : "http://localhost:8080/default/authorize",
  "end_session_endpoint" : "http://localhost:8080/default/endsession",
  "token_endpoint" : "http://localhost:8080/default/token",
  "jwks_uri" : "http://localhost:8080/default/jwks",
  "response_types_supported" : [ "query", "fragment", "form_post" ],
  "subject_types_supported" : [ "public" ],
  "id_token_signing_alg_values_supported" : [ "ES256", "ES384", "RS256", "RS384", "RS512", "PS256", "PS384", "PS512" ]
}

Expected response:

{
  "issuer" : "http://localhost:8080/default",
  "authorization_endpoint" : "http://localhost:8080/default/authorize",
  "end_session_endpoint" : "http://localhost:8080/default/endsession",
  "token_endpoint" : "http://localhost:8080/default/token",
  "userinfo_endpoint": "http://localhost:8080/default/userinfo",
  "jwks_uri" : "http://localhost:8080/default/jwks",
  "response_types_supported" : [ "query", "fragment", "form_post" ],
  "subject_types_supported" : [ "public" ],
  "id_token_signing_alg_values_supported" : [ "ES256", "ES384", "RS256", "RS384", "RS512", "PS256", "PS384", "PS512" ]
}

Feature Request: REST endpoint on Standalone server to define JSON_CONFIG

Hi

I am just started with Oauth2 and am sorry if my text contain some errors :-( and I am not sure if i under stud all concepts right.

I have a Project that has a System Test. In the System Test all apps are started in docker containers. The Test runs on public APIs of that containers and for some components i programmed some Mocks. I am able to generate Unique Test Data for every Test case. eg every Test has its own User.

Now i have to introduce the Security :-(.... I found your Project, and it looks like a perfect match :-) But one part is missing, when i create a new User, i also have to tell the mock-oauth2-server that this user is allowed to access the app with the following claims.

As fare as i understand it, the "sub" (equal UserId) has to be dynamic in my case. Therefore it would be cool if there were a endpoint where i can update/replace the JSON_CONFIG.

Best regards
G

status check to know when it has successfully started

I am looking to run this via docker-compose, and would like to add a status check so we can execute another script when this have successfully been loaded. So mock-oauth2-server have a status script, or api we can curl?

(Alternative approaches also appreciated).

On Ubuntu we had problems setting up host.docker.internal in hosts file, and made a script to automate it, and would like to do that via our own command so that if the user is on Ubuntu it will automatically update the hosts file.

allow issuer to be overriden by tokencallback

  • allow iss value from OAuth2TokenCallback.addClaims() to override server issuer url if set
  • consider making this "feature" a toggle as this may lead to confusing behaviour if not intentional, e.g. the server issuer url beeing different from the token issuer value

Running the standalone Mockserver as a Java exec in Maven is causing a runtime failure

Hi,

I am using the version 0.3.3 of the mock-oauth2-server plugin

I am trying to run the mock-oauth2-server as a standalone process by wrapping it up with exec-maven-plugin

This is what the section of the pom looks like

           <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>3.0.0</version>
                <executions>
                    <execution>
                        <phase>pre-integration-test</phase>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                        <configuration>
                            <executable>java</executable>
                            <classpathScope>test</classpathScope>
                            <async>true</async>
                            <arguments>
                                <argument>-classpath</argument>
                                <classpath/>
                                <argument>no.nav.security.mock.oauth2.StandaloneMockOAuth2ServerKt</argument>
                            </arguments>
                            <environmentVariables>
                                <SERVER_PORT>8989</SERVER_PORT>
                                <JSON_CONFIG_PATH>${target.conf.dir}/${mock-oauth2-config-file}</JSON_CONFIG_PATH>
                            </environmentVariables>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

When the server starts up, and the http://localhost:8989/default/.well-known/openid-configuration is hit, it fails with the following error

10:57:34.976 [nioEventLoopGroup-3-1] WARN  io.netty.channel.DefaultChannelPipeline - An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
java.lang.NoSuchFieldError: Companion
	at no.nav.security.mock.oauth2.http.NettyWrapper$RouterChannelHandler.toOkHttpHeaders(OAuth2HttpServer.kt:195) ~[mock-oauth2-server-0.3.3.jar:?]
	at no.nav.security.mock.oauth2.http.NettyWrapper$RouterChannelHandler.asOAuth2HttpRequest(OAuth2HttpServer.kt:171) ~[mock-oauth2-server-0.3.3.jar:?]
	at no.nav.security.mock.oauth2.http.NettyWrapper$RouterChannelHandler.channelRead0(OAuth2HttpServer.kt:153) ~[mock-oauth2-server-0.3.3.jar:?]
	at no.nav.security.mock.oauth2.http.NettyWrapper$RouterChannelHandler.channelRead0(OAuth2HttpServer.kt:147) ~[mock-oauth2-server-0.3.3.jar:?]
	at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99) ~[netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103) [netty-codec-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:93) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.handler.codec.http.HttpServerKeepAliveHandler.channelRead(HttpServerKeepAliveHandler.java:64) [netty-codec-http-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324) [netty-codec-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296) [netty-codec-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) [netty-transport-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) [netty-common-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) [netty-common-4.1.63.Final.jar:4.1.63.Final]
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) [netty-common-4.1.63.Final.jar:4.1.63.Final]
	at java.lang.Thread.run(Thread.java:834) [?:?]

Any tips on a possible solution to this issue?

Should provide claim `sid` for use in oauth2/callback

The sid claim is required by wonderwall.

It can be provided manually in the custom claims input, but it could/should provide a unique sid upon login.

Example wonderwall error when sid not provided:

{"level":"error","service":"wonderwall","httpRequest":{"proto":"HTTP/1.1","requestID":"4a73e2af-3101-4880-b792-56d50f9425f1","requestMethod":"GET","requestPath":"/jobbtreff/oauth2/callback"},"error":"callback: validating id_token: validating id_token: required claim \"sid\" was not found","timestamp":"2021-11-22T15:51:46.769561+01:00","message":"error in route: callback: validating id_token: validating id_token: required claim \"sid\" was not found"}

Exception in thread "MockWebServer" java.lang.NoSuchMethodError: 'okio.Buffer okio.Buffer.copy()'

Hello, thank you for the project.

I want to use it in my project, but unfortunately there is dependency conflict in runtime if spring-boot-starter-oauth2-client is used.

Whole stacktrace:

Exception in thread "MockWebServer" java.lang.NoSuchMethodError: 'okio.Buffer okio.Buffer.copy()'
	at no.nav.security.mock.oauth2.extensions.RecordedRequestExtensionsKt.asOAuth2HttpRequest(RecordedRequestExtensions.kt:7)
	at no.nav.security.mock.oauth2.http.MockWebServerWrapper$MockWebServerDispatcher.dispatch(OAuth2HttpServer.kt:90)
	at okhttp3.mockwebserver.MockWebServer$4.processOneRequest(MockWebServer.java:573)
	at okhttp3.mockwebserver.MockWebServer$4.processConnection(MockWebServer.java:531)
	at okhttp3.mockwebserver.MockWebServer$4.execute(MockWebServer.java:456)
	at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:829)

Here is the sample project, which reproduces the error: demo.zip

If spring-boot-starter-oauth2-client dependency is commented, MockOAuthServer starts without issues.

Probably as a workaround one should specify explicit okio dependency, I will check this and update this issue.

UPD
The following 2 explicit dependencies fix the issue:

<dependency>
	<groupId>com.squareup.okhttp3</groupId>
	<artifactId>mockwebserver</artifactId>
	<version>4.9.1</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>com.squareup.okhttp3</groupId>
	<artifactId>okhttp</artifactId>
	<version>4.9.1</version>
</dependency>

[bug] requestMappings in JSON_CONFIG not working as documented for docker

First off, thanks so much for this project! I'm super-stoked to get it working, and to be able to strip a lot of custom work-arounds from a local dev environment ❤️

The thing I'm hitting is that I'm unable to use the standalone mode as described, specifically with requestMappings. I've confirmed that:

  • the config json is being read,
  • top-level config can be changed (e.g., interactiveLogin),
  • malformed tokenCallbacks entries cause the server not to start

BUT using the exact config json as described in the README does not seem to match and generate any sort of custom token response when hitting the appropriate issuer.

I've created a full reproduction here: https://github.com/patcon/mock-oauth2-repro

Any advice is appreciated! 🙌

RemoteKeySourceException Read time out within Spring Boot test

We have a flaky test that sometimes fails in our source code and I can reproduce it in OAuth2ResourceServerAppTest when running ./gradlew cleanTest test --tests OAuth2ResourceServerAppTest multiple times.
The result is that the "api should return 200" test fails with the following stack trace

Failed to load ApplicationContext
java.lang.IllegalStateException: Failed to load ApplicationContext
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
	at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:118)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
	at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:43)
	at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:244)
	at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:138)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$6(ClassBasedTestDescriptor.java:350)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:355)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$7(ClassBasedTestDescriptor.java:350)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
	at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)
	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:312)
	at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:735)
	at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:734)
	at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:658)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:349)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$instantiateAndPostProcessTestInstance$4(ClassBasedTestDescriptor.java:270)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:269)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$2(ClassBasedTestDescriptor.java:259)
	at java.base/java.util.Optional.orElseGet(Optional.java:369)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$3(ClassBasedTestDescriptor.java:258)
	at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:101)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:100)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:65)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$1(NodeTestTask.java:111)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:111)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:79)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at com.sun.proxy.$Proxy2.stop(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:135)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:414)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
	at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration': Unsatisfied dependency expressed through method 'setSecurityWebFilterChains' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'securityWebFilterChain' defined in examples.java.springboot.resourceserver.OAuth2ResourceServerApp$SecurityConfiguration: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.server.SecurityWebFilterChain]: Factory method 'securityWebFilterChain' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jwtDecoderByIssuerUri' defined in class path resource [org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration$JwtConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.oauth2.jwt.ReactiveJwtDecoder]: Factory method 'jwtDecoderByIssuerUri' threw exception; nested exception is java.lang.IllegalStateException: com.nimbusds.jose.RemoteKeySourceException: Couldn't retrieve remote JWK set: Read timed out
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:768)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:720)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1413)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:601)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
	at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:64)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:338)
	at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:123)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
	... 93 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'securityWebFilterChain' defined in examples.java.springboot.resourceserver.OAuth2ResourceServerApp$SecurityConfiguration: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.server.SecurityWebFilterChain]: Factory method 'securityWebFilterChain' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jwtDecoderByIssuerUri' defined in class path resource [org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration$JwtConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.oauth2.jwt.ReactiveJwtDecoder]: Factory method 'jwtDecoderByIssuerUri' threw exception; nested exception is java.lang.IllegalStateException: com.nimbusds.jose.RemoteKeySourceException: Couldn't retrieve remote JWK set: Read timed out
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:638)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1334)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.addCandidateEntry(DefaultListableBeanFactory.java:1598)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1562)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveMultipleBeans(DefaultListableBeanFactory.java:1451)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1338)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:760)
	... 113 more
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.server.SecurityWebFilterChain]: Factory method 'securityWebFilterChain' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jwtDecoderByIssuerUri' defined in class path resource [org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration$JwtConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.oauth2.jwt.ReactiveJwtDecoder]: Factory method 'jwtDecoderByIssuerUri' threw exception; nested exception is java.lang.IllegalStateException: com.nimbusds.jose.RemoteKeySourceException: Couldn't retrieve remote JWK set: Read timed out
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653)
	... 129 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jwtDecoderByIssuerUri' defined in class path resource [org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration$JwtConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.oauth2.jwt.ReactiveJwtDecoder]: Factory method 'jwtDecoderByIssuerUri' threw exception; nested exception is java.lang.IllegalStateException: com.nimbusds.jose.RemoteKeySourceException: Couldn't retrieve remote JWK set: Read timed out
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:486)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1334)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:233)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1273)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1234)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:494)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1172)
	at org.springframework.security.config.web.server.ServerHttpSecurity.getBean(ServerHttpSecurity.java:1482)
	at org.springframework.security.config.web.server.ServerHttpSecurity.access$5800(ServerHttpSecurity.java:247)
	at org.springframework.security.config.web.server.ServerHttpSecurity$OAuth2ResourceServerSpec$JwtSpec.getJwtDecoder(ServerHttpSecurity.java:3892)
	at org.springframework.security.config.web.server.ServerHttpSecurity$OAuth2ResourceServerSpec$JwtSpec.getAuthenticationManager(ServerHttpSecurity.java:3903)
	at org.springframework.security.config.web.server.ServerHttpSecurity$OAuth2ResourceServerSpec$JwtSpec.configure(ServerHttpSecurity.java:3883)
	at org.springframework.security.config.web.server.ServerHttpSecurity$OAuth2ResourceServerSpec.configure(ServerHttpSecurity.java:3755)
	at org.springframework.security.config.web.server.ServerHttpSecurity.build(ServerHttpSecurity.java:1380)
	at examples.java.springboot.resourceserver.OAuth2ResourceServerApp$SecurityConfiguration.securityWebFilterChain(OAuth2ResourceServerApp.java:47)
	at examples.java.springboot.resourceserver.OAuth2ResourceServerApp$SecurityConfiguration$$EnhancerBySpringCGLIB$$c6b9d4f8.CGLIB$securityWebFilterChain$1(<generated>)
	at examples.java.springboot.resourceserver.OAuth2ResourceServerApp$SecurityConfiguration$$EnhancerBySpringCGLIB$$c6b9d4f8$$FastClassBySpringCGLIB$$fa327b33.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331)
	at examples.java.springboot.resourceserver.OAuth2ResourceServerApp$SecurityConfiguration$$EnhancerBySpringCGLIB$$c6b9d4f8.securityWebFilterChain(<generated>)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
	... 130 more
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.oauth2.jwt.ReactiveJwtDecoder]: Factory method 'jwtDecoderByIssuerUri' threw exception; nested exception is java.lang.IllegalStateException: com.nimbusds.jose.RemoteKeySourceException: Couldn't retrieve remote JWK set: Read timed out
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653)
	... 163 more
Caused by: java.lang.IllegalStateException: com.nimbusds.jose.RemoteKeySourceException: Couldn't retrieve remote JWK set: Read timed out
	at org.springframework.security.oauth2.jwt.JwtDecoderProviderConfigurationUtils.getSignatureAlgorithms(JwtDecoderProviderConfigurationUtils.java:107)
	at org.springframework.security.oauth2.jwt.ReactiveJwtDecoders.withProviderConfiguration(ReactiveJwtDecoders.java:120)
	at org.springframework.security.oauth2.jwt.ReactiveJwtDecoders.fromIssuerLocation(ReactiveJwtDecoders.java:100)
	at org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerJwkConfiguration$JwtConfiguration.jwtDecoderByIssuerUri(ReactiveOAuth2ResourceServerJwkConfiguration.java:95)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
	... 164 more
Caused by: com.nimbusds.jose.RemoteKeySourceException: Couldn't retrieve remote JWK set: Read timed out
	at com.nimbusds.jose.jwk.source.RemoteJWKSet.updateJWKSetFromURL(RemoteJWKSet.java:167)
	at com.nimbusds.jose.jwk.source.RemoteJWKSet.get(RemoteJWKSet.java:260)
	at org.springframework.security.oauth2.jwt.JwtDecoderProviderConfigurationUtils.getSignatureAlgorithms(JwtDecoderProviderConfigurationUtils.java:90)
	... 172 more
Caused by: java.net.SocketTimeoutException: Read timed out
	at java.base/java.net.SocketInputStream.socketRead0(Native Method)
	at java.base/java.net.SocketInputStream.socketRead(SocketInputStream.java:115)
	at java.base/java.net.SocketInputStream.read(SocketInputStream.java:168)
	at java.base/java.net.SocketInputStream.read(SocketInputStream.java:140)
	at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:252)
	at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:292)
	at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:351)
	at java.base/sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:754)
	at java.base/sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:689)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1615)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1520)
	at com.nimbusds.jose.util.DefaultResourceRetriever.getInputStream(DefaultResourceRetriever.java:305)
	at com.nimbusds.jose.util.DefaultResourceRetriever.retrieveResource(DefaultResourceRetriever.java:257)
	at com.nimbusds.jose.jwk.source.RemoteJWKSet.updateJWKSetFromURL(RemoteJWKSet.java:165)
	... 174 more

Maybe this has something to do with the change in #44 as we only experience it in our codebase after upgrading the version?

As a workaround, we can add a line like RestTemplate().getForObject(jwksUrl("default").toUri(), String::class.java) after calling start() and this resolves the issue.

Can you reproduce it? I use openjdk 11.0.12 on Linux.

Propose to make the library java 8 compliant

Making the library java 8 compliant will (possibly) make it easier for older applications to migrate from another security-framework.

This means that you loose the ability to use newer java features but this doesn't mean much at this point in time, as you can see in the proposed patch (ref:1). The patch is considered work in progress, but it should give an indication of how small this change really is.

It may be something I'm not aware of or haven't considered, but I'm willing to "do the heavy lifting" if the propose gets accepted.

Ref.
1: patped@8dfa35c

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.