Keycloak вернуться к потоку аутентификации из внешнего приложения - PullRequest
0 голосов
/ 04 сентября 2018

У меня есть пользовательский SPI keycloak и обработчик пользовательских действий. Я создаю QR-изображение с закодированным callbackUrl, куда пользователь должен вернуться после сканирования QR-изображения на своем мобильном телефоне. Когда я имитирую его на том же хосте и порте, все хорошо, но когда я ухожу, скажите другому порту и из этого приложения трийнг вернуться, используя тот же URL-адрес. Keycloak возвращает ответ «Страница истекла». Я полагаю, что это произошло потому, что AUTH_SESSION_ID является нулевым для нового источника

Authenticator

@Override
    public void authenticate(AuthenticationFlowContext context) {
        logger.info("authenticate called ... context = " + context);
        try {
             String submitActionTokenUrl = generateCallbackUrl(context, "command0");
             final String base64String = getBase64QR(submitActionTokenUrl, "command0");
             Response challenge = context.form()
                    .setAttribute("totpSecretQrCode", base64String)
                    .setAttribute("manualUrl", submitActionTokenUrl)
                    .createForm("qr-validation.ftl");
            context.challenge(challenge);

        } catch (Exception e) {
            logger.info("{}", e);
            Response challenge = context.form()
                    .setError("Failed to generate QR-code")
                    .createForm("validation-error.ftl");
            context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR, challenge);
}




