如何使用id令牌的at_hash声明来验证访问令牌?

时间:2015-05-20 17:26:28

标签: google-oauth google-openid

在交换从/token端点(using this example OAuth Playground request)获得的代码后,我说来自Google的OAuth2 /auth端点的响应如下:

{
  "access_token": "ya29.eQETFbFOkAs8nWHcmYXKwEi0Zz46NfsrUU_KuQLOLTwWS40y6Fb99aVzEXC0U14m61lcPMIr1hEIBA", 
  "token_type": "Bearer", 
  "expires_in": 3600, 
  "refresh_token": "1/ZagesePFconRc9yQbPxw2m1CnXZ5MNnni91GHxuHm-A", 
  "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjJhODc0MjBlY2YxNGU5MzRmOWY5MDRhMDE0NzY4MTMyMDNiMzk5NGIifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTEwMTY5NDg0NDc0Mzg2Mjc2MzM0IiwiYXpwIjoiNDA3NDA4NzE4MTkyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXRfaGFzaCI6ImFVQWtKRy11Nng0UlRXdUlMV3ktQ0EiLCJhdWQiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJpYXQiOjE0MzIwODI4NzgsImV4cCI6MTQzMjA4NjQ3OH0.xSwhf4KvEztFFhVj4YdgKFOC8aPEoLAAZcXDWIh6YBXpfjzfnwYhaQgsmCofzOl53yirpbj5h7Om5570yzlUziP5TYNIqrA3Nyaj60-ZyXY2JMIBWYYMr3SRyhXdW0Dp71tZ5IaxMFlS8fc0MhSx55ZNrCV-3qmkTLeTTY1_4Jc"
}

如何对访问令牌进行哈希处理,以便将其与ID令牌的at_hash声明进行比较?

我可以在服务器上本地验证ID令牌以防止客户端修改,并且想要验证访问令牌是使用id令牌发出的令牌(暗示受众和主题与ID令牌相匹配)

5 个答案:

答案 0 :(得分:11)

OpenID Connect的at_hash ID令牌声明为defined

  

访问令牌哈希值。它的值是base64url编码   ASCII的八位字节的最左半部分   access_token值的表示,其中哈希算法   used是ID的alg Header参数中使用的哈希算法   令牌的JOSE标题。例如,如果alg是RS256,则哈希   使用SHA-256的access_token值,然后取最左边的128位和   base64url对它们进行编码。 at_hash值是区分大小写的字符串。

混合流的c_hash ID令牌声明为defined similarly,可以使用相同的步骤来验证。

从令牌生成at_hashc_hash的步骤:

  1. 使用与ID令牌本身相同的alg哈希表示令牌的ASCII表示,在Google的情况下使用SHA-256。
  2. 将哈希截断到原始哈希值的前半部分 (重要的是:不是哈希的字符串十六进制表示。)
  3. Base64url编码(不填充)截断的哈希字节。
  4. 这里有一些python中的示例代码来创建该哈希值,您需要两个库,pycryptogoogle-api-python-client(用于base64编码和ID令牌比较,你可以替代替代方案)。您可以像这样安装pip:

    pip install pycrypto
    pip install --upgrade google-api-python-client
    

    然后,以交互方式运行python,并尝试以下操作:

    # Config: app's client id & tokens (in this case OAuth Playground's client id, and the tokens were extracted from the Token Endpoint response).
    client_id = "407408718192.apps.googleusercontent.com"
    id_token_string = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjcwZjZjNDI2NzkyNWIzMzEzNmExZDFjZmVlNGViYzU3YjI0OWU1Y2IifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiYXRfaGFzaCI6Iml5VkFfTnNtY2JJMDFHcFJDQVJaOEEiLCJhdWQiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTAxNjk0ODQ0NzQzODYyNzYzMzQiLCJhenAiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJpYXQiOjE0NjcyMTg1NzMsImV4cCI6MTQ2NzIyMjE3M30.e4hJJYeUaFVwJ9OC8LBnmOjwZln_E2-isEUJtb-Um7vt3GDZnBZkHdCokAPBL4OW3DXBNPk9iY0QL2P5Gpb-nX_s-PZKOIES8CE0i2DmGahCZgJY_Y3V2qwiP1fTEQjcUmHEG2e7OdCn6siSZveFQ0W7SiSbbSeJVLws9aoHROo_UXy8CVjaU5KinROG6m6igqCxFoskIWRzAynfx70xMadY4UdS8kbKK_v5id0_Rdg_gYlF1ND0lsPM9vdm3jOifQEAAkjHr-RuSDWlX4Bs4cQtEkeQkN6--MWhoqAshJITuGSazVIiDkVUNNBIXmB_dp9TO6ZjeQEEfeGCs6axKA"
    access_token = "ya29.Ci8QA5eGBdBglK59FXdqXIR5KnbMJs-swx6Alk6_AV_6YPkjhxdO1e0Hqxi-8NB3Ww"
    
    # Verifies & parses id token.
    idtoken = oauth2client.client.verify_id_token(id_token_string, client_id)
    
    # Token to hash & expected hash value (replace with code & c_hash to verify code).
    token_to_hash = access_token
    token_hash_expected = idtoken["at_hash"]
    
    # Step 1. hashes the access token using SHA-256 (Google uses `RS256` as the ID Token `alg`).
    hash = hashlib.sha256()
    hash.update(token_to_hash)
    digest = hash.digest()   # this returns the hash digest bytes (not a hex string)
    
    # Step 2. truncates the hash digest to the first half.
    digest_truncated = digest[:(len(digest)/2)]
    
    # Step 3. base64url encodes the truncated hash digest bytes.
    token_hash_computed = oauth2client.crypt._urlsafe_b64encode(digest_truncated)
    
    # Compares computed to expected, outputs result.
    str("Computed at_hash: %s" % token_hash_computed)
    str(token_hash_computed == token_hash_expected)
    

    要使用您自己帐户中的新ID令牌尝试此示例,请使用profile范围(或使用OAuth Playground)的this one创建请求,更换代码以进行刷新和访问令牌,并将响应复制到上面示例中的token_response_http_body(删除换行符)。

