Unsupported grant type in Google OAuth

和自甴很熟 提交于 2019-12-23 03:14:19

问题


I am getting an error "unsupported grant type" when I try to request an OAuth token for a service account using curl. I'm following the example for OAuth 2.0 for service accounts (https://developers.google.com/identity/protocols/OAuth2ServiceAccount) and I think I have everything setup correctly. I have a service account setup in Google Cloud and I'm using that email address in the OAuth request.

The documentation says to use the URL encoded grant type "urn:ietf:params:oauth:grant-type:jwt-bearer" but it isn't clear if this is the only option for the grant type or what other options might be.

I am sending the the base64 encoded header

{"alg":"RS256","typ":"JWT"}

and "." and base64 encoded claims

{
  "iss":"chargepubadmin@xxxxxxxx.iam.gserviceaccount.com",
  "scope":"https://www.googleapis.com/auth/pubsub",
  "aud":"https://www.googleapis.com/oauth2/v4/token",
  "exp":1497159875,
  "iat":1497156275
}

and "." and base64 encoded signature

{base64 header}.{base64 claims}

.

curl -X POST -d 'grant_type=http%3A%2F%2Foauth.net%2Fgrant_type%2Fdevice%2F1.0%26assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.ew0KICAiaXNzIjoiY2.......' "https://www.googleapis.com/oauth2/v4/token"

I'm using an online base64 encoding tool which matches the example base64 encoding.

Can anyone enlighten me as to the what the grant type is or should be?


回答1:


The grant type should be set as urn:ietf:params:oauth:grant-type:jwt-bearer documented here under the REST API Making the access token request section.

Working example using google-auth library

It will be very easy and simple, if you used the google-auth library which automatically takes care of parsing the private key json file, fetching access tokens, refreshing them and actually including them as part of the requests.

You only need to provide the request URL and body, the library takes care of the rest. Here is a simplified example:

#!/usr/bin/env python

from google.auth.transport.requests import AuthorizedSession
from google.oauth2.service_account import Credentials

# BEGIN CONFIGURATION - change as needed.
# Path to the JSON file containing the service account private key and email.
PRIVATE_KEY_JSON = '/path/to/json/file'
# The API scope this token will be valid for.
API_SCOPES = ['https://www.googleapis.com/auth/pubsub']
# END CONFIGURATION

if __name__ == '__main__':
  credentials = Credentials.from_service_account_file(
      PRIVATE_KEY_JSON, scopes=API_SCOPES)
  authed_session = AuthorizedSession(credentials)
  url = 'https://pubsub.googleapis.com/v1/<SOMETHING>'
  response = authed_session.get(url)
  print str(response.content)

Working example without additional libraries

If you do not want to use any additional libraries but can use the standard python libraries, here is a working sample (tested personally with a service account of my own) in Python (supports both 2.x and 3.x versions) which takes care of all the steps:

#!/usr/bin/env python

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 json
import time

try:
    from urllib.request import urlopen
except ImportError:
    from urllib2 import urlopen

try:
    from urllib.parse import urlencode
except ImportError:
    from urllib import urlencode


# BEGIN CONFIGURATION - change as needed.

# Path to the JSON file containing the service account private key and email.
PRIVATE_KEY_JSON = '/path/to/json/file'

# The API scope this token will be valid for.
API_SCOPE = 'https://www.googleapis.com/auth/pubsub'
# The validity of the token in seconds. Max allowed is 3600s.
ACCESS_TOKEN_VALIDITY_SECS = 3600

# END CONFIGURATION


