Почему мой токен доступа Azure JWT не проходит проверку подписи Java?

У меня возникают проблемы с проверкой токена доступа, который я получаю из Azure, в моем веб-приложении Java. jose4j библиотеки signature.verifySignature() просто возвращает false. Может ли кто-нибудь помочь мне понять, что я делаю неправильно?

Я успешно настроил интерфейсное веб-приложение для входа в систему через Azure Active Directory, после чего оно получает токен доступа в виде подписанного веб-токена Json. Я намерен прикрепить этот JWT в заголовок "Authoriation": "token <signed-access-token>" во всех запросах к другим моим веб-приложениям, работающим с базой данных. Я хотел бы создать Spring Filter для проверки подписи JWT для каждого из этих веб-приложений, работающих с базой данных, чтобы убедиться, что токен действительно был выпущен Azure.

У меня есть код моего небольшого доказательства концепции в этой ветке репозитория github: репо. Но вот мой фильтр:

package com.doug.example.oauthclient.microservice.config;

//...
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.jose4j.jwa.AlgorithmConstraints;
import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jwk.JsonWebKeySet;
import org.jose4j.jwk.VerificationJwkSelector;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.lang.JoseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.filter.GenericFilterBean;

public class AzureJwtFilter extends GenericFilterBean {
    private final static Logger LOG = LoggerFactory.getLogger(AzureJwtFilter.class);

    private final static String AUTHORIZATION_HEADER_NAME = "Authorization";
    private final static String TOKEN_PREFIX = "token ";

    private String azurePublicKeyUrl = "https://login.microsoftonline.com/<my-ad>.onmicrosoft.com/discovery/v2.0/keys";

    private RestOperations restTemplate = new RestTemplate();

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) {

        HttpServletRequest httpRequest = (HttpServletRequest)request;
        if (httpRequest.getHeader(AUTHORIZATION_HEADER_NAME) == null ||
                !httpRequest.getHeader(AUTHORIZATION_HEADER_NAME).startsWith(TOKEN_PREFIX) ) {
            throw new AuthenticationCredentialsNotFoundException("Missing or Invalid Authorization Header in Request");
        }

        String authorizationToken = httpRequest.getHeader(AUTHORIZATION_HEADER_NAME).replace(TOKEN_PREFIX, "");
        String[] tokenParts = authorizationToken.split("\\.");
        if (tokenParts.length != 3) {
            throw new InvalidTokenException("The authorization token did not have 3 parts");
        }

        try {
            String publicKeySetJson = restTemplate.getForObject(
                    new URI(azurePublicKeyUrl), String.class);
            JsonWebKeySet publicKeySet = new JsonWebKeySet(publicKeySetJson);
            JsonWebSignature signature = new JsonWebSignature();
            signature.setAlgorithmConstraints(new AlgorithmConstraints(
                    AlgorithmConstraints.ConstraintType.WHITELIST,
                    AlgorithmIdentifiers.RSA_USING_SHA256));
            signature.setCompactSerialization(authorizationToken);
            VerificationJwkSelector publicKeySelector = new VerificationJwkSelector();
            JsonWebKey jsonWebKey = publicKeySelector.select(
                    signature, publicKeySet.getJsonWebKeys());
            signature.setKey(jsonWebKey.getKey());
            if (!signature.verifySignature()) { // <<== ALWAYS FAILS VERIFICATION :(
                throw new RuntimeException("JSON Web Token Signature Invalid");
            }
        } catch (URISyntaxException e) {
            LOG.error("Invalid URL \"" + azurePublicKeyUrl + "\"", e);
        } catch (JoseException e) {
            LOG.error("An error occurred when validating the JWT signature", e);
            throw new RuntimeException(e);
        }

        try {
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }
}

Вот как выглядят метаданные токена, декодированные в Base64:

{"typ":"JWT","nonce":"AQABAAAAAADX8GCi6Js6SK82TsD2Pb7r9xWDjnazKO0nBJFdLLawrH4SsyXGtZpR4VSgvoX7ADMIjFSLUAOhd_xJnYCQw85rt3-pFp1UoMW8B9zL3Mjp6SAA","alg":"RS256","x5t":"iBjL1Rcqzhiy4fpxIxdZqohM2Yk","kid":"iBjL1Rcqzhiy4fpxIxdZqohM2Yk"}

Вот как выглядит тело токена в кодировке Base64:

{"aud":"https://graph.microsoft.com","iss":"https://sts.windows.net/.../","iat":12345,"nbf":12345,"exp":1528136094,"acr":"1","aio":"Y2dg.../33rf...","amr":["pwd"],"app_displayname":"localhost","appid":"...","appidacr":"1","family_name":"Snoop","given_name":"Dougg","ipaddr":"...","name":"Snoop, Dougg - Dougg","oid":"...","onprem_sid":"...","platf":"3","puid":"...","scp":"User.Read","signin_state":["inknownntwk","kmsi"],"sub":"...","tid":"...","unique_name":"...","upn":"...","uti":"...","ver":"1.0"}

Я видел некоторые упоминания о том, как nonce может нарушить проверку (ссылка на сайт), но я не понимаю, как это могло произойти. Я имею в виду, что первые две части JWT подписаны, почему свойства, которые они содержат, влияют на действительность подписи? Если Microsoft не подписывает токен, а затем добавляет одноразовый номер после того, как подпись уже вычислена?

Поскольку я не могу проверить подпись токена, я отправляю его в api графика Microsoft в качестве токена-носителя, чтобы проверить его (graph.microsoft.com/v1.0/me) ...

SnoopDougg 07.06.2018 19:35

Вы когда-нибудь понимали это? Я испытываю то же самое; если я установлю для ресурса идентификатор приложения, я смогу проверить токен. Но если я установлю ресурс на graph.microsoft.com, я не смогу проверить токен.

Brandon Smith 20.12.2018 16:34

Извините, я давно с этим не связывался, но что вы имеете в виду под the resource?

SnoopDougg 20.12.2018 22:52

В AD v1.0 вы передаете ресурс в параметрах для получения токена доступа; ресурс эквивалентен aud (аудитории).

Brandon Smith 21.12.2018 16:45

О, интересно, думаю, я не использовал v1.0, похоже, я использую v2.0, и токен доступа, который я получаю, когда включаю свой идентификатор приложения, - это тот, который я не могу проверить. В версии 1.0 вы можете проверить токен доступа для конкретного приложения? POST-запрос, который я отправляю для токена доступа, направлен на https://login.microsoftonline.com/<my organization>/oauth2/v2.0/token с переменными application / x-www-form-urlencoded для grant_type=authorization_code, client_id=<App ID>, code=<Authorization code>, redirect_url=https://<app host name> и client_secret=<App secret> ...

SnoopDougg 21.12.2018 16:58
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
5
1 447
0

Другие вопросы по теме