Use oauth2 service account to authenticate to Google API in python

大兔子大兔子 提交于 2019-12-22 09:46:17

问题


I've followed the directions in https://developers.google.com/accounts/docs/OAuth2ServiceAccount to use a service account to authenticate to the Google Cloud Storage API. I tried to send a JWT to google's authenticate servers in python, but got an error:

urllib2.HTTPError: HTTP Error 400: Bad Request

It looks like there's something wrong with the way I'm making, signing, or sending the JWT? The error wasn't specific so it could be any part of the process. Does anyone have any ideas?

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()

If I try to use curl to post to the server, I get the invalid grants error:

(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"
}

回答1:


Ok so there's a better way to do this! Google already has a python client API that handles some of the complexity. The following code works after installing google python client 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()



回答2:


maybe, slightly simplier:

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)



回答3:


Your first algorithm is completely ok and working, but you need SHA256, not SHA. Thank you for your code.




回答4:


Initial code is also working using Python 3.6. You only have to use bytes instead of str in case of base64:

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


来源:https://stackoverflow.com/questions/25433507/use-oauth2-service-account-to-authenticate-to-google-api-in-python

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!