@Override
    public void action(AuthenticationFlowContext context) {
        logger.info("action called ... context = " + context);
        final AuthenticationSessionModel authSession = context.getAuthenticationSession();
        if (!Objects.equals(authSession.getAuthNote(ExternalApplicationNotificationActionTokenHandler.INITIATED_BY_ACTION_TOKEN_EXT_APP), "true")) {
            authenticate(context);
            return;
        }
        logger.info("OKKKKKKKKKKKKKKK" + context);
        authSession.removeAuthNote(ExternalApplicationNotificationActionTokenHandler.INITIATED_BY_ACTION_TOKEN_EXT_APP);
        String commandString = context.getUriInfo().getQueryParameters().getFirst("command");
        if (commandString.equals("command1")) {
            Response challenge = Response
                    .status(Response.Status.OK)
                    .header("Content-type", "application/json")
                    .entity("KEYCLAOK-REPLY-command1")
                    .build();

            context.challenge(challenge);
        } else if (commandString.equals("command2")) {
            Response challenge = Response
                    .status(Response.Status.OK)
                    .header("Content-type", "application/json")
                    .entity("KEYCLAOK-REPLY-command2")
                    .build();
            context.challenge(challenge);
        } else if (commandString.equals("SUCCESS")) {
            context.success();
        } else {
            context.failure(AuthenticationFlowError.INTERNAL_ERROR);
        }


 private String generateApplicationToken() throws IOException {
        JsonWebToken tokenSentBack = new JsonWebToken();
        SecretKeySpec hmacSecretKeySpec = new SecretKeySpec(org.keycloak.common.util.Base64.decode(SECRET), "HmacSHA256");
        String appToken = new JWSBuilder().jsonContent(tokenSentBack).hmac256(hmacSecretKeySpec);
        return URLEncoder.encode(appToken, "UTF-8");
    }

    private String generateActionToken(AuthenticationFlowContext context) {
        int validityInSecs = context.getRealm().getActionTokenGeneratedByUserLifespan();
        int absoluteExpirationInSecs = Time.currentTime() + validityInSecs;
        final AuthenticationSessionModel authSession = context.getAuthenticationSession();
        final String clientId = authSession.getClient().getClientId();
        ExternalApplicationNotificationActionToken actionToken = new ExternalApplicationNotificationActionToken(
                context.getUser().getId(),
                absoluteExpirationInSecs,
                clientId,
                KeycloakRemoteAuthenticatorFactory.PROVIDER_ID
        );
        KeycloakSession session = context.getSession();
        RealmModel realmModel = context.getRealm();
        UriInfo uriInfo = context.getUriInfo();
        return actionToken.serialize(
                session,
                realmModel,
                uriInfo
        );
    }


    private String generateCallbackUrl(AuthenticationFlowContext context, String command) throws IOException {
        final AuthenticationSessionModel authSession = context.getAuthenticationSession();
        final String clientId = authSession.getClient().getClientId();
        String applicationToken = generateApplicationToken();
        String actionToken = generateActionToken(context);
        return Urls
                .actionTokenBuilder(context.getUriInfo().getBaseUri(), actionToken, clientId, authSession.getTabId())
                .queryParam(Constants.EXECUTION, context.getExecution().getId())
                .queryParam(QUERY_PARAM_APP_TOKEN, "{tokenParameterName}")
                .queryParam("command", command)
                .build(context.getRealm().getName(), applicationToken)
                .toString();
    }

Когда я генерирую QR-изображение, я также генерирую URL для ручного нажатия (чтобы не использовать мобильный во время разработки). Я создаю простой MobileSimulator, который просто отправляет запрос обратно в keycloak

Мобильный симулятор:

private String request(Data data, String command) {
        HttpHeaders headers = new HttpHeaders();
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(callbackURL)
                .queryParam("key", data.key)
                .queryParam("client_id", data.clientId)
                .queryParam("tab_id", data.tabId)
                .queryParam("app-token", data.appToken)
                .queryParam("command", command)

        HttpEntity<?> entity = new HttpEntity<>(headers);

        HttpEntity<String> response = restTemplate.exchange(
                builder.toUriString(),
                HttpMethod.GET,
                entity,
                String.class);
        response.body
}

Вот пример или сгенерированный URL:

http://localhost:8083/scan?key=eyJhbGciOiJIUzUxMiIsImtpZCIgOiAiNjZhMDY0MjctMjgyZC00ZmZmLTgxODYtYTQ4NmM0MTJjODkxIn0.eyJqdGkiOiJiZTBjNDAzZi04OWFhLTQ0NzktYjg2Ny1hOWNkNDAzYzg0N2EiLCJleHAiOjE1MzYwNDk1OTgsIm5iZiI6MCwiaWF0IjoxNTM2MDQ5Mjk4LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvdmFzY28iLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvdmFzY28iLCJzdWIiOiIxZTlkNGYxMS0yYmJmLTQ5ZDItODkyNC02NGJlYjRhZGEyZWMiLCJ0eXAiOiJleHRlcm5hbC1hcHAtbm90aWZpY2F0aW9uIiwibm9uY2UiOiJiZTBjNDAzZi04OWFhLTQ0NzktYjg2Ny1hOWNkNDAzYzg0N2EiLCJhcHAtaWQiOiJ2YXNjby1yZW1vdGUtYXV0aGVudGljYXRpb24iLCJhc2lkIjoidmFzY28tY2xpZW50IiwiYXNpZCI6InZhc2NvLWNsaWVudCJ9.384l47tk8ehkbUBWyCcpOcj5t-inREEwtcNwNTcdMlzkjoZqVtYJKVBEQ2wC3taFcS8oPOvdvB_vVCAGWuJOMA&client_id=vasco-client&tab_id=Y3h4BFVaTQk&execution=cc19e1d7-1b7f-4d69-aeb3-034541aee734&app-token=eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjAsIm5iZiI6MCwiaWF0IjowfQ.Q3PcCURqvGI9dLI-VPy9ZwE4-RcVsGW8im7kfPZlXdY&command=command0

и вот пример запроса на выполнение симулятора:

http://localhost:8080/auth/realms/vasco/login-actions/action-token?key=eyJhbGciOiJIUzUxMiIsImtpZCIgOiAiNjZhMDY0MjctMjgyZC00ZmZmLTgxODYtYTQ4NmM0MTJjODkxIn0.eyJqdGkiOiJiZTBjNDAzZi04OWFhLTQ0NzktYjg2Ny1hOWNkNDAzYzg0N2EiLCJleHAiOjE1MzYwNDk1OTgsIm5iZiI6MCwiaWF0IjoxNTM2MDQ5Mjk4LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvdmFzY28iLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvdmFzY28iLCJzdWIiOiIxZTlkNGYxMS0yYmJmLTQ5ZDItODkyNC02NGJlYjRhZGEyZWMiLCJ0eXAiOiJleHRlcm5hbC1hcHAtbm90aWZpY2F0aW9uIiwibm9uY2UiOiJiZTBjNDAzZi04OWFhLTQ0NzktYjg2Ny1hOWNkNDAzYzg0N2EiLCJhcHAtaWQiOiJ2YXNjby1yZW1vdGUtYXV0aGVudGljYXRpb24iLCJhc2lkIjoidmFzY28tY2xpZW50IiwiYXNpZCI6InZhc2NvLWNsaWVudCJ9.384l47tk8ehkbUBWyCcpOcj5t-inREEwtcNwNTcdMlzkjoZqVtYJKVBEQ2wC3taFcS8oPOvdvB_vVCAGWuJOMA&client_id=vasco-client&tab_id=Y3h4BFVaTQk&execution=cc19e1d7-1b7f-4d69-aeb3-034541aee734&app-token=eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjAsIm5iZiI6MCwiaWF0IjowfQ.Q3PcCURqvGI9dLI-VPy9ZwE4-RcVsGW8im7kfPZlXdY&command=SUCCESS
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...