使用oauth2服务帐号在Python中验证Google API

3 投票
4 回答
6093 浏览
提问于 2025-04-18 18:06

我按照这个链接上的说明,想用服务账号来认证谷歌云存储的API。我在用Python发送一个JWT(JSON Web Token)到谷歌的认证服务器时,遇到了一个错误:

urllib2.HTTPError: HTTP Error 400: Bad Request

看起来我在创建、签名或发送这个JWT的过程中出了问题?错误信息没有具体说明,所以可能是任何一个环节出了问题。有没有人能给点建议?

import Crypto.PublicKey.RSA as RSA
import Crypto.Hash.SHA as SHA
import Crypto.Signature.PKCS1_v1_5 as PKCS1_v1_5
import base64
import json
import time
import urllib2
import urllib

# Settings
json_key_file = 'GooglePM-9f75ad112f87-service.json'

# Load the private key associated with the Google service account
with open(json_key_file) as json_file:
    json_data = json.load(json_file)
    key = RSA.importKey(json_data['private_key'])

# Create an PKCS1_v1_5 object
signer = PKCS1_v1_5.new(key)

# Encode the JWT header
header_b64 = base64.urlsafe_b64encode(json.dumps({'alg':'RS256','typ':'JWT'}))

# JWT claims
jwt = {
    'iss': json_data['client_email'],
    'scope': 'https://www.googleapis.com/auth/devstorage.read_write',
    'aud': 'https://accounts.google.com/o/oauth2/token',
    'exp': int(time.time())+3600,
    'iat': int(time.time())
    }
jwt_json = json.dumps(jwt)

# Encode the JWT claims
jwt_json_b64 = base64.urlsafe_b64encode(jwt_json)

# Sign the JWT header and claims
msg_hash = SHA.new(header_b64 + "." + jwt_json_b64)
signature_b64 = base64.urlsafe_b64encode(signer.sign(msg_hash))

# Make the complete message
jwt_complete = header_b64 + "." + jwt_json_b64 + "." + signature_b64

data = {'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', 
    'assertion': jwt_complete}

f = urllib2.urlopen("https://accounts.google.com/o/oauth2/token", urllib.urlencode(data))

print f.read()

如果我尝试用curl命令向服务器发送请求,就会收到无效授权的错误:

(venv)$ curl -d 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=eyJhbGciOiAiUlMyNTYiLCAidHlwIjogIkpXVCJ9.eyJpc3MiOiAiMTM1MDY3NjIyMTk4LWVhbWUwZnFqdTNvamRoZ29zdDg2dnBpdTBsYW91NnZlQGRldmVsb3Blci5nc2VydmljZWFjY291bnQuY29tIiwgInNjb3BlIjogImh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvZGV2c3RvcmFnZS5yZWFkX3dyaXRlIiwgImF1ZCI6ICJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20vby9vYXV0aDIvdG9rZW4iLCAiZXhwIjogMTQwODY1MTU2OCwgImlhdCI6IDE0MDg2NDg1NTh9.HWC7h3QiOy7QsSuta4leq_Gjwmy9IdF-MUwflPhiohzAJ-Amykd56Ye4Y_Saf_sAc5STzOCmrSPzOTYvGXr6X_T_AmSTxXK2AJ2SpAiEUs2_Wp5h18xTUY3Y_hkKvSZLh5bRzeJ_0xRcmRIPE6tua0FHFwUDdnCIGdh4DGg6i4E%3D' https://accounts.google.com/o/oauth2/token
{
  "error" : "invalid_grant"
}

4 个回答

1

最初的代码在Python 3.6中也能正常运行。你只需要在处理base64的时候用字节(bytes)代替字符串(str)就可以了:

import requests
import json as js
import Crypto.PublicKey.RSA as RSA
import Crypto.Hash.SHA256 as SHA
import Crypto.Signature.PKCS1_v1_5 as PKCS1_v1_5
import base64
import time

# Settings
json_key_file = 'google-api.json'

# Load the private key associated with the Google service account
with open(json_key_file) as json_file:
    json_data = js.load(json_file)
    key = RSA.importKey(json_data['private_key'])

# Create an PKCS1_v1_5 object
signer = PKCS1_v1_5.new(key)

header = js.dumps({'alg':'RS256','typ':'JWT'})

# Encode the JWT header
header_b64 = base64.urlsafe_b64encode(header.encode("UTF-8"))

# JWT claims
jwt = {
    'iss': json_data['client_email'],
    'scope': 'https://www.googleapis.com/auth/analytics.readonly',
    'aud': 'https://accounts.google.com/o/oauth2/token',
    'exp': int(time.time())+3600,
    'iat': int(time.time())
    }
jwt_json = js.dumps(jwt)

# Encode the JWT claims
jwt_json_b64 = base64.urlsafe_b64encode(jwt_json.encode("UTF-8"))

# Sign the JWT header and claims
msg_hash = SHA.new((header_b64.decode("UTF-8") + "." + jwt_json_b64.decode("UTF-8")).encode("UTF-8"))
signature_b64 = base64.urlsafe_b64encode(signer.sign(msg_hash))

# Make the complete message
jwt_complete = (header_b64.decode("UTF-8") + "." + jwt_json_b64.decode("UTF-8") + "." + signature_b64.decode("UTF-8")).encode("UTF-8")

data = {'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', 
'assertion': jwt_complete}

requests.post(url="https://accounts.google.com/o/oauth2/token",data=data).text
1

你的第一个算法完全没问题,运行得很好,不过你需要用SHA256,而不是SHA。谢谢你的代码。

3

也许,这样说会简单一点:

import oauth2client.service_account

jsonfile = 'GooglePM-9f7sdf342f87-service.json'
# use static method .from_json_keyfile_name(filename)
credentials = oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name(jsonfile)
8

好的,其实有更简单的方法来做这个!谷歌已经提供了一个Python客户端API,可以帮你处理一些复杂的事情。下面的代码在安装了谷歌的Python客户端API之后就可以运行了: https://developers.google.com/api-client-library/python/guide/aaa_oauth

from oauth2client.client import SignedJwtAssertionCredentials
import json
import urllib
import urllib2

# Settings
json_key_file = 'GooglePM-9f75ad112f87-service.json'

# Load the private key associated with the Google service account
with open(json_key_file) as json_file:
    json_data = json.load(json_file)

# Get and sign JWT
credential = SignedJwtAssertionCredentials(json_data['client_email'], json_data['private_key'], 'https://www.googleapis.com/auth/devstorage.read_write')
jwt_complete = credential._generate_assertion()

# Get token from server
data = {'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', 
    'assertion': jwt_complete}
f = urllib2.urlopen("https://accounts.google.com/o/oauth2/token", urllib.urlencode(data))

print f.read()

撰写回答