Search in sources :

Example 1 with LogoutToken

use of org.keycloak.representations.LogoutToken in project keycloak by keycloak.

the class LogoutEndpoint method backchannelLogout.

/**
 * Backchannel logout endpoint implementation for Keycloak, which tries to logout the user from all sessions via
 * POST with a valid LogoutToken.
 *
 * Logout a session via a non-browser invocation. Will be implemented as a backchannel logout based on the
 * specification
 * https://openid.net/specs/openid-connect-backchannel-1_0.html
 *
 * @return
 */
@Path("/backchannel-logout")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response backchannelLogout() {
    MultivaluedMap<String, String> form = request.getDecodedFormParameters();
    event.event(EventType.LOGOUT);
    String encodedLogoutToken = form.getFirst(OAuth2Constants.LOGOUT_TOKEN);
    if (encodedLogoutToken == null) {
        event.error(Errors.INVALID_TOKEN);
        throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "No logout token", Response.Status.BAD_REQUEST);
    }
    LogoutTokenValidationCode validationCode = tokenManager.verifyLogoutToken(session, realm, encodedLogoutToken);
    if (!validationCode.equals(LogoutTokenValidationCode.VALIDATION_SUCCESS)) {
        event.error(Errors.INVALID_TOKEN);
        throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, validationCode.getErrorMessage(), Response.Status.BAD_REQUEST);
    }
    LogoutToken logoutToken = tokenManager.toLogoutToken(encodedLogoutToken).get();
    Stream<String> identityProviderAliases = tokenManager.getValidOIDCIdentityProvidersForBackchannelLogout(realm, session, encodedLogoutToken, logoutToken).map(idp -> idp.getConfig().getAlias());
    boolean logoutOfflineSessions = Boolean.parseBoolean(logoutToken.getEvents().getOrDefault(TokenUtil.TOKEN_BACKCHANNEL_LOGOUT_EVENT_REVOKE_OFFLINE_TOKENS, false).toString());
    BackchannelLogoutResponse backchannelLogoutResponse;
    if (logoutToken.getSid() != null) {
        backchannelLogoutResponse = backchannelLogoutWithSessionId(logoutToken.getSid(), identityProviderAliases, logoutOfflineSessions, logoutToken.getSubject());
    } else {
        backchannelLogoutResponse = backchannelLogoutFederatedUserId(logoutToken.getSubject(), identityProviderAliases, logoutOfflineSessions);
    }
    if (!backchannelLogoutResponse.getLocalLogoutSucceeded()) {
        event.error(Errors.LOGOUT_FAILED);
        throw new ErrorResponseException(OAuthErrorException.SERVER_ERROR, "There was an error in the local logout", Response.Status.NOT_IMPLEMENTED);
    }
    session.getProvider(SecurityHeadersProvider.class).options().allowEmptyContentType();
    if (oneOrMoreDownstreamLogoutsFailed(backchannelLogoutResponse)) {
        return Cors.add(request).auth().builder(Response.status(Response.Status.GATEWAY_TIMEOUT).type(MediaType.APPLICATION_JSON_TYPE)).build();
    }
    return Cors.add(request).auth().builder(Response.ok().type(MediaType.APPLICATION_JSON_TYPE)).build();
}
Also used : LogoutTokenValidationCode(org.keycloak.protocol.oidc.LogoutTokenValidationCode) LogoutToken(org.keycloak.representations.LogoutToken) ErrorResponseException(org.keycloak.services.ErrorResponseException) CorsErrorResponseException(org.keycloak.services.CorsErrorResponseException) BackchannelLogoutResponse(org.keycloak.protocol.oidc.BackchannelLogoutResponse) Path(javax.ws.rs.Path) POST(javax.ws.rs.POST) Consumes(javax.ws.rs.Consumes)

Example 2 with LogoutToken

use of org.keycloak.representations.LogoutToken in project keycloak by keycloak.

the class TokenManager method verifyLogoutToken.

