Add sub via protocol mapper to access token

Closes #21185

Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
This commit is contained in:
Giuseppe Graziano 2024-04-08 18:29:17 +02:00 committed by Marek Posolda
parent a34c10ea40
commit c76cbc94d8
14 changed files with 263 additions and 36 deletions

View file

@ -256,7 +256,6 @@ public class TokenVerifier<T extends JsonWebToken> {
public TokenVerifier<T> withDefaultChecks() {
return withChecks(
RealmUrlCheck.NULL_INSTANCE,
SUBJECT_EXISTS_CHECK,
TokenTypeCheck.INSTANCE_DEFAULT_TOKEN_TYPE,
IS_ACTIVE
);

View file

@ -67,6 +67,12 @@ Use the *Script Mapper* to map claims to tokens by running user-defined JavaScri
When scripts deploy, you should be able to select the deployed scripts from the list of available mappers.
== Pairwise subject identifier mapper
Subject claim _sub_ is mapped by default by *Subject (sub)* protocol mapper in the default client scope *basic*.
To use a pairwise subject identifier by using a protocol mapper such as *Pairwise subject identifier*, remove the *Subject (sub)* protocol mapper from the *basic* client scope.
[[_using_lightweight_access_token]]
== Using lightweight access token
The access token in {project_name} contains sensitive information, including Personal Identifiable Information (PII).
@ -75,7 +81,7 @@ Further, when the resource server acquires the PII removed from the access token
Information that cannot be removed from a lightweight access token::
Protocol mappers can controls which information is put onto an access token and the lightweight access token use the protocol mappers. Therefore, the following information cannot be removed from the lightweight access. +
`exp`, `iat`, `jti`, `iss`, `sub`, `typ`, `azp`, `nonce`, `sid`, `scope`, `cnf`
`exp`, `iat`, `jti`, `iss`, `typ`, `azp`, `sid`, `scope`, `cnf`
Using a lightweight access token in {project_name}::
By applying `use-lightweight-access-token` executor of <<_client_policies, client policies>> to a client, the client can receive a lightweight access token instead of an access token. The lightweight access token contains a claim controlled by a protocol mapper where its setting `Add to lightweight access token`(default OFF) is turned ON. Also, by turning ON its setting `Add to token introspection` of the protocol mapper, the client can obtain the claim by sending the access token to {project_name}'s token introspection endpoint.

View file

@ -89,7 +89,19 @@ Note that the `setSessionState()` method is also removed from the `IDToken` clas
A new `Session State (session_state)` mapper is also included and can be assigned to client scopes (for instance `basic` client scope) to revert to the old behavior.
If an old version of the JS adapter is used, the `Session State (session_state)` mapper should also be used via client scopes as described above.
If an old version of the JS adapter is used, the `Session State (session_state)` mapper should also be used by using client scopes as described above.
= `sub` claim is added to access token via protocol mapper
The `sub` claim, which was always added to the access token, is now added by default but using a new `Subject (sub)` protocol mapper.
The `Subject (sub)` mapper is configured by default in the `basic` client scope. Therefore, no extra configuration is required after upgrading to this version.
Only in the case you are using `Pairwise subject identifier` mapper to map `sub` claim for access token you should disable or remove `Subject (sub)` mapper.
You can use the `Subject (sub)` mapper to configure the `sub` claim only for access token, lightweight access token, and introspection response. IDToken and Userinfo always contain `sub` claim.
The mapper has no effects for service accounts, because no user session exists, and the`sub` claim is always added to the access token.
= Default `http-pool-max-threads` reduced

View file

@ -70,6 +70,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
ObjectNode tokenMetadata;
if (accessToken != null) {
UserSessionModel userSession = accessToken.getSessionId() == null ? null : session.sessions().getUserSession(realm, accessToken.getSessionId());
tokenMetadata = JsonSerialization.createObjectNode(accessToken);
tokenMetadata.put("client_id", accessToken.getIssuedFor());
@ -82,25 +83,21 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
if (accessToken.getPreferredUsername() != null) {
tokenMetadata.put("username", accessToken.getPreferredUsername());
} else {
UserModel userModel = session.users().getUserById(realm, accessToken.getSubject());
UserModel userModel = accessToken.getSubject() == null ? null : session.users().getUserById(realm, accessToken.getSubject());
if (userModel != null) {
tokenMetadata.put("username", userModel.getUsername());
} else if (userSession != null && userSession.getUser() != null) {
tokenMetadata.put("username", userSession.getUser().getUsername());
}
}
}
String sessionState = accessToken.getSessionState();
if (userSession != null) {
String actor = userSession.getNote(ImpersonationSessionNote.IMPERSONATOR_USERNAME.toString());
if (sessionState != null) {
UserSessionModel userSession = session.sessions().getUserSession(realm, sessionState);
if (userSession != null) {
String actor = userSession.getNote(ImpersonationSessionNote.IMPERSONATOR_USERNAME.toString());
if (actor != null) {
// for token exchange delegation semantics when an entity (actor) other than the subject is the acting party to whom authority has been delegated
tokenMetadata.putObject("act").put("sub", actor);
}
if (actor != null) {
// for token exchange delegation semantics when an entity (actor) other than the subject is the acting party to whom authority has been delegated
tokenMetadata.putObject("act").put("sub", actor);
}
}
@ -157,7 +154,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
AccessToken newToken = new AccessToken();
newToken.id(token.getId());
newToken.type(token.getType());
newToken.subject(token.getSubject() != null ? token.getSubject() : userSession.getUser().getId());
newToken.subject(token.getSubject());
newToken.iat(token.getIat());
newToken.exp(token.getExp());
newToken.issuedFor(token.getIssuedFor());

View file

@ -45,6 +45,7 @@ import org.keycloak.protocol.oidc.mappers.UserClientRoleMappingMapper;
import org.keycloak.protocol.oidc.mappers.UserPropertyMapper;
import org.keycloak.protocol.oidc.mappers.UserRealmRoleMappingMapper;
import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
import org.keycloak.protocol.oidc.mappers.SubMapper;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.ServicesLogger;
@ -226,7 +227,10 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
model = UserSessionNoteMapper.createClaimMapper(IDToken.AUTH_TIME, AuthenticationManager.AUTH_TIME,
IDToken.AUTH_TIME, "long",
true, true, false, true);
builtins.put(BASIC_SCOPE, model);
builtins.put(IDToken.AUTH_TIME, model);
model = SubMapper.create(IDToken.SUBJECT,true, true);
builtins.put(IDToken.SUBJECT, model);
}
private void createUserAttributeMapper(String name, String attrName, String claimName, String type) {
@ -420,7 +424,8 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
basicScope.setDisplayOnConsentScreen(false);
basicScope.setIncludeInTokenScope(false);
basicScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
basicScope.addProtocolMapper(builtins.get(BASIC_SCOPE));
basicScope.addProtocolMapper(builtins.get(IDToken.AUTH_TIME));
basicScope.addProtocolMapper(builtins.get(IDToken.SUBJECT));
newRealm.addDefaultClientScope(basicScope, true);

View file

@ -387,7 +387,7 @@ public class TokenManager {
*/
public static UserModel lookupUserFromStatelessToken(KeycloakSession session, RealmModel realm, AccessToken token) {
// Try to lookup user based on "sub" claim. It should work for most cases with some rare exceptions (EG. OIDC "pairwise" subjects)
UserModel user = session.users().getUserById(realm, token.getSubject());
UserModel user = token.getSubject() == null ? null : session.users().getUserById(realm, token.getSubject());
if (user != null) {
return user;
}
@ -976,7 +976,9 @@ public class TokenManager {
AccessToken token = new AccessToken();
token.id(KeycloakModelUtils.generateId());
token.type(TokenUtil.TOKEN_TYPE_BEARER);
token.subject(user.getId());
if (UserSessionModel.SessionPersistenceState.TRANSIENT.equals(session.getPersistenceState())) {
token.subject(user.getId());
}
token.issuedNow();
token.issuedFor(client.getClientId());
@ -1184,7 +1186,7 @@ public class TokenManager {
idToken = new IDToken();
idToken.id(KeycloakModelUtils.generateId());
idToken.type(TokenUtil.TOKEN_TYPE_ID);
idToken.subject(accessToken.getSubject());
idToken.subject(userSession.getUser().getId());
idToken.audience(client.getClientId());
idToken.issuedNow();
idToken.issuedFor(accessToken.getIssuedFor());

View file

@ -40,10 +40,14 @@ public class SessionStateMapper extends AbstractOIDCProtocolMapper implements OI
public static final String PROVIDER_ID = "oidc-session-state-mapper";
private static final Logger logger = Logger.getLogger(AcrProtocolMapper.class);
private static final Logger logger = Logger.getLogger(SessionStateMapper.class);
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
static {
OIDCAttributeMapperHelper.addIncludeInTokensConfig(configProperties, SessionStateMapper.class);
}
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}

View file

@ -0,0 +1,94 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.protocol.oidc.mappers;
import org.jboss.logging.Logger;
import org.keycloak.models.ClientSessionContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.IDToken;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:ggrazian@redhat.com">Giuseppe Graziano</a>
*/
public class SubMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, TokenIntrospectionTokenMapper {
public static final String PROVIDER_ID = "oidc-sub-mapper";
private static final Logger logger = Logger.getLogger(SubMapper.class);
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
static {
OIDCAttributeMapperHelper.addIncludeInTokensConfig(configProperties, SubMapper.class);
}
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getDisplayType() {
return "Subject (sub)";
}
@Override
public String getDisplayCategory() {
return TOKEN_MAPPER_CATEGORY;
}
@Override
public String getHelpText() {
return "Add Subject (sub) claim";
}
@Override
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession,
ClientSessionContext clientSessionCtx) {
if (userSession != null && userSession.getUser() != null) {
token.subject(userSession.getUser().getId());
}
}
public static ProtocolMapperModel create(String name, boolean accessToken, boolean introspectionEndpoint) {
ProtocolMapperModel mapper = new ProtocolMapperModel();
mapper.setName(name);
mapper.setProtocolMapper(PROVIDER_ID);
mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
Map<String, String> config = new HashMap<>();
if (accessToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
if (introspectionEndpoint) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_INTROSPECTION, "true");
mapper.setConfig(config);
return mapper;
}
}

View file

@ -49,3 +49,4 @@ org.keycloak.protocol.saml.mappers.UserAttributeNameIdMapper
org.keycloak.protocol.oidc.mappers.ClaimsParameterWithValueIdTokenMapper
org.keycloak.protocol.oidc.mappers.NonceBackwardsCompatibleMapper
org.keycloak.protocol.oidc.mappers.SessionStateMapper
org.keycloak.protocol.oidc.mappers.SubMapper

View file

@ -36,6 +36,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jboss.logging.Logger;
@ -107,8 +108,10 @@ public class HardcodedClientStorageProvider implements ClientStorageProvider, Cl
if (defaultScope) {
ClientScopeModel rolesScope = KeycloakModelUtils.getClientScopeByName(realm, OIDCLoginProtocolFactory.ROLES_SCOPE);
ClientScopeModel webOriginsScope = KeycloakModelUtils.getClientScopeByName(realm, OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE);
return Arrays.asList(rolesScope, webOriginsScope)
ClientScopeModel basicScope = KeycloakModelUtils.getClientScopeByName(realm, OIDCLoginProtocolFactory.BASIC_SCOPE);
return Arrays.asList(rolesScope, webOriginsScope, basicScope)
.stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(ClientScopeModel::getName, clientScope -> clientScope));
} else {

View file

@ -28,6 +28,7 @@ import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.client.registration.Auth;
import org.keycloak.client.registration.ClientRegistrationException;
import org.keycloak.client.registration.HttpErrorException;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
import org.keycloak.protocol.oidc.mappers.SHA256PairwiseSubMapper;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
@ -90,6 +91,7 @@ public class OIDCPairwiseClientRegistrationTest extends AbstractClientRegistrati
OIDCClientRepresentation clientRep = createRep();
clientRep.setSubjectType("pairwise");
OIDCClientRepresentation pairwiseClient = reg.oidc().create(clientRep);
removeDefaultBasicClientScope(pairwiseClient.getClientId());
return pairwiseClient;
}
@ -327,11 +329,8 @@ public class OIDCPairwiseClientRegistrationTest extends AbstractClientRegistrati
Assert.assertEquals(user.getId(), tokenUserId);
// Create pairwise client
OIDCClientRepresentation clientRep = createRep();
clientRep.setSubjectType("pairwise");
OIDCClientRepresentation pairwiseClient = reg.oidc().create(clientRep);
OIDCClientRepresentation pairwiseClient = createPairwise();
Assert.assertEquals("pairwise", pairwiseClient.getSubjectType());
// Login to pairwise client
oauth.clientId(pairwiseClient.getClientId());
oauth.openLoginForm();
@ -371,7 +370,6 @@ public class OIDCPairwiseClientRegistrationTest extends AbstractClientRegistrati
public void refreshPairwiseToken() throws Exception {
// Create pairwise client
OIDCClientRepresentation pairwiseClient = createPairwise();
// Login to pairwise client
OAuthClient.AccessTokenResponse accessTokenResponse = login(pairwiseClient, "test-user@localhost", "password");
@ -487,4 +485,24 @@ public class OIDCPairwiseClientRegistrationTest extends AbstractClientRegistrati
String payloadBase64 = token.split("\\.")[1];
return new String(Base64.getDecoder().decode(payloadBase64));
}
public void addDefaultBasicClientScope(String clientId) {
realmsResouce().realm(REALM_NAME).getDefaultDefaultClientScopes()
.stream()
.filter(scope-> scope.getName().equals(OIDCLoginProtocolFactory.BASIC_SCOPE))
.findFirst()
.ifPresent(scope-> {
ApiUtil.findClientResourceByClientId(adminClient.realm(REALM_NAME), clientId).addDefaultClientScope(scope.getId());
});
}
public void removeDefaultBasicClientScope(String clientId) {
realmsResouce().realm(REALM_NAME).getDefaultDefaultClientScopes()
.stream()
.filter(scope-> scope.getName().equals(OIDCLoginProtocolFactory.BASIC_SCOPE))
.findFirst()
.ifPresent(scope-> {
ApiUtil.findClientResourceByClientId(adminClient.realm(REALM_NAME), clientId).removeDefaultClientScope(scope.getId());
});
}
}

View file

@ -378,7 +378,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
// create a user attr mapping for some claims that exist as properties in the tokens
ClientResource app = findClientResourceByClientId(adminClient.realm("test"), "test-app");
app.getProtocolMappers().createMapper(createClaimMapper("userid-as-sub", "userid", "sub", "String", true, true, true,false)).close();
app.getProtocolMappers().createMapper(createClaimMapper("userid-as-sub", "userid", "sub", "String", false, true, true,false)).close();
app.getProtocolMappers().createMapper(createClaimMapper("useraud", "useraud", "aud", "String", true, true, true, true)).close();
app.getProtocolMappers().createMapper(createHardcodedClaim("website-hardcoded", "website", "http://localhost", "String", true, true, true)).close();
app.getProtocolMappers().createMapper(createHardcodedClaim("iat-hardcoded", "iat", "123", "long", true, false, true)).close();
@ -394,7 +394,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
assertThat(Arrays.asList(idToken.getAudience()), hasItems("test-app", "other"));
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
assertEquals(user.firstAttribute("userid"), accessToken.getSubject());
assertNotEquals(user.firstAttribute("userid"), accessToken.getSubject());
assertEquals("http://localhost", accessToken.getWebsite());
assertNotNull(accessToken.getAudience());
assertThat(Arrays.asList(accessToken.getAudience()), hasItems("test-app", "other"));

View file

@ -349,7 +349,6 @@ public class TokenIntrospectionTest extends AbstractTestRealmKeycloakTest {
assertTrue(rep.isActive());
assertEquals("test-user@localhost", rep.getUserName());
assertEquals("no-scope", rep.getClientId());
assertEquals(loginEvent.getUserId(), rep.getSubject());
assertNull(rep.getScope());
} finally {
testRealm.setClientScopes(preExistingClientScopes);

View file

@ -29,6 +29,7 @@ import org.keycloak.common.Profile;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
import org.keycloak.protocol.oidc.mappers.AudienceProtocolMapper;
import org.keycloak.protocol.oidc.mappers.GroupMembershipMapper;
import org.keycloak.protocol.oidc.mappers.HardcodedClaim;
@ -268,7 +269,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
@Test
public void testPolicyLightWeightFalseTest() throws Exception {
setUseLightweightAccessTokenExecutor();
ProtocolMappersResource protocolMappers = setProtocolMappers(true, true, false, true);
ProtocolMappersResource protocolMappers = setProtocolMappers(true, true, false, false);
try {
oauth.nonce("123456");
oauth.scope("address");
@ -303,7 +304,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
@Test
public void testPolicyLightWeightTrueTest() throws Exception {
setUseLightweightAccessTokenExecutor();
ProtocolMappersResource protocolMappers = setProtocolMappers(false, true, true, true);
ProtocolMappersResource protocolMappers = setProtocolMappers(false, true, true, false);
try {
oauth.nonce("123456");
oauth.scope("address");
@ -329,7 +330,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
@Test
public void testAlwaysUseLightWeightFalseTest() throws Exception {
alwaysUseLightWeightAccessToken(true);
ProtocolMappersResource protocolMappers = setProtocolMappers(true, true, false, true);
ProtocolMappersResource protocolMappers = setProtocolMappers(true, true, false, false);
try {
oauth.nonce("123456");
oauth.scope("address");
@ -364,7 +365,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
@Test
public void testAlwaysUseLightWeightTrueTest() throws Exception {
alwaysUseLightWeightAccessToken(true);
ProtocolMappersResource protocolMappers = setProtocolMappers(false, true, true, true);
ProtocolMappersResource protocolMappers = setProtocolMappers(false, true, true, false);
try {
oauth.nonce("123456");
oauth.scope("address");
@ -388,6 +389,71 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
}
}
@Test
public void testWithoutBasicClaim() throws Exception {
alwaysUseLightWeightAccessToken(true);
removeDefaultBasicClientScope();
ProtocolMappersResource protocolMappers = setProtocolMappers(true, true, false, false);
try {
oauth.clientId(TEST_CLIENT);
oauth.scope("address");
OAuthClient.AuthorizationEndpointResponse authsEndpointResponse = oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(authsEndpointResponse.getCode(), TEST_CLIENT_SECRET);
String accessToken = tokenResponse.getAccessToken();
logger.debug("access token:" + accessToken);
assertAccessToken(oauth.verifyToken(accessToken), true, false,true);
oauth.clientId(RESOURCE_SERVER_CLIENT_ID);
String introspectResponse = oauth.introspectAccessTokenWithClientCredential(RESOURCE_SERVER_CLIENT_ID, RESOURCE_SERVER_CLIENT_PASSWORD, accessToken);
logger.debug("tokenResponse:" + introspectResponse);
assertTokenIntrospectionResponse(JsonSerialization.readValue(introspectResponse, AccessToken.class), true, true, true);
oauth.clientId(TEST_CLIENT);
alwaysUseLightWeightAccessToken(false);
oauth.doLogout(tokenResponse.getRefreshToken(), TEST_CLIENT_SECRET);
authsEndpointResponse = oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);
tokenResponse = oauth.doAccessTokenRequest(authsEndpointResponse.getCode(), TEST_CLIENT_SECRET);
accessToken = tokenResponse.getAccessToken();
logger.debug("access token:" + accessToken);
assertAccessToken(oauth.verifyToken(accessToken), true, true, true);
oauth.clientId(RESOURCE_SERVER_CLIENT_ID);
introspectResponse = oauth.introspectAccessTokenWithClientCredential(RESOURCE_SERVER_CLIENT_ID, RESOURCE_SERVER_CLIENT_PASSWORD, accessToken);
logger.debug("tokenResponse:" + introspectResponse);
assertTokenIntrospectionResponse(JsonSerialization.readValue(introspectResponse, AccessToken.class), true, true, true);
} finally {
deleteProtocolMappers(protocolMappers);
addDefaultBasicClientScope();
}
}
@Test
public void clientCredentialWithoutBasicClaims() throws Exception {
removeDefaultBasicClientScope();
alwaysUseLightWeightAccessToken(true);
try {
oauth.nonce("123456");
oauth.clientId(TEST_CLIENT);
OAuthClient.AccessTokenResponse response = oauth.doClientCredentialsGrantAccessTokenRequest(TEST_CLIENT_SECRET);
String accessToken = response.getAccessToken();
logger.debug("accessToken:" + accessToken);
assertAccessToken(oauth.verifyToken(accessToken), false, false,false);
oauth.clientId(RESOURCE_SERVER_CLIENT_ID);
String tokenResponse = oauth.introspectAccessTokenWithClientCredential(RESOURCE_SERVER_CLIENT_ID, RESOURCE_SERVER_CLIENT_PASSWORD, accessToken);
logger.debug("tokenResponse:" + tokenResponse);
assertTokenIntrospectionResponse(JsonSerialization.readValue(tokenResponse, AccessToken.class), false, true, false);
} finally {
addDefaultBasicClientScope();
alwaysUseLightWeightAccessToken(false);
}
}
private void removeSession(final String sessionId) {
testingClient.testing().removeExpired(REALM_NAME);
try {
@ -446,7 +512,6 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
Assert.assertNotNull(token.getIssuedFor());
Assert.assertNotNull(token.getScope());
Assert.assertNotNull(token.getIssuer());
Assert.assertNotNull(token.getSubject());
if (isAuthCodeFlow) {
Assert.assertNotNull(token.getSessionId());
} else {
@ -457,7 +522,9 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
private void assertBasicClaims(AccessToken token, boolean isAuthCodeFlow, boolean missing) {
if (missing) {
Assert.assertNull(token.getAuth_time());
Assert.assertNull(token.getSubject());
} else {
Assert.assertNotNull(token.getSubject());
if (isAuthCodeFlow) {
Assert.assertNotNull(token.getAuth_time());
} else {
@ -491,6 +558,26 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
return adminClient.realm(REALM_NAME);
}
public void addDefaultBasicClientScope() {
testRealm().getDefaultDefaultClientScopes()
.stream()
.filter(scope-> scope.getName().equals(OIDCLoginProtocolFactory.BASIC_SCOPE))
.findFirst()
.ifPresent(scope-> {
ApiUtil.findClientResourceByClientId(adminClient.realm(REALM_NAME), TEST_CLIENT).addDefaultClientScope(scope.getId());
});
}
public void removeDefaultBasicClientScope() {
testRealm().getDefaultDefaultClientScopes()
.stream()
.filter(scope-> scope.getName().equals(OIDCLoginProtocolFactory.BASIC_SCOPE))
.findFirst()
.ifPresent(scope-> {
ApiUtil.findClientResourceByClientId(adminClient.realm(REALM_NAME), TEST_CLIENT).removeDefaultClientScope(scope.getId());
});
}
private void setScopeProtocolMappers(boolean isIncludeAccessToken, boolean isIncludeIntrospection, boolean isIncludeLightweightAccessToken) {
setScopeProtocolMapper(ACR_SCOPE, ACR, isIncludeAccessToken, isIncludeIntrospection, isIncludeLightweightAccessToken);
setScopeProtocolMapper(PROFILE_CLAIM, FULL_NAME, isIncludeAccessToken, isIncludeIntrospection, isIncludeLightweightAccessToken);