0

在我的 Java Web 应用程序中验证从 Azure 收到的访问令牌时遇到问题。图书馆jose4j只是signature.verifySignature()简单地返回false。有人可以帮我理解我做错了什么吗?

我已经成功配置了一个前端 Web 应用程序以通过 Azure Active Directory 登录,它会收到一个签名的 Json Web 令牌形式的访问令牌。我打算在"Authoriation": "token <signed-access-token>"对我的其他面向数据库的 Web 应用程序的所有请求中将该 JWT 附加到标头中。我想创建一个 Spring 过滤器来验证每个面向数据库的 Web 应用程序的 JWT 签名,以确保令牌实际上是由 Azure 颁发的。

我在这个 github repo 的分支中有我的小概念证明的代码: 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 解码的 Token Body 的样子:

{"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 的前 2 部分已签名,为什么它们包含的属性会影响签名的有效性?除非 Microsoft 签署令牌,然后在计算签名后添加随机数?

4

0 回答 0