diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AbstractAuthorizationGrant.java b/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AbstractAuthorizationGrant.java index a5527a51f9d..553bde0efef 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AbstractAuthorizationGrant.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AbstractAuthorizationGrant.java @@ -72,6 +72,7 @@ public abstract class AbstractAuthorizationGrant implements IAuthorizationGrant private String codeChallengeMethod; private String claims; private String dpopJkt; + private String referenceId; private String acrValues; private String sessionDn; @@ -98,6 +99,14 @@ protected void init(User user, AuthorizationGrantType authorizationGrantType, Cl this.grantId = UUID.randomUUID().toString(); } + public String getReferenceId() { + return referenceId; + } + + public void setReferenceId(String referenceId) { + this.referenceId = referenceId; + } + public String getDpopJkt() { return dpopJkt; } @@ -340,6 +349,7 @@ public AccessToken createAccessToken(ExecutionContext executionContext) { accessToken.setSessionDn(getSessionDn()); accessToken.setX5ts256(CertUtils.confirmationMethodHashS256(executionContext.getCertAsPem())); + accessToken.setReferenceId(executionContext.getTokenReferenceId()); final String dpop = executionContext.getDpop(); if (StringUtils.isNoneBlank(dpop)) { @@ -352,6 +362,8 @@ public AccessToken createAccessToken(ExecutionContext executionContext) { @Override public RefreshToken createRefreshToken(ExecutionContext context) { + context.generateRandomTokenReferenceId(); + int lifetime = appConfiguration.getRefreshTokenLifetime(); if (client.getRefreshTokenLifetime() != null && client.getRefreshTokenLifetime() > 0) { lifetime = client.getRefreshTokenLifetime(); @@ -361,15 +373,20 @@ public RefreshToken createRefreshToken(ExecutionContext context) { refreshToken.setSessionDn(getSessionDn()); refreshToken.setDpop(context.getDpop()); + refreshToken.setReferenceId(context.getTokenReferenceId()); + return refreshToken; } @Override public RefreshToken createRefreshToken(ExecutionContext context, int lifetime) { + context.generateRandomTokenReferenceId(); + RefreshToken refreshToken = new RefreshToken(lifetime); refreshToken.setSessionDn(getSessionDn()); refreshToken.setDpop(context.getDpop()); + refreshToken.setReferenceId(context.getTokenReferenceId()); return refreshToken; } diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AbstractToken.java b/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AbstractToken.java index 8d00a61930c..376231b0c51 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AbstractToken.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AbstractToken.java @@ -8,7 +8,6 @@ import io.jans.as.model.crypto.signature.SignatureAlgorithm; import io.jans.as.model.util.HashUtil; -import io.jans.as.model.util.Util; import io.jans.as.server.model.token.HandleTokenFactory; import io.jans.as.server.util.ServerUtil; import io.jans.orm.annotation.AttributeName; @@ -56,6 +55,9 @@ public abstract class AbstractToken implements Serializable, Deletable { @AttributeName(name = "dpop") private String dpop; + @AttributeName(name = "jansId") + private String referenceId; + @Expiration private int ttl; @@ -212,6 +214,24 @@ public synchronized void setRevoked(boolean revoked) { this.revoked = revoked; } + /** + * Gets reference id + * + * @return reference id + */ + public String getReferenceId() { + return referenceId; + } + + /** + * Sets reference id + * + * @param referenceId reference id + */ + public void setReferenceId(String referenceId) { + this.referenceId = referenceId; + } + /** * Return true if the token has expired. * diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AuthorizationGrant.java b/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AuthorizationGrant.java index ac382ca845a..96f1738fb67 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AuthorizationGrant.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AuthorizationGrant.java @@ -122,6 +122,7 @@ private IdToken createIdTokenInternal(AuthorizationCode authorizationCode, Acces JsonWebResponse jwr = idTokenFactory.createJwr(this, authorizationCode, accessToken, refreshToken, executionContext); final IdToken idToken = new IdToken(jwr.toString(), jwr.getClaims().getClaimAsDate(JwtClaimName.ISSUED_AT), jwr.getClaims().getClaimAsDate(JwtClaimName.EXPIRATION_TIME)); + idToken.setReferenceId(executionContext.getTokenReferenceId()); if (log.isTraceEnabled()) log.trace("Created id_token: {}", idToken.getCode()); return idToken; @@ -202,6 +203,7 @@ private void initTokenFromGrant(TokenEntity token) { public AccessToken createAccessToken(ExecutionContext context) { try { context.initFromGrantIfNeeded(this); + context.generateRandomTokenReferenceId(); final AccessToken accessToken = super.createAccessToken(context); if (accessToken.getExpiresIn() < 0) { @@ -282,6 +284,7 @@ public JwtSigner createAccessTokenAsJwt(AccessToken accessToken, ExecutionContex jwt.getClaims().setIssuedAt(accessToken.getCreationDate()); jwt.getClaims().setSubjectIdentifier(getSub()); jwt.getClaims().setClaim("x5t#S256", accessToken.getX5ts256()); + jwt.getClaims().setClaim("jti", context.getTokenReferenceId()); final AuthzDetails authzDetails = getAuthzDetails(); if (!AuthzDetails.isEmpty(authzDetails)) { @@ -401,6 +404,7 @@ public IdToken createIdToken( executionContext.setClaimsAsString(getClaims()); executionContext.setNonce(nonce); executionContext.setState(state); + executionContext.generateRandomTokenReferenceId(); final IdToken idToken = createIdTokenInternal(authorizationCode, accessToken, refreshToken, executionContext); final AuthorizationGrant grant = executionContext.getGrant(); @@ -488,6 +492,7 @@ public TokenEntity asTokenEntity(AbstractToken token) { result.setUserId(getUserId()); result.setUserDn(getUserDn()); result.setClientId(getClientId()); + result.setReferenceId(token.getReferenceId()); result.getAttributes().setX5cs256(token.getX5ts256()); result.getAttributes().setDpopJkt(getDpopJkt()); diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AuthorizationGrantList.java b/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AuthorizationGrantList.java index 8a3158713cf..7c854b097c6 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AuthorizationGrantList.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AuthorizationGrantList.java @@ -275,6 +275,18 @@ public AuthorizationGrant getAuthorizationGrantByIdToken(String idToken) { return null; } + @Override + public AuthorizationGrant getAuthorizationGrantByReferenceId(String referenceId) { + if (StringUtils.isBlank(referenceId)) { + return null; + } + final TokenEntity tokenEntity = grantService.getGrantByReferenceId(referenceId); + if (tokenEntity != null) { + return asGrant(tokenEntity); + } + return null; + } + public AuthorizationGrant asGrant(TokenEntity tokenEntity) { if (tokenEntity != null) { final AuthorizationGrantType grantType = AuthorizationGrantType.fromString(tokenEntity.getGrantType()); @@ -353,6 +365,7 @@ public AuthorizationGrant asGrant(TokenEntity tokenEntity) { result.setX5ts256(tokenEntity.getAttributes().getX5cs256()); result.setDpopJkt(tokenEntity.getAttributes().getDpopJkt()); result.setTokenEntity(tokenEntity); + result.setReferenceId(tokenEntity.getReferenceId()); if (StringUtils.isNotBlank(grantId)) { result.setGrantId(grantId); } @@ -381,34 +394,40 @@ public AuthorizationGrant asGrant(TokenEntity tokenEntity) { final AuthorizationCode code = new AuthorizationCode(tokenEntity.getTokenCode(), tokenEntity.getCreationDate(), tokenEntity.getExpirationDate()); final AuthorizationCodeGrant g = (AuthorizationCodeGrant) result; code.setX5ts256(g.getX5ts256()); + code.setReferenceId(tokenEntity.getReferenceId()); g.setAuthorizationCode(code); } break; case REFRESH_TOKEN: final RefreshToken refreshToken = new RefreshToken(tokenEntity.getTokenCode(), tokenEntity.getCreationDate(), tokenEntity.getExpirationDate()); refreshToken.setX5ts256(result.getX5ts256()); + refreshToken.setReferenceId(tokenEntity.getReferenceId()); result.setRefreshTokens(Collections.singletonList(refreshToken)); break; case ACCESS_TOKEN: final AccessToken accessToken = new AccessToken(tokenEntity.getTokenCode(), tokenEntity.getCreationDate(), tokenEntity.getExpirationDate()); accessToken.setDpop(tokenEntity.getDpop()); accessToken.setX5ts256(result.getX5ts256()); + accessToken.setReferenceId(tokenEntity.getReferenceId()); result.setAccessTokens(Collections.singletonList(accessToken)); break; case TX_TOKEN: final TxToken txToken = new TxToken(tokenEntity.getTokenCode(), tokenEntity.getCreationDate(), tokenEntity.getExpirationDate()); txToken.setDpop(tokenEntity.getDpop()); txToken.setX5ts256(result.getX5ts256()); + txToken.setReferenceId(tokenEntity.getReferenceId()); result.setTxTokens(Collections.singletonList(txToken)); break; case ID_TOKEN: final IdToken idToken = new IdToken(tokenEntity.getTokenCode(), tokenEntity.getCreationDate(), tokenEntity.getExpirationDate()); idToken.setX5ts256(result.getX5ts256()); + idToken.setReferenceId(tokenEntity.getReferenceId()); result.setIdToken(idToken); break; case LONG_LIVED_ACCESS_TOKEN: final AccessToken longLivedAccessToken = new AccessToken(tokenEntity.getTokenCode(), tokenEntity.getCreationDate(), tokenEntity.getExpirationDate()); longLivedAccessToken.setX5ts256(result.getX5ts256()); + longLivedAccessToken.setReferenceId(tokenEntity.getReferenceId()); result.setLongLivedAccessToken(longLivedAccessToken); break; } diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/ExecutionContext.java b/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/ExecutionContext.java index 93e7e3f7c16..94c4b272fe7 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/ExecutionContext.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/ExecutionContext.java @@ -18,6 +18,7 @@ import io.jans.as.server.model.audit.OAuth2AuditLog; import io.jans.model.custom.script.conf.CustomScriptConfiguration; import io.jans.model.token.TokenEntity; +import io.jans.util.IdUtil; import jakarta.faces.context.ExternalContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -65,6 +66,7 @@ public class ExecutionContext { private String nonce; private String state; + private String tokenReferenceId = IdUtil.randomShortUUID(); private boolean includeIdTokenClaims; @@ -158,6 +160,19 @@ public static ExecutionContext of(ExecutionContext context) { return executionContext; } + public String generateRandomTokenReferenceId() { + tokenReferenceId = IdUtil.randomShortUUID(); + return tokenReferenceId; + } + + public String getTokenReferenceId() { + return tokenReferenceId; + } + + public void setTokenReferenceId(String tokenReferenceId) { + this.tokenReferenceId = tokenReferenceId; + } + public ExecutionContext copy() { return of(this); } diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/IAuthorizationGrantList.java b/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/IAuthorizationGrantList.java index 0e3589655cf..32ed6363cd2 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/IAuthorizationGrantList.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/IAuthorizationGrantList.java @@ -47,6 +47,8 @@ public interface IAuthorizationGrantList { AuthorizationGrant getAuthorizationGrantByIdToken(String idToken); + AuthorizationGrant getAuthorizationGrantByReferenceId(String idToken); + CIBAGrant getCIBAGrant(String authReqId); DeviceCodeGrant createDeviceGrant(DeviceAuthorizationCacheControl data, User user); diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/model/token/IdTokenFactory.java b/jans-auth-server/server/src/main/java/io/jans/as/server/model/token/IdTokenFactory.java index b665a6608b0..ac16c678af9 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/model/token/IdTokenFactory.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/model/token/IdTokenFactory.java @@ -148,7 +148,7 @@ private void fillClaims(JsonWebResponse jwr, jwr.getClaims().setExpirationTime(expiration); jwr.getClaims().setIssuedAt(issuedAt); - jwr.setClaim("random", UUID.randomUUID().toString()); // provided uniqueness of id_token for same RP requests, oxauth: 1493 + jwr.setClaim("jti", executionContext.getTokenReferenceId()); // provided uniqueness of id_token for same RP requests, oxauth: 1493 if (executionContext.getPreProcessing() != null) { executionContext.getPreProcessing().apply(jwr); diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/GrantService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/GrantService.java index 40d32f0e667..2e0565dda24 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/service/GrantService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/GrantService.java @@ -207,6 +207,22 @@ public TokenEntity getGrantByCode(String code) { } } + public TokenEntity getGrantByReferenceId(String referenceId) { + try { + final List grants = persistenceEntryManager.findEntries(tokenBaseDn(), TokenEntity.class, Filter.createEqualityFilter("jansId", referenceId)); + if (grants.size() > 1) { + log.error("Found more then one tokens by referenceId {}", referenceId); + return null; + } + if (grants.size() == 1) { + return grants.get(0); + } + } catch (Exception e) { + logException(e); + } + return null; + } + private void logException(Exception e) { if (isTrue(appConfiguration.getLogNotFoundEntityAsError())) { log.error(e.getMessage(), e); diff --git a/jans-core/service/src/main/java/io/jans/model/token/TokenEntity.java b/jans-core/service/src/main/java/io/jans/model/token/TokenEntity.java index d7374065eed..5b8cc5d2469 100644 --- a/jans-core/service/src/main/java/io/jans/model/token/TokenEntity.java +++ b/jans-core/service/src/main/java/io/jans/model/token/TokenEntity.java @@ -6,16 +6,11 @@ package io.jans.model.token; +import io.jans.orm.annotation.*; + import java.io.Serializable; import java.util.Date; -import io.jans.orm.annotation.AttributeName; -import io.jans.orm.annotation.DN; -import io.jans.orm.annotation.DataEntry; -import io.jans.orm.annotation.Expiration; -import io.jans.orm.annotation.JsonObject; -import io.jans.orm.annotation.ObjectClass; - /** * @author Yuriy Zabrovarnyy * @author Javier Rojas Blum @@ -68,6 +63,8 @@ public class TokenEntity implements Serializable { private String claims; @AttributeName(name = "tknBndCnf") private String tokenBindingHash; + @AttributeName(name = "jansId") + private String referenceId; @AttributeName(name = "acr") private String authMode; @@ -84,6 +81,14 @@ public class TokenEntity implements Serializable { @AttributeName(name = "dpop") private String dpop; + public String getReferenceId() { + return referenceId; + } + + public void setReferenceId(String referenceId) { + this.referenceId = referenceId; + } + public TokenAttributes getAttributes() { if (attributes == null) { attributes = new TokenAttributes(); diff --git a/jans-core/util/src/main/java/io/jans/util/IdUtil.java b/jans-core/util/src/main/java/io/jans/util/IdUtil.java new file mode 100644 index 00000000000..035a2b82f0b --- /dev/null +++ b/jans-core/util/src/main/java/io/jans/util/IdUtil.java @@ -0,0 +1,34 @@ +package io.jans.util; + +import java.nio.ByteBuffer; +import java.util.Base64; +import java.util.Random; +import java.util.UUID; + +/** + * @author Yuriy Z + */ +public class IdUtil { + + // we are ok to have not secured random + private static final Random RANDOM = new Random(); + + private IdUtil() { + } + + public static String randomShortUUID() { + return toShortUUID(UUID.randomUUID()); + } + + public static String toShortUUID(UUID uuid) { + ByteBuffer byteBuffer = ByteBuffer.allocate(16); + byteBuffer.putLong(uuid.getMostSignificantBits()); + byteBuffer.putLong(uuid.getLeastSignificantBits()); + return Base64.getEncoder().withoutPadding().encodeToString(byteBuffer.array()) + .replace("/", randomChar()).replace("+", randomChar()); + } + + private static String randomChar() { + return (char) (RANDOM.nextInt(26) + 'a') + ""; + } +} diff --git a/jans-core/util/src/test/java/io/jans/util/IdUtilTest.java b/jans-core/util/src/test/java/io/jans/util/IdUtilTest.java new file mode 100644 index 00000000000..101293fd752 --- /dev/null +++ b/jans-core/util/src/test/java/io/jans/util/IdUtilTest.java @@ -0,0 +1,24 @@ +package io.jans.util; + +import org.testng.annotations.Test; + +import static org.testng.AssertJUnit.assertEquals; + +/** + * @author Yuriy Z + */ +public class IdUtilTest { + + @Test + public void shortUuid_lenthMustBe22() { + assertEquals(22, IdUtil.randomShortUUID().length()); + } + + @Test(enabled = false) + public void shortUuid_generateALotIdsAndPrintThem() { + for (int i = 0; i < 100000; i++) { + final String shortUUID = IdUtil.randomShortUUID(); + System.out.println(shortUUID + " length: " + shortUUID.length()); + }; + } +} diff --git a/jans-linux-setup/jans_setup/schema/jans_schema.json b/jans-linux-setup/jans_setup/schema/jans_schema.json index 79c8f7f54d8..ef1c7677229 100644 --- a/jans-linux-setup/jans_setup/schema/jans_schema.json +++ b/jans-linux-setup/jans_setup/schema/jans_schema.json @@ -4498,7 +4498,8 @@ "ssnId", "attr", "tknBndCnf", - "dpop" + "dpop", + "jansId" ], "must": [ "objectclass"