Manipulate OpenID redirect-response with custom implementation

Signed-off-by: Captain-P-Goldfish <captain.p.goldfish@gmx.de>
This commit is contained in:
Captain-P-Goldfish 2024-07-16 21:19:41 +02:00 committed by Marek Posolda
parent b88ddcf981
commit 526286e851

View file

@ -16,10 +16,10 @@
*/ */
package org.keycloak.protocol.oidc; package org.keycloak.protocol.oidc;
import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.approveOAuth2DeviceAuthorization; import jakarta.ws.rs.core.HttpHeaders;
import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.denyOAuth2DeviceAuthorization; import jakarta.ws.rs.core.Response;
import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.isOAuth2DeviceVerificationFlow; import jakarta.ws.rs.core.UriBuilder;
import jakarta.ws.rs.core.UriInfo;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
@ -43,6 +43,8 @@ import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpointChecker; import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpointChecker;
import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest; import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
import org.keycloak.protocol.oidc.utils.LogoutUtil; import org.keycloak.protocol.oidc.utils.LogoutUtil;
import org.keycloak.protocol.oidc.utils.OAuth2Code;
import org.keycloak.protocol.oidc.utils.OAuth2CodeParser;
import org.keycloak.protocol.oidc.utils.OIDCRedirectUriBuilder; import org.keycloak.protocol.oidc.utils.OIDCRedirectUriBuilder;
import org.keycloak.protocol.oidc.utils.OIDCResponseMode; import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.protocol.oidc.utils.OIDCResponseType;
@ -55,8 +57,6 @@ import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.context.ImplicitHybridTokenResponse; import org.keycloak.services.clientpolicy.context.ImplicitHybridTokenResponse;
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.protocol.oidc.utils.OAuth2Code;
import org.keycloak.protocol.oidc.utils.OAuth2CodeParser;
import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.util.TokenUtil; import org.keycloak.util.TokenUtil;
@ -66,10 +66,9 @@ import java.net.URI;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import jakarta.ws.rs.core.HttpHeaders; import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.approveOAuth2DeviceAuthorization;
import jakarta.ws.rs.core.Response; import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.denyOAuth2DeviceAuthorization;
import jakarta.ws.rs.core.UriBuilder; import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.isOAuth2DeviceVerificationFlow;
import jakarta.ws.rs.core.UriInfo;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -237,13 +236,13 @@ public class OIDCLoginProtocol implements LoginProtocol {
String code = null; String code = null;
if (responseType.hasResponseType(OIDCResponseType.CODE)) { if (responseType.hasResponseType(OIDCResponseType.CODE)) {
OAuth2Code codeData = new OAuth2Code(UUID.randomUUID().toString(), OAuth2Code codeData = new OAuth2Code(UUID.randomUUID().toString(),
Time.currentTime() + userSession.getRealm().getAccessCodeLifespan(), Time.currentTime() + userSession.getRealm().getAccessCodeLifespan(),
nonce, nonce,
authSession.getClientNote(OAuth2Constants.SCOPE), authSession.getClientNote(OAuth2Constants.SCOPE),
authSession.getClientNote(OIDCLoginProtocol.REDIRECT_URI_PARAM), authSession.getClientNote(OIDCLoginProtocol.REDIRECT_URI_PARAM),
authSession.getClientNote(OIDCLoginProtocol.CODE_CHALLENGE_PARAM), authSession.getClientNote(OIDCLoginProtocol.CODE_CHALLENGE_PARAM),
authSession.getClientNote(OIDCLoginProtocol.CODE_CHALLENGE_METHOD_PARAM), authSession.getClientNote(OIDCLoginProtocol.CODE_CHALLENGE_METHOD_PARAM),
userSession.getId()); userSession.getId());
code = OAuth2CodeParser.persistCode(session, clientSession, codeData); code = OAuth2CodeParser.persistCode(session, clientSession, codeData);
redirectUri.addParam(OAuth2Constants.CODE, code); redirectUri.addParam(OAuth2Constants.CODE, code);
@ -253,7 +252,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
if (responseType.isImplicitOrHybridFlow()) { if (responseType.isImplicitOrHybridFlow()) {
org.keycloak.protocol.oidc.TokenManager tokenManager = new org.keycloak.protocol.oidc.TokenManager(); org.keycloak.protocol.oidc.TokenManager tokenManager = new org.keycloak.protocol.oidc.TokenManager();
org.keycloak.protocol.oidc.TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, clientSession.getClient(), event, session, userSession, clientSessionCtx) org.keycloak.protocol.oidc.TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, clientSession.getClient(), event, session, userSession, clientSessionCtx)
.generateAccessToken(); .generateAccessToken();
if (responseType.hasResponseType(OIDCResponseType.ID_TOKEN)) { if (responseType.hasResponseType(OIDCResponseType.ID_TOKEN)) {
@ -282,7 +281,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
if (!clientConfig.isExcludeIssuerFromAuthResponse()) { if (!clientConfig.isExcludeIssuerFromAuthResponse()) {
redirectUri.addParam(OAuth2Constants.ISSUER, clientSession.getNote(OIDCLoginProtocol.ISSUER)); redirectUri.addParam(OAuth2Constants.ISSUER, clientSession.getNote(OIDCLoginProtocol.ISSUER));
} }
return redirectUri.build(); return buildRedirectUri(redirectUri, authSession, userSession, clientSessionCtx, cpe, null);
} }
AccessTokenResponse res = responseBuilder.build(); AccessTokenResponse res = responseBuilder.build();
@ -298,7 +297,35 @@ public class OIDCLoginProtocol implements LoginProtocol {
} }
} }
return redirectUri.build(); return buildRedirectUri(redirectUri, authSession, userSession, clientSessionCtx);
}
/**
* this method can be used in extension-implementations to the {@link OIDCLoginProtocol} to add additional
* parameters to the redirectUri after successful authentication and to store these e.g. in the clientSession
*
* @see https://github.com/keycloak/keycloak/issues/31086
*/
public Response buildRedirectUri(OIDCRedirectUriBuilder redirectUriBuilder,
AuthenticationSessionModel authSession,
UserSessionModel userSession,
ClientSessionContext clientSessionCtx) {
return redirectUriBuilder.build();
}
/**
* this method can be used in extension-implementations to the {@link OIDCLoginProtocol} to add additional
* parameters to the redirectUri after failed authentication
*
* @see https://github.com/keycloak/keycloak/issues/31086
*/
public Response buildRedirectUri(OIDCRedirectUriBuilder redirectUriBuilder,
AuthenticationSessionModel authSession,
UserSessionModel userSession,
ClientSessionContext clientSessionCtx,
Exception ex,
Error oidcError) {
return redirectUriBuilder.build();
} }
// For FAPI 1.0 Advanced // For FAPI 1.0 Advanced
@ -324,7 +351,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
// Remove authenticationSession from current tab // Remove authenticationSession from current tab
new AuthenticationSessionManager(session).removeTabIdInAuthenticationSession(realm, authSession); new AuthenticationSessionManager(session).removeTabIdInAuthenticationSession(realm, authSession);
return redirectUri.build(); return buildRedirectUri(redirectUri, authSession, null, null, null, error);
} }
private OIDCRedirectUriBuilder buildErrorRedirectUri(String redirect, String state, Error error) { private OIDCRedirectUriBuilder buildErrorRedirectUri(String redirect, String state, Error error) {
@ -353,9 +380,9 @@ public class OIDCLoginProtocol implements LoginProtocol {
@Override @Override
public ClientData getClientData(AuthenticationSessionModel authSession) { public ClientData getClientData(AuthenticationSessionModel authSession) {
return new ClientData(authSession.getRedirectUri(), return new ClientData(authSession.getRedirectUri(),
authSession.getClientNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM), authSession.getClientNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM),
authSession.getClientNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM), authSession.getClientNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM),
authSession.getClientNote(OIDCLoginProtocol.STATE_PARAM)); authSession.getClientNote(OIDCLoginProtocol.STATE_PARAM));
} }
@Override @Override
@ -365,11 +392,11 @@ public class OIDCLoginProtocol implements LoginProtocol {
// Should check if clientData are valid for current client // Should check if clientData are valid for current client
AuthorizationEndpointRequest req = AuthorizationEndpointRequest.fromClientData(clientData); AuthorizationEndpointRequest req = AuthorizationEndpointRequest.fromClientData(clientData);
AuthorizationEndpointChecker checker = new AuthorizationEndpointChecker() AuthorizationEndpointChecker checker = new AuthorizationEndpointChecker()
.event(event) .event(event)
.client(client) .client(client)
.realm(realm) .realm(realm)
.request(req) .request(req)
.session(session); .session(session);
try { try {
checker.checkResponseType(); checker.checkResponseType();
checker.checkRedirectUri(); checker.checkRedirectUri();
@ -379,7 +406,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
setupResponseTypeAndMode(clientData.getResponseType(), clientData.getResponseMode()); setupResponseTypeAndMode(clientData.getResponseType(), clientData.getResponseMode());
OIDCRedirectUriBuilder redirectUri = buildErrorRedirectUri(clientData.getRedirectUri(), clientData.getState(), error); OIDCRedirectUriBuilder redirectUri = buildErrorRedirectUri(clientData.getRedirectUri(), clientData.getState(), error);
return redirectUri.build(); return buildRedirectUri(redirectUri, null, null, null, null, error);
} }
private OAuth2ErrorRepresentation translateError(Error error) { private OAuth2ErrorRepresentation translateError(Error error) {
@ -465,12 +492,12 @@ public class OIDCLoginProtocol implements LoginProtocol {
return false; return false;
} }
int authTimeInt = authTime==null ? 0 : Integer.parseInt(authTime); int authTimeInt = authTime == null ? 0 : Integer.parseInt(authTime);
int maxAgeInt = Integer.parseInt(maxAge); int maxAgeInt = Integer.parseInt(maxAge);
if (authTimeInt + maxAgeInt < Time.currentTime()) { if (authTimeInt + maxAgeInt < Time.currentTime()) {
logger.debugf("Authentication time is expired, needs to reauthenticate. userSession=%s, clientId=%s, maxAge=%d, authTime=%d", userSession.getId(), logger.debugf("Authentication time is expired, needs to reauthenticate. userSession=%s, clientId=%s, maxAge=%d, authTime=%d", userSession.getId(),
authSession.getClient().getId(), maxAgeInt, authTimeInt); authSession.getClient().getId(), maxAgeInt, authTimeInt);
return true; return true;
} }