Why is my Azure Access Token JWT failing java signature validation?

时间:2018-06-04 17:43:27

标签: java oauth-2.0 jwt azure-active-directory jose4j

I'm having trouble validating an access token I receive from Azure in my Java web app. The jose4j library's signature.verifySignature() is simply returning false. Could someone please help me understand what I'm doing wrong?

I have successfully configured a front-facing web app to login via Azure Active Directory, upon which it receives an access token in the form of a signed Json Web Token. I intend to attach that JWT in a header, "Authoriation": "token <signed-access-token>", in all requests to my other database-facing web apps. I would like to create a Spring Filter to validate the JWT's signature for each of these database-facing web apps, to ensure that the token was in fact issued by Azure.

I have the code for my little proof of concept in this github repo's branch: repo. But here's my filter:

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());
        }
    }
}

Here's what the Base64 decoded token metadata looks like:

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

Here's what the Base64 decoded Token Body looks like:

{"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"}

Now I have seen some mention of how the nonce might throw off validation (link), but I don't understand how that would happen. I mean the first 2 parts of the JWT are signed, why would the properties they contain affect the signature's validity? Unless Microsoft is signing the token and then adding in the nonce after the signature is already computed?

0 个答案:

没有答案