diff --git a/authenticator/src/main/java/cz/cesnet/keycloak/CustomAuthenticator.java b/authenticator/src/main/java/cz/cesnet/keycloak/CustomAuthenticator.java index 0b79a3789c77c245c7c1ff5dc37e7c2d9adb1bbf..0c778cd5ca04289fe63869058deffdaaef10248d 100644 --- a/authenticator/src/main/java/cz/cesnet/keycloak/CustomAuthenticator.java +++ b/authenticator/src/main/java/cz/cesnet/keycloak/CustomAuthenticator.java @@ -17,7 +17,6 @@ import org.slf4j.LoggerFactory; import jakarta.ws.rs.core.Response; -import java.util.List; import java.util.Map; import static org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE; diff --git a/authenticator/src/main/java/cz/cesnet/keycloak/CustomTokenExchangeProvider.java b/authenticator/src/main/java/cz/cesnet/keycloak/CustomTokenExchangeProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..7ea5967ad36b5f15e6fff7443d06291ac26a2b43 --- /dev/null +++ b/authenticator/src/main/java/cz/cesnet/keycloak/CustomTokenExchangeProvider.java @@ -0,0 +1,60 @@ +package cz.cesnet.keycloak; + +import jakarta.ws.rs.core.Response; +import org.keycloak.OAuthErrorException; +import org.keycloak.events.Details; +import org.keycloak.events.Errors; +import org.keycloak.events.EventBuilder; +import org.keycloak.protocol.oidc.TokenExchangeContext; +import org.keycloak.protocol.oidc.TokenExchangeProvider; +import org.keycloak.protocol.oidc.DefaultTokenExchangeProvider; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.jose.jws.JWSInputException; +import org.keycloak.representations.JsonWebToken; +import org.keycloak.services.CorsErrorResponseException; +import org.keycloak.services.cors.Cors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CustomTokenExchangeProvider implements TokenExchangeProvider { + + private static final Logger logger = LoggerFactory.getLogger(CustomTokenExchangeProvider.class); + + @Override + public Response exchange(TokenExchangeContext context) { + logger.debug("Exchanging token by custom token exchange provider."); + + EventBuilder event = context.getEvent(); + JsonWebToken jwt; + Cors cors = context.getCors(); + TokenExchangeContext.Params params = context.getParams(); + String subjectToken = params.getSubjectToken(); + + try { + JWSInput jws = new JWSInput(subjectToken); + jwt = jws.readJsonContent(JsonWebToken.class); + } catch (JWSInputException e) { + event.detail(Details.REASON, "unable to parse jwt subject_token"); + event.error(Errors.INVALID_TOKEN); + throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_TOKEN, "Invalid subject token", Response.Status.BAD_REQUEST); + } + + String issuer = jwt.getIssuer(); + logger.info("Token exchange issuer: {}", issuer); + + logger.info(jwt.getOtherClaims().toString()); + + DefaultTokenExchangeProvider defaultTokenExchangeProvider = new DefaultTokenExchangeProvider(); + return defaultTokenExchangeProvider.exchange(context); + } + + @Override + public void close() { + } + + @Override + public boolean supports(TokenExchangeContext context) { + return true; + } + +} \ No newline at end of file diff --git a/authenticator/src/main/java/cz/cesnet/keycloak/CustomTokenExchangeProviderFactory.java b/authenticator/src/main/java/cz/cesnet/keycloak/CustomTokenExchangeProviderFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..3d4fab02739b04ba85527ced0c353ed137c6c78d --- /dev/null +++ b/authenticator/src/main/java/cz/cesnet/keycloak/CustomTokenExchangeProviderFactory.java @@ -0,0 +1,40 @@ +package cz.cesnet.keycloak; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.keycloak.models.KeycloakSession; +import org.keycloak.protocol.oidc.TokenExchangeProvider; +import org.keycloak.protocol.oidc.TokenExchangeProviderFactory; + +public class CustomTokenExchangeProviderFactory implements TokenExchangeProviderFactory { + private static final String PROVIDER_ID = "custom-token-exchange"; + private static final Logger logger = LoggerFactory.getLogger(CustomTokenExchangeProviderFactory.class); + + @Override + public TokenExchangeProvider create(KeycloakSession session) { + return new CustomTokenExchangeProvider(); + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public void init(org.keycloak.Config.Scope config) { + } + + @Override + public void postInit(org.keycloak.models.KeycloakSessionFactory factory) { + } + + @Override + public void close() { + } + + @Override + public int order() { + // important that this value is higher than the default TokenExchangeProvider, otherwise it won't use this one. + return 1000; + } +} \ No newline at end of file diff --git a/authenticator/src/main/resources/META-INF/services/org.keycloak.protocol.oidc.TokenExchangeProviderFactory b/authenticator/src/main/resources/META-INF/services/org.keycloak.protocol.oidc.TokenExchangeProviderFactory new file mode 100644 index 0000000000000000000000000000000000000000..17d4e6a7be4b18f5c9ff6bb23f679f75470c20b3 --- /dev/null +++ b/authenticator/src/main/resources/META-INF/services/org.keycloak.protocol.oidc.TokenExchangeProviderFactory @@ -0,0 +1 @@ +cz.cesnet.keycloak.CustomTokenExchangeProviderFactory \ No newline at end of file