Enhancing Lightweight access token M2(keycloak#25716)

Closes keycloak#23724

Signed-off-by: shigeyuki kabano <shigeyuki.kabano.sj@hitachi.com>
This commit is contained in:
shigeyuki kabano 2023-12-14 19:07:22 +09:00 committed by Marek Posolda
parent 7fad0e805e
commit 67e73d3d4e
10 changed files with 378 additions and 101 deletions

View file

@ -2968,4 +2968,6 @@ referralHelp=Specifies if LDAP referrals should be followed or ignored. Please n
authenticatorRefConfig.value.help=Add a custom reference name for the authenticator. When this authenticator is successfully completed during an authentication flow, the Authentication Method Reference (AMR) protocol mapper will use this value to populate the amr claim of the generated tokens. Note, the AMR protocol must be configured for the given client to populate the AMR claim
authenticatorRefConfig.value.label=Authenticator Reference
authenticatorRefConfig.maxAge.help=The max age in seconds that the authenticator reference value is good for in an SSO session. When the Authentication Method Reference (AMR) protocol mapper is used, the AMR will only be considered valid and populated in the token if the authenticator execution was completed within the specified max age.
authenticatorRefConfig.maxAge.label=Authenticator Reference Max Age
authenticatorRefConfig.maxAge.label=Authenticator Reference Max Age
includeInLightweight.label=Add to lightweight access token
includeInLightweight.tooltip=Should the claim be added to the lightweight access token?

View file

@ -163,4 +163,5 @@ public final class Constants {
public static final String SESSION_NOTE_LIGHTWEIGHT_USER = "keycloak.userModel";
public static final String USE_LIGHTWEIGHT_ACCESS_TOKEN_ENABLED = "client.use.lightweight.access.token.enabled";
}

View file

@ -19,6 +19,7 @@ package org.keycloak.protocol.oidc.mappers;
import org.keycloak.Config;
import org.keycloak.models.ClientSessionContext;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ProtocolMapperModel;
@ -80,10 +81,16 @@ public abstract class AbstractOIDCProtocolMapper implements ProtocolMapper {
return token;
}
boolean getShouldUseLightweightToken(KeycloakSession session) {
Object attributeValue = session.getAttribute(Constants.USE_LIGHTWEIGHT_ACCESS_TOKEN_ENABLED);
return (attributeValue != null) ? (boolean) attributeValue : false;
}
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)){
boolean shouldUseLightweightToken = getShouldUseLightweightToken(session);
boolean includeInAccessToken = shouldUseLightweightToken ? OIDCAttributeMapperHelper.includeInLightweightAccessToken(mappingModel) : OIDCAttributeMapperHelper.includeInAccessToken(mappingModel);
if (!includeInAccessToken){
return token;
}

View file

@ -83,8 +83,9 @@ public class AllowedWebOriginsProtocolMapper extends AbstractOIDCProtocolMapper
@Override
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
if (!includeInAccessToken(mappingModel)){
boolean shouldUseLightweightToken = getShouldUseLightweightToken(session);
boolean includeInAccessToken = shouldUseLightweightToken ? OIDCAttributeMapperHelper.includeInLightweightAccessToken(mappingModel) : includeInAccessToken(mappingModel);
if (!includeInAccessToken){
return token;
}
setWebOrigin(token, session, clientSessionCtx);

View file

@ -88,7 +88,9 @@ public class AudienceResolveProtocolMapper extends AbstractOIDCProtocolMapper im
@Override
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
if (!includeInAccessToken(mappingModel)){
boolean shouldUseLightweightToken = getShouldUseLightweightToken(session);
boolean includeInAccessToken = shouldUseLightweightToken ? OIDCAttributeMapperHelper.includeInLightweightAccessToken(mappingModel) : includeInAccessToken(mappingModel);
if (!includeInAccessToken){
return token;
}
setAudience(token, clientSessionCtx, session);

View file

@ -71,6 +71,12 @@ public class OIDCAttributeMapperHelper {
public static final String INCLUDE_IN_INTROSPECTION_LABEL = "includeInIntrospection.label";
public static final String INCLUDE_IN_INTROSPECTION_HELP_TEXT = "includeInIntrospection.tooltip";
public static final String INCLUDE_IN_LIGHTWEIGHT_ACCESS_TOKEN = "lightweight.claim";
public static final String INCLUDE_IN_LIGHTWEIGHT_ACCESS_TOKEN_LABEL = "includeInLightweight.label";
public static final String INCLUDE_IN_LIGHTWEIGHT_ACCESS_TOKEN_HELP_TEXT = "includeInLightweight.tooltip";
private static final Logger logger = Logger.getLogger(OIDCAttributeMapperHelper.class);
/**
@ -392,6 +398,10 @@ public class OIDCAttributeMapperHelper {
return "true".equals(includeInIntrospection);
}
public static boolean includeInLightweightAccessToken(ProtocolMapperModel mappingModel) {
return "true".equals(mappingModel.getConfig().get(INCLUDE_IN_LIGHTWEIGHT_ACCESS_TOKEN));
}
public static void addAttributeConfig(List<ProviderConfigProperty> configProperties, Class<? extends ProtocolMapper> protocolMapperClass) {
addTokenClaimNameConfig(configProperties);
addJsonTypeConfig(configProperties);
@ -443,6 +453,14 @@ public class OIDCAttributeMapperHelper {
property.setDefaultValue("true");
property.setHelpText(INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT);
configProperties.add(property);
ProviderConfigProperty property2 = new ProviderConfigProperty();
property2.setName(INCLUDE_IN_LIGHTWEIGHT_ACCESS_TOKEN);
property2.setLabel(INCLUDE_IN_LIGHTWEIGHT_ACCESS_TOKEN_LABEL);
property2.setType(ProviderConfigProperty.BOOLEAN_TYPE);
property2.setDefaultValue("false");
property2.setHelpText(INCLUDE_IN_LIGHTWEIGHT_ACCESS_TOKEN_HELP_TEXT);
configProperties.add(property2);
}
if (UserInfoTokenMapper.class.isAssignableFrom(protocolMapperClass)) {

View file

@ -0,0 +1,51 @@
/*
* Copyright 2023 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.services.clientpolicy.executor;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException;
public class UseLightweightAccessTokenExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfigurationRepresentation> {
private final KeycloakSession session;
public UseLightweightAccessTokenExecutor(KeycloakSession session) {
this.session = session;
}
@Override
public String getProviderId() {
return UseLightweightAccessTokenExecutorFactory.PROVIDER_ID;
}
@Override
public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
switch (context.getEvent()) {
case TOKEN_REQUEST:
case TOKEN_REFRESH:
case RESOURCE_OWNER_PASSWORD_CREDENTIALS_REQUEST:
case SERVICE_ACCOUNT_TOKEN_REQUEST:
case BACKCHANNEL_TOKEN_REQUEST:
case DEVICE_TOKEN_REQUEST:
session.setAttribute(Constants.USE_LIGHTWEIGHT_ACCESS_TOKEN_ENABLED, true);
break;
}
}
}

View file

@ -0,0 +1,70 @@
/*
* Copyright 2023 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.services.clientpolicy.executor;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.Collections;
import java.util.List;
public class UseLightweightAccessTokenExecutorFactory implements ClientPolicyExecutorProviderFactory {
public static final String PROVIDER_ID = "use-lightweight-access-token";
@Override
public String getHelpText() {
return "Use lightweight access token";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return Collections.emptyList();
}
@Override
public ClientPolicyExecutorProvider create(KeycloakSession session) {
return new UseLightweightAccessTokenExecutor(session);
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public boolean isSupported() {
return true;
}
}

View file

@ -22,4 +22,5 @@ org.keycloak.services.clientpolicy.executor.SuppressRefreshTokenRotationExecutor
org.keycloak.services.clientpolicy.executor.RegistrationAccessTokenRotationDisabledExecutorFactory
org.keycloak.services.clientpolicy.executor.RejectImplicitGrantExecutorFactory
org.keycloak.services.clientpolicy.executor.SecureParContentsExecutorFactory
org.keycloak.services.clientpolicy.executor.DPoPBindEnforcerExecutorFactory
org.keycloak.services.clientpolicy.executor.DPoPBindEnforcerExecutorFactory
org.keycloak.services.clientpolicy.executor.UseLightweightAccessTokenExecutorFactory

View file

@ -1,6 +1,25 @@
/*
* Copyright 2023 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.testsuite.oidc;
import jakarta.ws.rs.NotFoundException;
import org.jboss.logging.Logger;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.ClientScopeResource;
@ -23,15 +42,17 @@ import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.services.clientpolicy.condition.AnyClientConditionFactory;
import org.keycloak.services.clientpolicy.executor.UseLightweightAccessTokenExecutorFactory;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.client.policies.AbstractClientPoliciesTest;
import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.ClientPoliciesUtil;
import org.keycloak.testsuite.util.KeycloakModelUtils;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.ProtocolMapperUtil;
import org.keycloak.util.JsonSerialization;
import org.wildfly.common.Assert;
import java.io.IOException;
import java.util.ArrayList;
@ -62,25 +83,29 @@ import static org.keycloak.protocol.oidc.mappers.AudienceProtocolMapper.INCLUDED
import static org.keycloak.protocol.oidc.mappers.HardcodedClaim.CLAIM_VALUE;
import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN;
import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.INCLUDE_IN_INTROSPECTION;
import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.INCLUDE_IN_LIGHTWEIGHT_ACCESS_TOKEN;
import static org.keycloak.protocol.oidc.mappers.PairwiseSubMapperHelper.PAIRWISE_SUB_ALGORITHM_SALT;
import static org.keycloak.protocol.oidc.mappers.RoleNameMapper.NEW_ROLE_NAME;
import static org.keycloak.protocol.oidc.mappers.RoleNameMapper.ROLE_CONFIG;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
import static org.keycloak.testsuite.util.ClientPoliciesUtil.createAnyClientConditionConfig;
@EnableFeature(value = Profile.Feature.TOKEN_EXCHANGE, skipRestart = true)
public class LightWeightAccessTokenTest extends AbstractKeycloakTest {
public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
private static final Logger logger = Logger.getLogger(LightWeightAccessTokenTest.class);
private static String RESOURCE_SERVER_CLIENT_ID = "resource-server";
private static String RESOURCE_SERVER_CLIENT_PASSWORD = "password";
@Before
public void clientConfiguration() {
ClientManager.realm(adminClient.realm("test")).clientId("test-app").directAccessGrant(true).setServiceAccountsEnabled(true);
ClientManager.realm(adminClient.realm("test")).clientId("resource-server").directAccessGrant(true);
ClientManager.realm(adminClient.realm(REALM_NAME)).clientId(TEST_CLIENT).directAccessGrant(true).setServiceAccountsEnabled(true);
ClientManager.realm(adminClient.realm(REALM_NAME)).clientId(RESOURCE_SERVER_CLIENT_ID).directAccessGrant(true);
}
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
UserRepresentation user = findUser(realm, "test-user@localhost");
UserRepresentation user = findUser(realm, TEST_USER_NAME);
Map<String, List<String>> attributes = new HashMap<>(){{
put("street", Arrays.asList("1 My Street"));
put("locality", Arrays.asList("Cardiff"));
@ -89,8 +114,8 @@ public class LightWeightAccessTokenTest extends AbstractKeycloakTest {
}};
user.setAttributes(attributes);
user.setGroups(Arrays.asList("/topGroup/level2group"));
ClientRepresentation confApp = KeycloakModelUtils.createClient(realm, "resource-server");
confApp.setSecret("password");
ClientRepresentation confApp = KeycloakModelUtils.createClient(realm, RESOURCE_SERVER_CLIENT_ID);
confApp.setSecret(RESOURCE_SERVER_CLIENT_PASSWORD);
confApp.setServiceAccountsEnabled(Boolean.TRUE);
testRealms.add(realm);
}
@ -99,7 +124,6 @@ public class LightWeightAccessTokenTest extends AbstractKeycloakTest {
for (UserRepresentation user : testRealm.getUsers()) {
if (user.getUsername().equals(userName)) return user;
}
return null;
}
@ -109,15 +133,15 @@ public class LightWeightAccessTokenTest extends AbstractKeycloakTest {
try {
oauth.nonce("123456");
oauth.scope("address");
oauth.clientId("test-app");
OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password").tokenResponse;
oauth.clientId(TEST_CLIENT);
OAuthClient.AccessTokenResponse response = browserLogin(TEST_CLIENT_SECRET, TEST_USER_NAME, TEST_USER_PASSWORD).tokenResponse;
String accessToken = response.getAccessToken();
System.out.println("accessToken:" + accessToken);
logger.debug("accessToken:" + accessToken);
assertAccessToken(oauth.verifyToken(accessToken), true, false);
oauth.clientId("resource-server");
String tokenResponse = oauth.introspectAccessTokenWithClientCredential("resource-server", "password", accessToken);
System.out.println("tokenResponse:" + tokenResponse);
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), true, true, false);
} finally {
deleteProtocolMappers(protocolMappers);
@ -130,15 +154,15 @@ public class LightWeightAccessTokenTest extends AbstractKeycloakTest {
try {
oauth.nonce("123456");
oauth.scope("address");
oauth.clientId("test-app");
OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password").tokenResponse;
oauth.clientId(TEST_CLIENT);
OAuthClient.AccessTokenResponse response = browserLogin(TEST_CLIENT_SECRET, TEST_USER_NAME, TEST_USER_PASSWORD).tokenResponse;
String accessToken = response.getAccessToken();
System.out.println("accessToken:" + accessToken);
logger.debug("accessToken:" + accessToken);
assertAccessToken(oauth.verifyToken(accessToken), true, true);
oauth.clientId("resource-server");
String tokenResponse = oauth.introspectAccessTokenWithClientCredential("resource-server", "password", accessToken);
System.out.println("tokenResponse:" + tokenResponse);
oauth.clientId(RESOURCE_SERVER_CLIENT_ID);
String tokenResponse = oauth.introspectAccessTokenWithClientCredential(RESOURCE_SERVER_CLIENT_ID, RESOURCE_SERVER_CLIENT_PASSWORD, accessToken);
logger.debug("tokenResponse:" + tokenResponse);
// Most of the claims should not be included in introspectionResponse as introspectionMapper was disabled
assertTokenIntrospectionResponse(JsonSerialization.readValue(tokenResponse, AccessToken.class), true, false, false);
} finally {
@ -152,15 +176,15 @@ public class LightWeightAccessTokenTest extends AbstractKeycloakTest {
try {
oauth.nonce("123456");
oauth.scope("address");
oauth.clientId("test-app");
OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password").tokenResponse;
oauth.clientId(TEST_CLIENT);
OAuthClient.AccessTokenResponse response = browserLogin(TEST_CLIENT_SECRET, TEST_USER_NAME, TEST_USER_PASSWORD).tokenResponse;
String accessToken = response.getAccessToken();
System.out.println("accessToken:" + accessToken);
logger.debug("accessToken:" + accessToken);
assertAccessToken(oauth.verifyToken(accessToken), true, true);
oauth.clientId("resource-server");
String tokenResponse = oauth.introspectAccessTokenWithClientCredential("resource-server", "password", accessToken);
System.out.println("tokenResponse:" + tokenResponse);
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), true, true, false);
} finally {
deleteProtocolMappers(protocolMappers);
@ -174,33 +198,24 @@ public class LightWeightAccessTokenTest extends AbstractKeycloakTest {
oauth.nonce("123456");
oauth.scope("openid address offline_access");
oauth.clientId("test-app");
TokenResponseContext ctx = browserLogin("password", "test-user@localhost", "password");
oauth.clientId(TEST_CLIENT);
TokenResponseContext ctx = browserLogin(TEST_CLIENT_SECRET, TEST_USER_NAME, TEST_USER_PASSWORD);
OAuthClient.AccessTokenResponse response = ctx.tokenResponse;
String accessToken = response.getAccessToken();
System.out.println("accessToken:" + accessToken);
System.out.println("idtoken:" + response.getIdToken());
logger.debug("accessToken:" + accessToken);
logger.debug("idtoken:" + response.getIdToken());
assertAccessToken(oauth.verifyToken(accessToken), true, false);
oauth.clientId("resource-server");
oauth.clientId(RESOURCE_SERVER_CLIENT_ID);
removeSession(ctx.userSessionId);
String tokenResponse = oauth.introspectAccessTokenWithClientCredential("resource-server", "password", accessToken);
System.out.println("tokenResponse:" + tokenResponse);
String tokenResponse = oauth.introspectAccessTokenWithClientCredential(RESOURCE_SERVER_CLIENT_ID, RESOURCE_SERVER_CLIENT_PASSWORD, accessToken);
logger.debug("tokenResponse:" + tokenResponse);
assertTokenIntrospectionResponse(JsonSerialization.readValue(tokenResponse, AccessToken.class), true, true, false);
} finally {
deleteProtocolMappers(protocolMappers);
}
}
private void removeSession(final String sessionId) {
testingClient.testing().removeExpired("test");
try {
testingClient.testing().removeUserSession("test", sessionId);
} catch (NotFoundException nfe) {
// Ignore
}
}
@Test
public void clientCredentialTest() throws Exception {
ProtocolMappersResource protocolMappers = setProtocolMappers(false, true, false);
@ -208,15 +223,15 @@ public class LightWeightAccessTokenTest extends AbstractKeycloakTest {
oauth.nonce("123456");
oauth.scope("address");
oauth.clientId("test-app");
OAuthClient.AccessTokenResponse response = oauth.doClientCredentialsGrantAccessTokenRequest("password");
oauth.clientId(TEST_CLIENT);
OAuthClient.AccessTokenResponse response = oauth.doClientCredentialsGrantAccessTokenRequest(TEST_CLIENT_SECRET);
String accessToken = response.getAccessToken();
System.out.println("accessToken:" + accessToken);
logger.debug("accessToken:" + accessToken);
assertAccessToken(oauth.verifyToken(accessToken), false, false);
oauth.clientId("resource-server");
String tokenResponse = oauth.introspectAccessTokenWithClientCredential("resource-server", "password", accessToken);
System.out.println("tokenResponse:" + tokenResponse);
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);
} finally {
deleteProtocolMappers(protocolMappers);
@ -230,24 +245,94 @@ public class LightWeightAccessTokenTest extends AbstractKeycloakTest {
oauth.nonce("123456");
oauth.scope("address");
oauth.clientId("test-app");
OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password").tokenResponse;
oauth.clientId(TEST_CLIENT);
OAuthClient.AccessTokenResponse response = browserLogin(TEST_CLIENT_SECRET, TEST_USER_NAME, TEST_USER_PASSWORD).tokenResponse;
String accessToken = response.getAccessToken();
System.out.println("accessToken:" + accessToken);
logger.debug("accessToken:" + accessToken);
assertAccessToken(oauth.verifyToken(accessToken), true, false);
response = oauth.doTokenExchange(TEST, accessToken, null, "test-app", "password");
response = oauth.doTokenExchange(TEST, accessToken, null, TEST_CLIENT, TEST_CLIENT_SECRET);
String exchangedTokenString = response.getAccessToken();
System.out.println("exchangedTokenString:" + exchangedTokenString);
logger.debug("exchangedTokenString:" + exchangedTokenString);
oauth.clientId("resource-server");
String tokenResponse = oauth.introspectAccessTokenWithClientCredential("resource-server", "password", exchangedTokenString);
System.out.println("tokenResponse:" + tokenResponse);
oauth.clientId(RESOURCE_SERVER_CLIENT_ID);
String tokenResponse = oauth.introspectAccessTokenWithClientCredential(RESOURCE_SERVER_CLIENT_ID, RESOURCE_SERVER_CLIENT_PASSWORD, exchangedTokenString);
logger.debug("tokenResponse:" + tokenResponse);
assertTokenIntrospectionResponse(JsonSerialization.readValue(tokenResponse, AccessToken.class), true, true, true);
} finally {
deleteProtocolMappers(protocolMappers);
}
}
@Test
public void testPolicyLightWeightFalseTest() throws Exception {
setUseLightweightAccessTokenExecutor();
ProtocolMappersResource protocolMappers = setProtocolMappers(true, true, false, true);
try {
oauth.nonce("123456");
oauth.scope("address");
oauth.clientId(TEST_CLIENT);
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();
assertAccessToken(oauth.verifyToken(accessToken), true, false);
logger.debug("lightweight access token:" + accessToken);
oauth.clientId(RESOURCE_SERVER_CLIENT_ID);
String introspectResponse = oauth.introspectAccessTokenWithClientCredential(RESOURCE_SERVER_CLIENT_ID, RESOURCE_SERVER_CLIENT_PASSWORD, accessToken);
assertTokenIntrospectionResponse(JsonSerialization.readValue(introspectResponse, AccessToken.class), true, true, false);
logger.debug("tokenResponse:" + introspectResponse);
oauth.clientId(TEST_CLIENT);
deletePolicy(POLICY_NAME);
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);
} finally {
deleteProtocolMappers(protocolMappers);
}
}
@Test
public void testPolicyLightWeightTrueTest() throws Exception {
setUseLightweightAccessTokenExecutor();
ProtocolMappersResource protocolMappers = setProtocolMappers(false, true, true, true);
try {
oauth.nonce("123456");
oauth.scope("address");
oauth.clientId(TEST_CLIENT);
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, 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, false);
} finally {
deleteProtocolMappers(protocolMappers);
}
}
private void removeSession(final String sessionId) {
testingClient.testing().removeExpired(REALM_NAME);
try {
testingClient.testing().removeUserSession(REALM_NAME, sessionId);
} catch (NotFoundException nfe) {
// Ignore
}
}
private void assertMapperClaims(AccessToken token, boolean isAddMapperResponseFlag, boolean isAuthCodeFlow) {
if (isAddMapperResponseFlag) {
if (isAuthCodeFlow) {
@ -269,21 +354,21 @@ public class LightWeightAccessTokenTest extends AbstractKeycloakTest {
Assert.assertNotNull(token.getPreferredUsername());
} else {
if (isAuthCodeFlow) {
Assert.assertTrue(token.getName() == null);
Assert.assertTrue(token.getGivenName() == null);
Assert.assertTrue(token.getFamilyName() == null);
Assert.assertTrue(token.getAddress() == null);
Assert.assertTrue(token.getEmail() == null);
Assert.assertTrue(token.getOtherClaims().get("user-session-note") == null);
Assert.assertTrue(token.getOtherClaims().get("test-claim") == null);
Assert.assertTrue(token.getOtherClaims().get("group-name") == null);
Assert.assertNull(token.getName());
Assert.assertNull(token.getGivenName());
Assert.assertNull(token.getFamilyName());
Assert.assertNull(token.getAddress());
Assert.assertNull(token.getEmail());
Assert.assertNull(token.getOtherClaims().get("user-session-note"));
Assert.assertNull(token.getOtherClaims().get("test-claim"));
Assert.assertNull(token.getOtherClaims().get("group-name"));
}
Assert.assertTrue(token.getAcr() == null);
Assert.assertTrue(token.getAllowedOrigins() == null);
Assert.assertTrue(token.getRealmAccess() == null);
Assert.assertNull(token.getAcr());
Assert.assertNull(token.getAllowedOrigins());
Assert.assertNull(token.getRealmAccess());
Assert.assertTrue(token.getResourceAccess().isEmpty());
Assert.assertTrue(token.getEmailVerified() == null);
Assert.assertTrue(token.getPreferredUsername() == null);
Assert.assertNull(token.getEmailVerified());
Assert.assertNull(token.getPreferredUsername());
}
}
@ -296,12 +381,13 @@ public class LightWeightAccessTokenTest extends AbstractKeycloakTest {
Assert.assertNotNull(token.getSessionId());
Assert.assertNotNull(token.getAuth_time());
} else {
Assert.assertTrue(token.getSessionId() == null);
Assert.assertTrue(token.getAuth_time() == null);
Assert.assertNull(token.getSessionId());
Assert.assertNull(token.getAuth_time());
}
Assert.assertNotNull(token.getIssuedFor());
Assert.assertNotNull(token.getScope());
Assert.assertNotNull(token.getIssuer());
Assert.assertNotNull(token.getSubject());
}
private void assertIntrospectClaims(AccessToken token) {
@ -314,7 +400,7 @@ public class LightWeightAccessTokenTest extends AbstractKeycloakTest {
if (isAuthCodeFlow && !exchangeToken) {
Assert.assertNotNull(token.getNonce());
} else {
Assert.assertTrue(token.getNonce() == null);
Assert.assertNull(token.getNonce());
}
}
@ -336,25 +422,25 @@ public class LightWeightAccessTokenTest extends AbstractKeycloakTest {
}
protected RealmResource testRealm() {
return adminClient.realm("test");
return adminClient.realm(REALM_NAME);
}
private void setScopeProtocolMappers(boolean isIncludeAccessToken, boolean isIncludeIntrospection) {
setScopeProtocolMapper(ACR_SCOPE, ACR, isIncludeAccessToken, isIncludeIntrospection);
setScopeProtocolMapper(PROFILE_CLAIM, FULL_NAME, isIncludeAccessToken, isIncludeIntrospection);
setScopeProtocolMapper(EMAIL, EMAIL, isIncludeAccessToken, isIncludeIntrospection);
setScopeProtocolMapper(EMAIL, EMAIL_VERIFIED, isIncludeAccessToken, isIncludeIntrospection);
setScopeProtocolMapper(PROFILE_CLAIM, GIVEN_NAME, isIncludeAccessToken, isIncludeIntrospection);
setScopeProtocolMapper(PROFILE_CLAIM, FAMILY_NAME, isIncludeAccessToken, isIncludeIntrospection);
setScopeProtocolMapper(PROFILE_CLAIM, USERNAME, isIncludeAccessToken, isIncludeIntrospection);
setScopeProtocolMapper(WEB_ORIGINS_SCOPE, ALLOWED_WEB_ORIGINS, isIncludeAccessToken, isIncludeIntrospection);
setScopeProtocolMapper(ROLES_SCOPE, REALM_ROLES, isIncludeAccessToken, isIncludeIntrospection);
setScopeProtocolMapper(ROLES_SCOPE, CLIENT_ROLES, isIncludeAccessToken, isIncludeIntrospection);
setScopeProtocolMapper(ROLES_SCOPE, AUDIENCE_RESOLVE, isIncludeAccessToken, isIncludeIntrospection);
setScopeProtocolMapper(ADDRESS, ADDRESS, isIncludeAccessToken, isIncludeIntrospection);
private void setScopeProtocolMappers(boolean isIncludeAccessToken, boolean isIncludeIntrospection, boolean isIncludeLightweightAccessToken) {
setScopeProtocolMapper(ACR_SCOPE, ACR, isIncludeAccessToken, isIncludeIntrospection, isIncludeLightweightAccessToken);
setScopeProtocolMapper(PROFILE_CLAIM, FULL_NAME, isIncludeAccessToken, isIncludeIntrospection, isIncludeLightweightAccessToken);
setScopeProtocolMapper(EMAIL, EMAIL, isIncludeAccessToken, isIncludeIntrospection, isIncludeLightweightAccessToken);
setScopeProtocolMapper(EMAIL, EMAIL_VERIFIED, isIncludeAccessToken, isIncludeIntrospection, isIncludeLightweightAccessToken);
setScopeProtocolMapper(PROFILE_CLAIM, GIVEN_NAME, isIncludeAccessToken, isIncludeIntrospection, isIncludeLightweightAccessToken);
setScopeProtocolMapper(PROFILE_CLAIM, FAMILY_NAME, isIncludeAccessToken, isIncludeIntrospection, isIncludeLightweightAccessToken);
setScopeProtocolMapper(PROFILE_CLAIM, USERNAME, isIncludeAccessToken, isIncludeIntrospection, isIncludeLightweightAccessToken);
setScopeProtocolMapper(WEB_ORIGINS_SCOPE, ALLOWED_WEB_ORIGINS, isIncludeAccessToken, isIncludeIntrospection, isIncludeLightweightAccessToken);
setScopeProtocolMapper(ROLES_SCOPE, REALM_ROLES, isIncludeAccessToken, isIncludeIntrospection, isIncludeLightweightAccessToken);
setScopeProtocolMapper(ROLES_SCOPE, CLIENT_ROLES, isIncludeAccessToken, isIncludeIntrospection, isIncludeLightweightAccessToken);
setScopeProtocolMapper(ROLES_SCOPE, AUDIENCE_RESOLVE, isIncludeAccessToken, isIncludeIntrospection, isIncludeLightweightAccessToken);
setScopeProtocolMapper(ADDRESS, ADDRESS, isIncludeAccessToken, isIncludeIntrospection, isIncludeLightweightAccessToken);
}
private void setScopeProtocolMapper(String scopeName, String mapperName, boolean isIncludeAccessToken, boolean isIncludeIntrospection) {
private void setScopeProtocolMapper(String scopeName, String mapperName, boolean isIncludeAccessToken, boolean isIncludeIntrospection, boolean isIncludeLightweightAccessToken) {
ClientScopeResource scope = ApiUtil.findClientScopeByName(testRealm(), scopeName);
ProtocolMapperRepresentation protocolMapper = ApiUtil.findProtocolMapperByName(scope, mapperName);
Map<String, String> config = protocolMapper.getConfig();
@ -368,31 +454,49 @@ public class LightWeightAccessTokenTest extends AbstractKeycloakTest {
} else {
config.put(INCLUDE_IN_INTROSPECTION, "false");
}
if (isIncludeLightweightAccessToken) {
config.put(INCLUDE_IN_LIGHTWEIGHT_ACCESS_TOKEN, "true");
} else {
config.put(INCLUDE_IN_LIGHTWEIGHT_ACCESS_TOKEN, "false");
}
scope.getProtocolMappers().update(protocolMapper.getId(), protocolMapper);
}
private ProtocolMappersResource setProtocolMappers(boolean isIncludeAccessToken, boolean isIncludeIntrospection, boolean setPairWise) {
setScopeProtocolMappers(isIncludeAccessToken, isIncludeIntrospection);
setScopeProtocolMappers(isIncludeAccessToken, isIncludeIntrospection, false);
List<ProtocolMapperRepresentation> protocolMapperList = new ArrayList<>();
setExistingProtocolMappers(protocolMapperList, isIncludeAccessToken, isIncludeIntrospection, setPairWise);
ProtocolMappersResource protocolMappers = ApiUtil.findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers();
setExistingProtocolMappers(protocolMapperList, isIncludeAccessToken, isIncludeIntrospection, false, setPairWise);
ProtocolMappersResource protocolMappers = ApiUtil.findClientResourceByClientId(adminClient.realm(REALM_NAME), TEST_CLIENT).getProtocolMappers();
protocolMappers.createMapper(protocolMapperList);
return protocolMappers;
}
private void setExistingProtocolMappers(List<ProtocolMapperRepresentation> protocolMapperList, boolean isIncludeAccessToken, boolean isIncludeIntrospection, boolean setPairWise) {
private ProtocolMappersResource setProtocolMappers(boolean isIncludeAccessToken, boolean isIncludeIntrospection, boolean isIncludeLightweightAccessToken, boolean setPairWise) {
setScopeProtocolMappers(isIncludeAccessToken, isIncludeIntrospection, isIncludeLightweightAccessToken);
List<ProtocolMapperRepresentation> protocolMapperList = new ArrayList<>();
setExistingProtocolMappers(protocolMapperList, isIncludeAccessToken, isIncludeIntrospection, isIncludeLightweightAccessToken, setPairWise);
ProtocolMappersResource protocolMappers = ApiUtil.findClientResourceByClientId(adminClient.realm(REALM_NAME), TEST_CLIENT).getProtocolMappers();
protocolMappers.createMapper(protocolMapperList);
return protocolMappers;
}
private void setExistingProtocolMappers(List<ProtocolMapperRepresentation> protocolMapperList, boolean isIncludeAccessToken, boolean isIncludeIntrospection, boolean isIncludeLightweightAccessToken, boolean setPairWise) {
Map<String, String> config = new HashMap<>();
if (isIncludeAccessToken) {
config.put(INCLUDE_IN_ACCESS_TOKEN, "true");
} else {
config.put(INCLUDE_IN_ACCESS_TOKEN, "false");
}
if (isIncludeIntrospection) {
config.put(INCLUDE_IN_INTROSPECTION, "true");
} else {
config.put(INCLUDE_IN_INTROSPECTION, "false");
}
if (isIncludeLightweightAccessToken) {
config.put(INCLUDE_IN_LIGHTWEIGHT_ACCESS_TOKEN, "true");
} else {
config.put(INCLUDE_IN_LIGHTWEIGHT_ACCESS_TOKEN, "false");
}
ProtocolMapperRepresentation audienceProtocolMapper = createClaimMapper("audience", AudienceProtocolMapper.PROVIDER_ID, new HashMap<>(config) {{
put(INCLUDED_CLIENT_AUDIENCE, "account-console");
@ -469,4 +573,24 @@ public class LightWeightAccessTokenTest extends AbstractKeycloakTest {
this.tokenResponse = tokenResponse;
}
}
private void setUseLightweightAccessTokenExecutor() throws Exception {
// register profiles
String json = (new ClientPoliciesUtil.ClientProfilesBuilder()).addProfile(
(new ClientPoliciesUtil.ClientProfileBuilder()).createProfile(PROFILE_NAME, "Use Lightweight Access Token")
.addExecutor(UseLightweightAccessTokenExecutorFactory.PROVIDER_ID, null)
.toRepresentation()
).toString();
updateProfiles(json);
// register policies
json = (new ClientPoliciesUtil.ClientPoliciesBuilder()).addPolicy(
(new ClientPoliciesUtil.ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Use Lightweight Access Token Policy", Boolean.TRUE)
.addCondition(AnyClientConditionFactory.PROVIDER_ID,
createAnyClientConditionConfig())
.addProfile(PROFILE_NAME)
.toRepresentation()
).toString();
updatePolicies(json);
}
}