public LogoutTokenValidationCode verifyLogoutToken(KeycloakSession session, RealmModel realm, String encodedLogoutToken) {
    Optional<LogoutToken> logoutTokenOptional = toLogoutToken(encodedLogoutToken);
    if (!logoutTokenOptional.isPresent()) {
        return LogoutTokenValidationCode.DECODE_TOKEN_FAILED;
    }
    LogoutToken logoutToken = logoutTokenOptional.get();
    List<OIDCIdentityProvider> identityProviders = getOIDCIdentityProviders(realm, session).collect(Collectors.toList());
    if (identityProviders.isEmpty()) {
        return LogoutTokenValidationCode.COULD_NOT_FIND_IDP;
    }
    Stream<OIDCIdentityProvider> validOidcIdentityProviders = validateLogoutTokenAgainstIdpProvider(identityProviders.stream(), encodedLogoutToken, logoutToken);
    if (validOidcIdentityProviders.count() == 0) {
        return LogoutTokenValidationCode.TOKEN_VERIFICATION_WITH_IDP_FAILED;
    }
    if (logoutToken.getSubject() == null && logoutToken.getSid() == null) {
        return LogoutTokenValidationCode.MISSING_SID_OR_SUBJECT;
    }
    if (!checkLogoutTokenForEvents(logoutToken)) {
        return LogoutTokenValidationCode.BACKCHANNEL_LOGOUT_EVENT_MISSING;
    }
    if (logoutToken.getOtherClaims().get(NONCE) != null) {
        return LogoutTokenValidationCode.NONCE_CLAIM_IN_TOKEN;
    }
    if (logoutToken.getId() == null) {
        return LogoutTokenValidationCode.LOGOUT_TOKEN_ID_MISSING;
    }
    if (logoutToken.getIat() == null) {
        return LogoutTokenValidationCode.MISSING_IAT_CLAIM;
    }
    return LogoutTokenValidationCode.VALIDATION_SUCCESS;
}
Also used : LogoutToken(org.keycloak.representations.LogoutToken) OIDCIdentityProvider(org.keycloak.broker.oidc.OIDCIdentityProvider)

Example 3 with LogoutToken

use of org.keycloak.representations.LogoutToken in project keycloak by keycloak.

the class LogoutTokenUtil method generateSignedLogoutToken.

public static String generateSignedLogoutToken(PrivateKey privateKey, String keyId, String issuer, String clientId, String userId, String sessionId, boolean revokeOfflineSessions) throws IOException {
    JWSHeader jwsHeader = new JWSHeader(Algorithm.RS256, OAuth2Constants.JWT, ContentType.APPLICATION_JSON.toString(), keyId);
    String logoutTokenHeaderEncoded = Base64Url.encode(JsonSerialization.writeValueAsBytes(jwsHeader));
    LogoutToken logoutToken = new LogoutToken();
    logoutToken.setSid(sessionId);
    logoutToken.putEvents(TokenUtil.TOKEN_BACKCHANNEL_LOGOUT_EVENT, new HashMap<>());
    logoutToken.putEvents(TokenUtil.TOKEN_BACKCHANNEL_LOGOUT_EVENT_REVOKE_OFFLINE_TOKENS, revokeOfflineSessions);
    logoutToken.setSubject(userId);
    logoutToken.issuer(issuer);
    logoutToken.id(UUID.randomUUID().toString());
    logoutToken.issuedNow();
    logoutToken.audience(clientId);
    String logoutTokenPayloadEncoded = Base64Url.encode(JsonSerialization.writeValueAsBytes(logoutToken));
    try {
        Signature signature = Signature.getInstance(JavaAlgorithm.RS256);
        signature.initSign(privateKey);
        String data = logoutTokenHeaderEncoded + "." + logoutTokenPayloadEncoded;
        byte[] dataByteArray = data.getBytes();
        signature.update(dataByteArray);
        byte[] signatureByteArray = signature.sign();
        return data + "." + Base64Url.encode(signatureByteArray);
    } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
        return null;
    }
}
Also used : Signature(java.security.Signature) LogoutToken(org.keycloak.representations.LogoutToken) NoSuchAlgorithmException(java.security.NoSuchAlgorithmException) SignatureException(java.security.SignatureException) InvalidKeyException(java.security.InvalidKeyException) JWSHeader(org.keycloak.jose.jws.JWSHeader)

Example 4 with LogoutToken

use of org.keycloak.representations.LogoutToken in project keycloak by keycloak.

the class DefaultTokenManager method initLogoutToken.

public LogoutToken initLogoutToken(ClientModel client, UserModel user, AuthenticatedClientSessionModel clientSession) {
    LogoutToken token = new LogoutToken();
    token.id(KeycloakModelUtils.generateId());
    token.issuedNow();
    token.issuer(clientSession.getNote(OIDCLoginProtocol.ISSUER));
    token.putEvents(TokenUtil.TOKEN_BACKCHANNEL_LOGOUT_EVENT, JsonSerialization.createObjectNode());
    token.addAudience(client.getClientId());
    OIDCAdvancedConfigWrapper oidcAdvancedConfigWrapper = OIDCAdvancedConfigWrapper.fromClientModel(client);
    if (oidcAdvancedConfigWrapper.isBackchannelLogoutSessionRequired()) {
        token.setSid(clientSession.getUserSession().getId());
    }
    if (oidcAdvancedConfigWrapper.getBackchannelLogoutRevokeOfflineTokens()) {
        token.putEvents(TokenUtil.TOKEN_BACKCHANNEL_LOGOUT_EVENT_REVOKE_OFFLINE_TOKENS, true);
    }
    token.setSubject(user.getId());
    return token;
}
Also used : OIDCAdvancedConfigWrapper(org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper) LogoutToken(org.keycloak.representations.LogoutToken)

