Почему мой Azure Access Token JWT не проходит проверку подписи Java? - PullRequest
0 голосов
/ 04 июня 2018

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

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

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

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 может отбросить проверку ( ссылка ), но я не понимаю, как это могло произойти.Я имею в виду, что первые 2 части JWT подписаны, почему свойства, которые они содержат, влияют на действительность подписи?Разве Microsoft не подписывает токен, а затем добавляет одноразовый номер после того, как подпись уже вычислена?

...