class OauthAccessTokenGetter:
    """Fetches a new Google OAuth 2.0 access token.

    The code is based on the steps described here: https://developers.go
    ogle.com/identity/protocols/OAuth2ServiceAccount#authorizingrequests

    """

    ACCESS_TOKEN_AUD = 'https://www.googleapis.com/oauth2/v4/token'
    REQUEST_URL = 'https://www.googleapis.com/oauth2/v4/token'
    GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer'

    def __init__(self, private_key_json_file, scope, token_valid_secs=3600):
        self.private_key_json = self.LoadPrivateKeyJsonFromFile(
            private_key_json_file)
        self.scope = scope
        self.token_valid_secs = token_valid_secs

    @classmethod
    def Base64UrlEncode(cls, data):
        """Returns the base64url encoded string for the specified data."""
        return base64.urlsafe_b64encode(data)

    @classmethod
    def LoadPrivateKeyJsonFromFile(cls, private_key_json_file):
        """Returns JSON object by parsing the specified private key JSON
        file."""
        with open(private_key_json_file) as private_key_json_file:
            return json.load(private_key_json_file)

    def GetPrivateKey(self):
        """Returns the imported RSA private key from the JSON data."""
        return RSA.importKey(self.private_key_json['private_key'])

    def GetSigner(self):
        """Returns a PKCS1-V1_5 object for signing."""
        return PKCS1_v1_5.new(self.GetPrivateKey())

    @classmethod
    def GetEncodedJwtHeader(cls):
        """Returns the base64url encoded JWT header."""
        return cls.Base64UrlEncode(json.dumps({'alg': 'RS256', 'typ': 'JWT'}).encode('utf-8'))

    def GetEncodedJwtClaimSet(self):
        """Returns the base64url encoded JWT claim set."""
        current_time_secs = int(time.time())
        jwt_claims = {
            'iss': self.private_key_json['client_email'],
            'scope': self.scope,
            'aud': self.ACCESS_TOKEN_AUD,
            'exp': current_time_secs + self.token_valid_secs,
            'iat': current_time_secs
        }
        return self.Base64UrlEncode(json.dumps(jwt_claims).encode('utf-8'))

    def GetJwtSignature(self, message):
        """Returns signature of JWT as per JSON Web Signature (JWS) spec."""
        signed_message = self.GetSigner().sign(SHA.new(message))
        return self.Base64UrlEncode(signed_message)

    def GetSignedJwt(self):
        """Returns signed JWT."""
        header = self.GetEncodedJwtHeader()
        jwt_claim_set = self.GetEncodedJwtClaimSet()
        signature = self.GetJwtSignature(header + b'.' + jwt_claim_set)
        return header + b'.' + jwt_claim_set + b'.' + signature

    def SendRequest(self, body):
        """Returns the response by sending the specified request."""
        return urlopen(self.REQUEST_URL, urlencode(body).encode('utf-8')).read()

    def GetAccessToken(self):
        """Returns the access token."""
        body = {
            'grant_type': self.GRANT_TYPE,
            'assertion': self.GetSignedJwt()
        }
        response = json.loads(self.SendRequest(body))
        return response['access_token']


if __name__ == '__main__':
    print (OauthAccessTokenGetter(PRIVATE_KEY_JSON, API_SCOPE,
                                  ACCESS_TOKEN_VALIDITY_SECS).GetAccessToken())

After you get the access token, you need to include it as the Bearer header in the requests you send as described here.

GET /drive/v2/files HTTP/1.1
Authorization: Bearer <access_token>
Host: www.googleapis.com/

Equivalently in curl as:

curl -H "Authorization: Bearer <access_token>" https://www.googleapis.com/drive/v2/files

Although it is described here that you can specify the token using access_token= parameter, I could not get it working at least for Google Compute Engine APIs, may be it works with PubSub, but the Bearer header approach has worked always in my experience.

UPDATE: As per the discovery doc for PubSub API, there seems to be a query parameter for access_token=, so it might very well work too.

"access_token": {
      "description": "OAuth access token.",
      "type": "string",
      "location": "query"
    },

And the discovery doc for Compute Engine APIs indicate the use of oauth_token query parameter instead and I did verify that it worked.

"oauth_token": {
   "type": "string",
   "description": "OAuth 2.0 token for the current user.",
   "location": "query"
  },


来源:https://stackoverflow.com/questions/44480882/unsupported-grant-type-in-google-oauth

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