KEYCLOAK-18982 Token OIDC introspection endpoint should not update any of the timestamps
This commit is contained in:
parent
c49c7d0ffc
commit
b42f765c2a
4 changed files with 32 additions and 11 deletions
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue