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

View file

@ -67,7 +67,6 @@ import org.keycloak.representations.RefreshToken;
import org.keycloak.services.ErrorResponseException; import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AuthenticationSessionManager; import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.managers.UserSessionCrossDCManager; import org.keycloak.services.managers.UserSessionCrossDCManager;
import org.keycloak.services.managers.UserSessionManager; import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.services.resources.IdentityBrokerService; 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 * Checks if the token is valid. Optionally the session last refresh and client session timestamp
* is updated if the token was valid. This is used to keep the session alive when long lived tokens are used. * are updated if the token was valid. This is used to keep the session alive when long lived tokens are used.
* *
* @param session * @param session
* @param realm * @param realm
* @param token * @param token
* @param updateTimestamps
* @return * @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()); ClientModel client = realm.getClientByClientId(token.getIssuedFor());
if (client == null || !client.isEnabled()) { if (client == null || !client.isEnabled()) {
return false; return false;
@ -283,7 +282,7 @@ public class TokenManager {
return false; return false;
} }
if (valid) { if (updateTimestamps && valid) {
int currentTime = Time.currentTime(); int currentTime = Time.currentTime();
userSession.setLastSessionRefresh(currentTime); userSession.setLastSessionRefresh(currentTime);
if (clientSession != null) { if (clientSession != null) {

View file

@ -105,7 +105,7 @@ public class OpenShiftTokenReviewEndpoint implements OIDCExtProvider, Environmen
error(401, Errors.INVALID_TOKEN, "Token verification failure"); 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"); 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()); AbstractOIDCScopeTest.assertScopes("openid email profile", rep.getScope());
} }
@Test @Test
public void testIntrospectAccessTokenES256() throws Exception { public void testIntrospectAccessTokenES256() throws Exception {
testIntrospectAccessToken(Algorithm.ES256); testIntrospectAccessToken(Algorithm.ES256);
@ -398,6 +400,28 @@ public class TokenIntrospectionTest extends AbstractTestRealmKeycloakTest {
assertEquals("test-app", rep.getClientId()); 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 @Test
public void testIntrospectAccessTokenUserDisabled() throws Exception { public void testIntrospectAccessTokenUserDisabled() throws Exception {