答案 1 :(得分:1)

PHP解决方案:

$accessToken = 'xxx';
$idToken = 'yyy';
$client = new Google_Client();

$verification = $client->verifyIdToken($idToken);

$hash = hash('sha256', $accessToken);
$hash = substr($hash, 0, 32);
$hash = hex2bin($hash);
$hash = base64_encode($hash);
$hash = rtrim($hash, '=');
$hash = str_replace('/', '_', $hash);
$hash = str_replace('+', '-', $hash);

if ($hash === $verification['at_hash']) {
    // access token is valid
}

Google_Client在这里可用:https://packagist.org/packages/google/apiclient

答案 2 :(得分:1)

我将根据OpenID Connect Core规范(read here)给出答案。查看3.2.2.9节,客户端可以验证由授权服务器使用ID令牌提供的访问令牌。

步骤如下:

  1. 使用以下命令散列access_token的ASCII表示形式的八位字节 JWA中为alg标头参数指定的哈希算法 ID令牌的JOSE标头。例如,如果alg是RS256,则 使用的哈希算法是SHA-256。
  2. 将哈希的最左半部分和base64url进行编码。
  3. ID令牌中at_hash的值必须与上一步中产生的值匹配。

第1步要求客户知道使用什么算法来签名 ID令牌。可以通过解码ID令牌并检查 alg属性的标题部分。假设alg等于 如果使用RS256,则用于创建at_hash的哈希算法为SHA-256。如果 是RS384,然后是散列算法SHA-384,依此类推, 重点。

第2步需要将哈希值减半,然后将左半部分取为 应用base64url编码。

然后,第3步期望ID令牌中的at_hash值等于 在步骤1和2中完成的哈希运算。如果不相等,则 没有使用指定的ID令牌颁发访问令牌。

PHP实现大致如下:

public function verifyToken($id_token, $access_token)
{
    $header = $this->decodeJWT($id_token, 0);
    $claims = $this->decodeJWT($id_token, 1);

    return $this->createAtHash($access_token, $header['alg']) === $claims['at_hash'];
}

public function decodeJWT($jwt, $section = 0) 
{    
    $parts = explode(".", $jwt);

    return json_decode(base64url_decode($parts[$section]));
}

public function createAtHash($access_token, $alg)
{
    // maps HS256 and RS256 to sha256, etc.
    $hash_algorithm = 'sha' . substr($alg, 2);
    $hash = hash($hash_algorithm, $access_token, true);
    $at_hash = substr($hash, 0, strlen($hash) / 2);

    return $this->urlSafeB64Encode($at_hash);
}

public function urlSafeB64Encode($data)
{
    $b64 = base64_encode($data);
    $b64 = str_replace(array('+', '/', "\r", "\n", '='),
            array('-', '_'),
            $b64);

    return $b64;
}

通过您的ID令牌和访问令牌呼叫verifyToken。如果哈希匹配,它将返回true,反之则返回false。

答案 3 :(得分:0)

基本Java解决方案:

private static final String acccesToken = "rvArgQKPbBDJkeTHwoIAOQVkV8J0_i8PhrRKyLDaKkk.iY6nzJoIb2dRXBoqHAa3Yb6gkHveTXbnM6PGtmoKXvo";

public static void main(String[] args) throws NoSuchAlgorithmException {
    MessageDigest md = MessageDigest.getInstance("SHA-256");
    byte[] asciiValue = acccesToken.getBytes(StandardCharsets.US_ASCII);
    byte[] encodedHash = md.digest(asciiValue);
    byte[] halfOfEncodedHash = Arrays.copyOf(encodedHash, (encodedHash.length / 2));
    System.out.println("at_hash generated from access-token: " + Base64.getUrlEncoder().withoutPadding().encodeToString(halfOfEncodedHash));
}

答案 4 :(得分:0)

C#解决方案,尽管我不确定它是否在所有情况下均有效:

using System.Linq;
using System.Security.Cryptography;
using System.Text;

    static readonly char[] padding = { '=' };

    private static string CreateGoogleAtHash(string accessToken)
    {
        using (SHA256 sha256Hash = SHA256.Create())
        {
            byte[] bytes = sha256Hash.ComputeHash(Encoding.ASCII.GetBytes(accessToken));
            byte[] firstHalf = bytes.Take(bytes.Length / 2).ToArray();

            return System.Convert.ToBase64String(firstHalf).TrimEnd(padding).Replace('+', '-').Replace('/', '_');
        }
    }