KEYCLOAK-2722 Check user session in token introspection endpoint
This commit is contained in:
parent
cee8fee504
commit
55c5e9a381
4 changed files with 117 additions and 3 deletions
|
@ -174,8 +174,31 @@ public class TokenManager {
|
||||||
verifyAccess(oldToken, newToken);
|
verifyAccess(oldToken, newToken);
|
||||||
|
|
||||||
return new TokenValidation(user, userSession, clientSession, newToken);
|
return new TokenValidation(user, userSession, clientSession, newToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTokenValid(KeycloakSession session, RealmModel realm, AccessToken token) throws OAuthErrorException {
|
||||||
|
if (!token.isActive()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.getIssuedAt() < realm.getNotBefore()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserModel user = session.users().getUserById(token.getSubject(), realm);
|
||||||
|
if (user == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!user.isEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
|
||||||
|
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RefreshResult refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel authorizedClient, String encodedRefreshToken, EventBuilder event, HttpHeaders headers) throws OAuthErrorException {
|
public RefreshResult refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel authorizedClient, String encodedRefreshToken, EventBuilder event, HttpHeaders headers) throws OAuthErrorException {
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
|
import org.keycloak.protocol.oidc.TokenManager.TokenValidation;
|
||||||
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.services.ErrorResponseException;
|
import org.keycloak.services.ErrorResponseException;
|
||||||
|
@ -106,7 +107,8 @@ public class TokenIntrospectionEndpoint {
|
||||||
AccessToken toIntrospect = toAccessToken(tokenTypeHint, token);
|
AccessToken toIntrospect = toAccessToken(tokenTypeHint, token);
|
||||||
ObjectNode tokenMetadata;
|
ObjectNode tokenMetadata;
|
||||||
|
|
||||||
if (toIntrospect.isActive()) {
|
boolean active = tokenManager.isTokenValid(session, realm, toIntrospect);
|
||||||
|
if (active) {
|
||||||
tokenMetadata = JsonSerialization.createObjectNode(toIntrospect);
|
tokenMetadata = JsonSerialization.createObjectNode(toIntrospect);
|
||||||
tokenMetadata.put("client_id", toIntrospect.getIssuedFor());
|
tokenMetadata.put("client_id", toIntrospect.getIssuedFor());
|
||||||
tokenMetadata.put("username", toIntrospect.getPreferredUsername());
|
tokenMetadata.put("username", toIntrospect.getPreferredUsername());
|
||||||
|
@ -114,7 +116,7 @@ public class TokenIntrospectionEndpoint {
|
||||||
tokenMetadata = JsonSerialization.createObjectNode();
|
tokenMetadata = JsonSerialization.createObjectNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenMetadata.put("active", toIntrospect.isActive());
|
tokenMetadata.put("active", active);
|
||||||
|
|
||||||
this.event.success();
|
this.event.success();
|
||||||
|
|
||||||
|
|
|
@ -166,7 +166,7 @@ public class UsersResource {
|
||||||
attrsToRemove = Collections.emptySet();
|
attrsToRemove = Collections.emptySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rep.isEnabled() != null && rep.isEnabled()) {
|
if (rep.isEnabled() != null && rep.isEnabled() && rep.getUsername() != null) {
|
||||||
UsernameLoginFailureModel failureModel = session.sessions().getUserLoginFailure(realm, rep.getUsername().toLowerCase());
|
UsernameLoginFailureModel failureModel = session.sessions().getUserLoginFailure(realm, rep.getUsername().toLowerCase());
|
||||||
if (failureModel != null) {
|
if (failureModel != null) {
|
||||||
failureModel.clearFailures();
|
failureModel.clearFailures();
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.admin.client.Keycloak;
|
import org.keycloak.admin.client.Keycloak;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.events.Event;
|
import org.keycloak.events.Event;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
@ -31,6 +32,7 @@ import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.representations.oidc.TokenMetadataRepresentation;
|
import org.keycloak.representations.oidc.TokenMetadataRepresentation;
|
||||||
import org.keycloak.services.managers.ClientManager;
|
import org.keycloak.services.managers.ClientManager;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
@ -41,6 +43,7 @@ import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
import org.keycloak.testsuite.rule.WebResource;
|
import org.keycloak.testsuite.rule.WebResource;
|
||||||
import org.keycloak.testsuite.rule.WebRule;
|
import org.keycloak.testsuite.rule.WebRule;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
@ -207,4 +210,90 @@ public class TokenIntrospectionTest {
|
||||||
|
|
||||||
events.clear();
|
events.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIntrospectAccessToken() throws Exception {
|
||||||
|
oauth.doLogin("test-user@localhost", "password");
|
||||||
|
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||||
|
Event loginEvent = events.expectLogin().assertEvent();
|
||||||
|
AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||||
|
String tokenResponse = oauth.introspectAccessTokenWithClientCredential("confidential-cli", "secret1", accessTokenResponse.getAccessToken());
|
||||||
|
TokenMetadataRepresentation rep = JsonSerialization.readValue(tokenResponse, TokenMetadataRepresentation.class);
|
||||||
|
|
||||||
|
assertTrue(rep.isActive());
|
||||||
|
assertEquals("test-user@localhost", rep.getUserName());
|
||||||
|
assertEquals("test-app", rep.getClientId());
|
||||||
|
assertEquals(loginEvent.getUserId(), rep.getSubject());
|
||||||
|
|
||||||
|
events.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIntrospectAccessTokenSessionInvalid() throws Exception {
|
||||||
|
oauth.doLogin("test-user@localhost", "password");
|
||||||
|
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||||
|
AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||||
|
oauth.doLogout(accessTokenResponse.getRefreshToken(), "password");
|
||||||
|
|
||||||
|
String tokenResponse = oauth.introspectAccessTokenWithClientCredential("confidential-cli", "secret1", accessTokenResponse.getAccessToken());
|
||||||
|
TokenMetadataRepresentation rep = JsonSerialization.readValue(tokenResponse, TokenMetadataRepresentation.class);
|
||||||
|
|
||||||
|
assertFalse(rep.isActive());
|
||||||
|
assertNull(rep.getUserName());
|
||||||
|
assertNull(rep.getClientId());
|
||||||
|
assertNull(rep.getSubject());
|
||||||
|
|
||||||
|
events.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIntrospectAccessTokenUserDisabled() throws Exception {
|
||||||
|
oauth.doLogin("test-user@localhost", "password");
|
||||||
|
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||||
|
AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||||
|
|
||||||
|
Event loginEvent = events.expectLogin().assertEvent();
|
||||||
|
|
||||||
|
UserRepresentation userRep = new UserRepresentation();
|
||||||
|
try {
|
||||||
|
userRep.setEnabled(false);
|
||||||
|
keycloak.realm(oauth.getRealm()).users().get(loginEvent.getUserId()).update(userRep);
|
||||||
|
|
||||||
|
String tokenResponse = oauth.introspectAccessTokenWithClientCredential("confidential-cli", "secret1", accessTokenResponse.getAccessToken());
|
||||||
|
TokenMetadataRepresentation rep = JsonSerialization.readValue(tokenResponse, TokenMetadataRepresentation.class);
|
||||||
|
|
||||||
|
assertFalse(rep.isActive());
|
||||||
|
assertNull(rep.getUserName());
|
||||||
|
assertNull(rep.getClientId());
|
||||||
|
assertNull(rep.getSubject());
|
||||||
|
|
||||||
|
events.clear();
|
||||||
|
} finally {
|
||||||
|
userRep.setEnabled(true);
|
||||||
|
keycloak.realm(oauth.getRealm()).users().get(loginEvent.getUserId()).update(userRep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIntrospectAccessTokenExpired() throws Exception {
|
||||||
|
oauth.doLogin("test-user@localhost", "password");
|
||||||
|
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||||
|
AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Time.setOffset(keycloak.realm(oauth.getRealm()).toRepresentation().getAccessTokenLifespan() + 1);
|
||||||
|
|
||||||
|
String tokenResponse = oauth.introspectAccessTokenWithClientCredential("confidential-cli", "secret1", accessTokenResponse.getAccessToken());
|
||||||
|
TokenMetadataRepresentation rep = JsonSerialization.readValue(tokenResponse, TokenMetadataRepresentation.class);
|
||||||
|
|
||||||
|
assertFalse(rep.isActive());
|
||||||
|
assertNull(rep.getUserName());
|
||||||
|
assertNull(rep.getClientId());
|
||||||
|
assertNull(rep.getSubject());
|
||||||
|
|
||||||
|
events.clear();
|
||||||
|
} finally {
|
||||||
|
Time.setOffset(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue