在交换从/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令牌相匹配)
答案 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_hash
或c_hash
的步骤:
alg
哈希表示令牌的ASCII表示,在Google的情况下使用SHA-256。 这里有一些python中的示例代码来创建该哈希值,您需要两个库,pycrypto
和google-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步要求客户知道使用什么算法来签名 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('/', '_');
}
}