JWT / OAuth令牌签名验证失败

时间:2019-01-21 14:47:21

标签: php .net oauth-2.0 openssl jwt

有一个基于.NET Framework的Windows桌面应用程序,可与基于PHP的后端网站进行通信。尝试验证.NET应用程序提供给PHP网站的令牌时,我遇到永久性签名验证失败(openssl_verify返回0)。

  1. .NET Framework应用程序使用Microsoft Authentication Library(MSAL)对Azure Active Directory(AAD)进行用户身份验证。该库将令牌作为字符串返回。应用程序将此令牌作为其请求的一部分发送到基于PHP的服务器。

    var result = await Application.AcquireTokenAsync(scopes).ConfigureAwait(false);

    var令牌= result.AccessToken;

    headers.Add(“ X-Auth-AAD-Token”,令牌);

  2. 基于PHP的服务器网站接收.NET应用程序提供的令牌。然后,它会尝试验证令牌并在从Azure Active Directory请求用户数据时使用它。 PHP网站使用Networg/oauth2-azure库,该库是通用thephpleage/oauth2-client库的提供者,然后使用firebase/php-jwt库处理JWT令牌。

PHP应用程序实例化一个Azure提供程序并调用

$provider->validateAccessToken($token);

其中$token是从.NET应用程序接收的字符串。此方法调用

$keys = $this->getJwtVerificationKeys();
(array)JWT::decode($accessToken, $keys, ['RS256'])

其中$keys是从https://login.windows.net/common/discovery/keys端点检索到的公共密钥的数组。

JWT::decode然后将令牌拆分为标头,有效负载和签名,对其进行解码,选择正确的公钥并验证签名:

public static function decode($jwt, $key, array $allowed_algs = array())
    $tks = explode('.', $jwt);
    list($headb64, $bodyb64, $cryptob64) = $tks;
    $header = static::jsonDecode(static::urlsafeB64Decode($headb64))
    $sig = static::urlsafeB64Decode($cryptob64);
    $key = $key[$header->kid];

    static::verify("$headb64.$bodyb64", $sig, $key, $header->alg);

jsonDecode呼叫

$obj = json_decode($input, false, 512, JSON_BIGINT_AS_STRING);

urlsafeB64Decode看起来

public static function urlsafeB64Decode($input)
    $remainder = strlen($input) % 4;
    if ($remainder) {
        $padlen = 4 - $remainder;
        $input .= str_repeat('=', $padlen);
    }
    return base64_decode(strtr($input, '-_', '+/'));

然后,verify方法尝试通过调用openssl_verify来验证签名。

private static function verify($msg, $signature, $key, $alg)
    list($function, $algorithm) = static::$supported_algs[$alg]; // list('openssl', 'SHA256')
    openssl_verify($msg, $signature, $key, $algorithm);

openssl_verify函数返回0,这意味着签名验证失败(不匹配)。

我在做什么错?如何解决?

编辑:除非为“我”发行令牌,否则我不应该验证签名。由于我检查的令牌的作用域是Graph API,因此仅Graph API可以验证它。在更改了向Web应用程序请求的令牌的范围后,签名将按预期进行验证。

1 个答案:

答案 0 :(得分:0)

decode函数中,语句$key = $key[$header->kid];返回与您从https://login.windows.net/common/discovery/keys获得的kid头参数相对应的键。

由于上述url中提供的密钥位于JWK格式下,因此该语句将返回以下内容:

{
"kty": "RSA",
"use": "sig",
"kid": "-sxMJMLCIDWMTPvZyJ6tx-CDxw0",
"x5t": "-sxMJMLCIDWMTPvZyJ6tx-CDxw0",
"n": "rxlPnqW6fNuCbdrhDEzwGJVux3iPvtt_8r-uHHIKa7C_b_ux5hewNMS91SgUPZOrsqb54uHj_7INWKqKEtFc4YP83Fhss_uO_mT97czENs4zWaSN9Eww_Fz36xq_uZ65750lHKwXQJ1A_pe-VOgNlPg8ECi7meQDJ05r838eu1jpKFjxkQrdRFTLgYtRQ7TxX-zzRyoRR8iqJc6Rvnijh19-YfWtBsCI1r127SFakUBrY_ZKsKyE9KNWUL7H65EyFRNgK80XfYvhQlGw3-Ajf28fi71wW-BypK1bTCArzwX7zgF3H6P1u8PKosSOSN_Q9-Qc9X-R_Y-3bOpOIiLOvw",
"e": "AQAB",
"x5c": [
"MIIDBTCCAe2gAwIBAgIQKOfEJNDyDplBSXKYcM6UcjANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE4MTIyMjAwMDAwMFoXDTIwMTIyMjAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK8ZT56lunzbgm3a4QxM8BiVbsd4j77bf/K/rhxyCmuwv2/7seYXsDTEvdUoFD2Tq7Km+eLh4/+yDViqihLRXOGD/NxYbLP7jv5k/e3MxDbOM1mkjfRMMPxc9+sav7meue+dJRysF0CdQP6XvlToDZT4PBAou5nkAydOa/N/HrtY6ShY8ZEK3URUy4GLUUO08V/s80cqEUfIqiXOkb54o4dffmH1rQbAiNa9du0hWpFAa2P2SrCshPSjVlC+x+uRMhUTYCvNF32L4UJRsN/gI39vH4u9cFvgcqStW0wgK88F+84Bdx+j9bvDyqLEjkjf0PfkHPV/kf2Pt2zqTiIizr8CAwEAAaMhMB8wHQYDVR0OBBYEFC//HOy7pEIKtnpMj4bEMA3oJ39uMA0GCSqGSIb3DQEBCwUAA4IBAQAIYxZXIpwUX8HjSKWUMiyQEn0gRizAyqQhC5wdWOFCBIZPJs8efOkGTsBg/hA+X1fvN6htcBbJRfFfDlP/LkLIVNv2zX4clGM20YhY8FQQh9FWs5qchlnP4lSk7UmScxgT3a6FG3OcLToukNoK722Om2yQ1ayWtn9K82hvZl5L3P8zYaG1gbHPGW5VlNXds60jIpcSWLdU2hacYmwz4pPQyvNOW68aK/Y/tWrJ3DKrf1feDbmm7O5kpWVYWRpah+i6ePjELNkc2Jr+2DchBQTIh9Fxe8sz+9iOyLh9tubMJ+7RTs/ksK0sQ1NVScGFxK+o5hFOOMK7y/F5r467jHez"
]
}

OpenSSl无法直接使用JWK。您必须将其转换为PEM或DER公共密钥文件。 希望这种密钥格式已经包含在JWK本身中:它包含在x5c参数的第一个X.509证书中。

您只需要

  • 稍作修改以获取有效的X.509证书。
$certificate = '-----BEGIN CERTIFICATE----'.PHP_EOL;
$certificate .= chunk_split($key['x5c'][0], 64, PHP_EOL);
$certificate .= '-----END CERTIFICATE-----'.PHP_EOL;
  • 从此证书获取公共密钥
$publicKey = openssl_pkey_get_public($certificate);
  • 通过openssl_verify函数使用该公钥
static::verify("$headb64.$bodyb64", $sig, $publicKey, $header->alg);