Example 5 with LogoutToken

use of org.keycloak.representations.LogoutToken in project keycloak by keycloak.

the class ResourceAdminManager method sendBackChannelLogoutRequestToClientUri.

protected Response sendBackChannelLogoutRequestToClientUri(ClientModel resource, AuthenticatedClientSessionModel clientSessionModel, String managementUrl) {
    UserModel user = clientSessionModel.getUserSession().getUser();
    LogoutToken logoutToken = session.tokens().initLogoutToken(resource, user, clientSessionModel);
    String token = session.tokens().encode(logoutToken);
    if (logger.isDebugEnabled())
        logger.debugv("logout resource {0} url: {1} sessionIds: ", resource.getClientId(), managementUrl);
    HttpPost post = null;
    try {
        post = new HttpPost(managementUrl);
        List<NameValuePair> parameters = new LinkedList<>();
        if (logoutToken != null) {
            parameters.add(new BasicNameValuePair(OAuth2Constants.LOGOUT_TOKEN, token));
        }
        CloseableHttpClient httpClient = session.getProvider(HttpClientProvider.class).getHttpClient();
        UrlEncodedFormEntity formEntity;
        formEntity = new UrlEncodedFormEntity(parameters);
        post.setEntity(formEntity);
        try (CloseableHttpResponse response = httpClient.execute(post)) {
            try {
                int status = response.getStatusLine().getStatusCode();
                EntityUtils.consumeQuietly(response.getEntity());
                boolean success = status == 204 || status == 200;
                logger.debugf("logout success for %s: %s", managementUrl, success);
                return Response.status(status).build();
            } finally {
                EntityUtils.consumeQuietly(response.getEntity());
            }
        }
    } catch (IOException e) {
        ServicesLogger.LOGGER.logoutFailed(e, resource.getClientId());
        return Response.serverError().build();
    } finally {
        if (post != null) {
            post.reset();
        }
    }
}
Also used : HttpPost(org.apache.http.client.methods.HttpPost) BasicNameValuePair(org.apache.http.message.BasicNameValuePair) NameValuePair(org.apache.http.NameValuePair) CloseableHttpClient(org.apache.http.impl.client.CloseableHttpClient) LogoutToken(org.keycloak.representations.LogoutToken) UrlEncodedFormEntity(org.apache.http.client.entity.UrlEncodedFormEntity) IOException(java.io.IOException) LinkedList(java.util.LinkedList) UserModel(org.keycloak.models.UserModel) HttpClientProvider(org.keycloak.connections.httpclient.HttpClientProvider) BasicNameValuePair(org.apache.http.message.BasicNameValuePair) CloseableHttpResponse(org.apache.http.client.methods.CloseableHttpResponse)

Aggregations

LogoutToken (org.keycloak.representations.LogoutToken)8 Path (javax.ws.rs.Path)2 Test (org.junit.Test)2 ClientsResource (org.keycloak.admin.client.resource.ClientsResource)2 JWSInput (org.keycloak.jose.jws.JWSInput)2 IDToken (org.keycloak.representations.IDToken)2 ClientRepresentation (org.keycloak.representations.idm.ClientRepresentation)2 AbstractTestRealmKeycloakTest (org.keycloak.testsuite.AbstractTestRealmKeycloakTest)2 OAuthClient (org.keycloak.testsuite.util.OAuthClient)2 IOException (java.io.IOException)1 InvalidKeyException (java.security.InvalidKeyException)1 NoSuchAlgorithmException (java.security.NoSuchAlgorithmException)1 Signature (java.security.Signature)1 SignatureException (java.security.SignatureException)1 LinkedList (java.util.LinkedList)1 Consumes (javax.ws.rs.Consumes)1 GET (javax.ws.rs.GET)1 POST (javax.ws.rs.POST)1 NameValuePair (org.apache.http.NameValuePair)1 UrlEncodedFormEntity (org.apache.http.client.entity.UrlEncodedFormEntity)1