Add sub via protocol mapper to access token
Closes #21185 Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
This commit is contained in:
parent
a34c10ea40
commit
c76cbc94d8
14 changed files with 263 additions and 36 deletions
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue