KEYCLOAK-18982 Token OIDC introspection endpoint should not update any of the timestamps

This commit is contained in:
Martin Kanis 2021-08-05 11:26:47 +02:00 committed by Hynek Mlnařík
parent c49c7d0ffc
commit b42f765c2a
4 changed files with 32 additions and 11 deletions

View file

@ -18,7 +18,6 @@
package org.keycloak.protocol.oidc;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.keycloak.OAuthErrorException;
import org.keycloak.TokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.crypto.SignatureProvider;
@ -32,7 +31,6 @@ import org.keycloak.util.JsonSerialization;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -80,7 +78,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
}
}
protected AccessToken verifyAccessToken(String token) throws OAuthErrorException, IOException {
protected AccessToken verifyAccessToken(String token) {
AccessToken accessToken;
try {
@ -97,7 +95,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
RealmModel realm = this.session.getContext().getRealm();
return tokenManager.checkTokenValidForIntrospection(session, realm, accessToken) ? accessToken : null;
return tokenManager.checkTokenValidForIntrospection(session, realm, accessToken, false) ? accessToken : null;
}
@Override

View file

@ -67,7 +67,6 @@ import org.keycloak.representations.RefreshToken;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.managers.UserSessionCrossDCManager;
import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.services.resources.IdentityBrokerService;
@ -218,16 +217,16 @@ public class TokenManager {
}
/**
* Checks if the token is valid. Intended usage is for token introspection endpoints as the session last refresh
* is updated if the token was valid. This is used to keep the session alive when long lived tokens are used.
* Checks if the token is valid. Optionally the session last refresh and client session timestamp
* are updated if the token was valid. This is used to keep the session alive when long lived tokens are used.
*
* @param session
* @param realm
* @param token
* @param updateTimestamps
* @return
* @throws OAuthErrorException
*/
public boolean checkTokenValidForIntrospection(KeycloakSession session, RealmModel realm, AccessToken token) throws OAuthErrorException {
public boolean checkTokenValidForIntrospection(KeycloakSession session, RealmModel realm, AccessToken token, boolean updateTimestamps) {
ClientModel client = realm.getClientByClientId(token.getIssuedFor());
if (client == null || !client.isEnabled()) {
return false;
@ -283,7 +282,7 @@ public class TokenManager {
return false;
}
if (valid) {
if (updateTimestamps && valid) {
int currentTime = Time.currentTime();
userSession.setLastSessionRefresh(currentTime);
if (clientSession != null) {

View file

@ -105,7 +105,7 @@ public class OpenShiftTokenReviewEndpoint implements OIDCExtProvider, Environmen
error(401, Errors.INVALID_TOKEN, "Token verification failure");
}
if (!tokenManager.checkTokenValidForIntrospection(session, realm, token)) {
if (!tokenManager.checkTokenValidForIntrospection(session, realm, token, true)) {
error(401, Errors.INVALID_TOKEN, "Token verification failure");
}

View file

@ -314,6 +314,8 @@ public class TokenIntrospectionTest extends AbstractTestRealmKeycloakTest {
AbstractOIDCScopeTest.assertScopes("openid email profile", rep.getScope());
}
@Test
public void testIntrospectAccessTokenES256() throws Exception {
testIntrospectAccessToken(Algorithm.ES256);
@ -398,6 +400,28 @@ public class TokenIntrospectionTest extends AbstractTestRealmKeycloakTest {
assertEquals("test-app", rep.getClientId());
}
@Test
public void testIntrospectDoesntExtendTokenLifespan() throws Exception {
oauth.doLogin("test-user@localhost", "password");
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code, "password");
accessTokenResponse = oauth.doRefreshTokenRequest(accessTokenResponse.getRefreshToken(), "password");
setTimeOffset(1200);
String tokenResponse = oauth.introspectRefreshTokenWithClientCredential("confidential-cli", "secret1", accessTokenResponse.getRefreshToken());
TokenMetadataRepresentation rep = JsonSerialization.readValue(tokenResponse, TokenMetadataRepresentation.class);
assertTrue(rep.isActive());
assertEquals("test-user@localhost", rep.getUserName());
assertEquals("test-app", rep.getClientId());
setTimeOffset(1200 + 1200);
accessTokenResponse = oauth.doRefreshTokenRequest(accessTokenResponse.getRefreshToken(), "password");
assertEquals(400, accessTokenResponse.getStatusCode());
assertEquals("Token is not active", accessTokenResponse.getErrorDescription());
}
@Test
public void testIntrospectAccessTokenUserDisabled() throws Exception {