ch4mpy / spring-addons

Ease spring OAuth2 resource-servers configuration and testing

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Define a way with `WithMockKeycloakAuth` to populate accessToken information

SrMouraSilva opened this issue · comments

In the version 2.1.0, is possible to define sessionState

@WithMockKeycloakAuth(authorities=["something"]), accessToken=WithAccessToken(sessionState="00000000-0000-0000-0000-000000000000")

But in the version 2.4.0, I don't found a way to define AccessToken.sessionState (session_state). Also, I need define too AccessToken.id (jti) and AccessToken.subject (sub).

I found how to define the sub, but not others

@WithMockKeycloakAuth(authorities=[CLASSROOM_TURMAS_CADASTRAR_COMANDO], id=IdTokenClaims(sub="00000000-0000-0000-0000-000000000000"))

Maybe a way to do this is defining a hashmap or something like to add all the (custom) token values that are needed

In 2.3.0, I reorganized the properties, but this is mostly code shuffling, you should have about the same configuration options.
It is possible I lost a few very low-level JWT claims like jti in the process, but see at the end of this post how you can set it in privateClaims).
How comes your @controller code (or @service or any other "business" code) needs access to such claims (session_state and jti)? Shouldn't this be used by upstream frameworks only (like Spring-security, before access is granted and KeycloakAuthenticationToken is build)?

The reason for this change is I chose to get closer to the OpenID spec to re-use some code I wrote for another OpenID Authentication implementation (I personally don't use KeycloakAuthenticationToken any more mostly because Keycloak Spring libs are moving too slow). This spec is there https://openid.net/specs/openid-connect-core-1_0.html

Current state:

  • IDToken claims are in @WithMockKeycloakAuth(id = @IdTokenClaims(...))
  • StandardClaims are in `@WithMockKeycloakAuth(oidc = @OidcStandardClaims(...))
  • KeycloakAccessToken, which wraps Keycloak private claims (not in the standard) are defined in @WithMockKeycloakAuth(accessToken = @KeycloakAccessToken(...))

What this means for you:

  • if you are looking for OpenID standard claims, set it either in @WithMockKeycloakAuth id or oidc properties (sub is standard IDToken claim and so under id)
  • if you are looking for Keycloak "standard" claims (I mean for the claims I knew at moment I wrote the lib), dig under accessToken property
  • if you want to set claims that are neither in OpenID nor Keycloak specs (like jti and session_state or claims I missed), you should still be able to set it in privateClaims

As usual, samples are rather informative:

	@Test
	@WithMockKeycloakAuth(
			authorities = { "USER", "AUTHORIZED_PERSONNEL" },
			id = @IdTokenClaims(sub = "42"),
			oidc = @OidcStandardClaims(
					email = "ch4mp@c4-soft.com",
					emailVerified = true,
					nickName = "Tonton-Pirate",
					preferredUsername = "ch4mpy"),
			accessToken = @KeycloakAccessToken(
					realmAccess = @KeycloakAccess(roles = { "TESTER" }),
					authorization = @KeycloakAuthorization(
							permissions = @KeycloakPermission(rsid = "toto", rsname = "truc", scopes = "abracadabra"))),
			privateClaims = @ClaimSet(stringClaims = @StringClaim(name = "foo", value = "bar")))
	public void whenAuthenticatedWithKeycloakAuthenticationTokenThenCanGreet() throws Exception {
		api.get("/greet")
				.andExpect(status().isOk())
				.andExpect(content().string(startsWith("Hello ch4mpy! You are granted with ")))
				.andExpect(content().string(containsString("AUTHORIZED_PERSONNEL")))
				.andExpect(content().string(containsString("USER")))
				.andExpect(content().string(containsString("TESTER")));
	}

Hi, thanks for the response!

How comes your @controller code (or @service or any other "business" code) needs access to such claims (session_state and jti)? Shouldn't this be used by upstream frameworks only (like Spring-security, before access is granted and KeycloakAuthenticationToken is build)?

I create a audit Filter what use this information by auditing purposes. In fact this fields are not relevant by the test, but the object is used to other things, including a way to obtained the user data. As this three fields are always informed by Keycloak, I defined they as not null in my Kotlin code

It is possible I lost a few very low-level JWT claims like jti in the process, [...]

I don't found any reference in the current branch to jti by the Github seach

--

Ok, now I change to the similar example,

@WithMockKeycloakAuth(
            authorities=[CLASSROOM_TURMAS_CADASTRAR_COMANDO],
            id=IdTokenClaims(sub="00000000-0000-0000-0000-000000000000"),
            oidc = OidcStandardClaims(),
            privateClaims = ClaimSet(stringClaims=[
                StringClaim(name = "jti", value = "00000000-0000-0000-0000-000000000000"),
                StringClaim(name = IDToken.SESSION_STATE, value = "00000000-0000-0000-0000-000000000000")
            ])
    )

but this privateClaims only populate otherClaims
image

This spec is there https://openid.net/specs/openid-connect-core-1_0.html

session_scope can be found in https://openid.net/specs/openid-connect-session-1_0.html#CreatingUpdatingSessions, but is a draft


Congratulations for the awesome project :D !

I re-open. Further investigations needed

Renamed privateClaims to otherClaims in @WithMockKeycloakAuth for clarity: Keycloak's token model does not support private claims at root as per JWT spec. Instead, it maps unknown claims to otherClaims properties.

Added jti and nbf (from JWT spec) to @IdTokenClaims (an ID token is a JWT)

Also added session_state to @IdTokenClaims as per https://openid.net/specs/openid-connect-session-1_0.html#CreatingUpdatingSessions

Sample usage:

	@WithMockKeycloakAuth(
			authorities = { "USER", "AUTHORIZED_PERSONNEL" },
			id = @IdTokenClaims(
					sub = "42",
					jti = "123-456-789",
					nbf = "2020-11-18T20:38:00Z",
					sessionState = "987-654-321"),
			oidc = @OidcStandardClaims(
					email = "ch4mp@c4-soft.com",
					emailVerified = true,
					nickName = "Tonton-Pirate",
					preferredUsername = "ch4mpy"),
			accessToken = @KeycloakAccessToken(
					realmAccess = @KeycloakAccess(roles = { "TESTER" }),
					authorization = @KeycloakAuthorization(
							permissions = @KeycloakPermission(rsid = "toto", rsname = "truc", scopes = "abracadabra"))),
			otherClaims = @ClaimSet(stringClaims = @StringClaim(name = "foo", value = "bar")))

Please re-open if you find other missing claims.

P.S.
@SrMouraSilva sorry for abnormally long processing. I got caught by urgent stuff at work, then changed my laptop (and had to re-install everything) and then just ... forgot about this opened issue => so very, very sorry :(

@SrMouraSilva sorry for abnormally long processing. I got caught by urgent stuff at work, then changed my laptop (and had to re-install everything) and then just ... forgot about this opened issue => so very, very sorry :(

Hi ch4mpy! I completely understand the situation. Thank you for your availability! I am updating my code.