在GAE上使用Python通过Github API进行身份验证
我在使用GAE(Google App Engine)作为应用程序时,遇到了在Github API上进行身份验证的问题(GAE在我使用Github3时会抛出异常)。
import os, sys
sys.path.append("lib")
import jinja2, webapp2, urllib
from google.appengine.api import users, oauth, urlfetch
JINJA_ENVIRONMENT = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
extensions=['jinja2.ext.autoescape'],
autoescape=True)
class ConsoleLogin(webapp2.RequestHandler):
def get(self):
google_user = users.get_current_user()
if google_user:
fields = {
"client_id" : os.environ.get('CLIENT_ID'),
"scope" : "user, repo"
}
url = 'https://github.com/login/oauth/authorize'
data = urllib.urlencode(fields)
result = urlfetch.fetch(url=url,
payload=data,
method=urlfetch.GET
)
在这段代码之后,你应该能从Github那里获取一个临时代码。
问题是:我根本找不到这个代码。我在指南中看到,应该把它作为环境变量获取,但我看不到它。
如果有人能帮我完成这个Python脚本,我会非常感激。;)
3 个回答
你在运行OAuth授权请求时似乎缺少了一些必要的内容。根据GitHub API文档,你需要向授权请求传递4个参数(这也是OAuth 2协议的标准要求):
- 第一个是
client_id
(这个是你在GitHub应用注册时获得的 - 你确定它在操作系统的环境变量中吗?你自己放进去的吗?为了测试,你可以先把它直接写成字符串放在代码里;等一切正常后再改进会更好); - 第二个是
scope
(你已经定义了这个 - 这没问题); - 第三个是你选择的一个随机
state
,GitHub会在下一步把它返回给你; - 最后也是最重要的,是
redirect_uri
,这是GitHub在用户允许访问其账户后会把客户端转发到的地址:这个地址必须是你自己网站上的一个URL,你需要处理这个地址来获取code
和state
参数。
举个例子,redirect_uri
可以是http://localhost:8080/oauth/accept_github
,然后你需要准备你的app.yaml
文件和Python代码来处理发送到/oauth/accept_github
的请求。在处理这些请求的代码中,尝试显示self.request.get('state')
和self.request.get('code')
的内容:如果一切正常,它们应该包含GitHub API返回给你的内容。现在你准备好进入下一步了:把你的code
转换成access_token
:)
这里是GitHub的oAuth认证的实际实现。这个实现是基于Flask框架的,而不是Webapp2,不过你可以很容易地把这个处理程序移植到Webapp2上。你可以看看一个名为gae-init的gae启动项目,特别的代码片段来自一个支持多种oAuth提供者的分支gae-init-auth。(注意:@github.tokengetter
这个装饰器是由flask_oauth.py
提供的)
github_oauth = oauth.OAuth()
github = github_oauth.remote_app(
'github',
base_url='https://api.github.com/',
request_token_url=None,
access_token_url='https://github.com/login/oauth/access_token',
authorize_url='https://github.com/login/oauth/authorize',
consumer_key=config.CONFIG_DB.github_client_id,
consumer_secret=config.CONFIG_DB.github_client_secret,
request_token_params={'scope': 'user:email'},
)
@app.route('/_s/callback/github/oauth-authorized/')
@github.authorized_handler
def github_authorized(resp):
if resp is None:
return 'Access denied: error=%s' % flask.request.args['error']
flask.session['oauth_token'] = (resp['access_token'], '')
me = github.get('user')
user_db = retrieve_user_from_github(me.data)
return signin_user_db(user_db)
@github.tokengetter
def get_github_oauth_token():
return flask.session.get('oauth_token')
@app.route('/signin/github/')
def signin_github():
return github.authorize(
callback=flask.url_for('github_authorized',
next=util.get_next_url(),
_external=True,
)
)
def retrieve_user_from_github(response):
auth_id = 'github_%s' % str(response['id'])
user_db = model.User.retrieve_one_by('auth_ids', auth_id)
if user_db:
return user_db
return create_user_db(
auth_id,
response['name'] or response['login'],
response['login'],
response['email'] or '',
)
我不是说这个代码好看——它确实不好看。这段代码丑得很,但它在使用GAE(谷歌应用引擎)、Webapp2和urllib2时能正常工作,而不是使用其他框架或库。
import os, sys, cgi, json, cookielib
sys.path.append("lib")
import jinja2, webapp2, urllib, urllib2
from google.appengine.api import users, oauth, urlfetch
from webapp2_extras import sessions
JINJA_ENVIRONMENT = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
extensions=['jinja2.ext.autoescape'],
autoescape=True)
class BaseHandler(webapp2.RequestHandler):
def dispatch(self):
# Get a session store for this request.
self.session_store = sessions.get_store(request=self.request)
try:
# Dispatch the request.
webapp2.RequestHandler.dispatch(self)
finally:
# Save all sessions.
self.session_store.save_sessions(self.response)
@webapp2.cached_property
def session(self):
# Returns a session using the default cookie key.
return self.session_store.get_session()
class ConsoleLogin(BaseHandler):
def get(self):
# Set variables to avoid problems later
code = ''
url = ''
access_token = ''
scope = ''
username = ''
google_user = users.get_current_user()
if google_user:
url = self.request.url
if ('code' not in url and not self.session.get('access_token')):
# First time user coming to site. Redirect to GH for code
url = 'https://github.com/login/oauth/authorize?scope=user,repo&client_id=' + os.environ.get('CLIENT_ID')
self.redirect(url)
elif 'code' in url:
# User has been to GH, continue with auth process
code = url.replace('http://localhost:8080/?code=', '')
# We have code, now get Access Token
fields = {
"client_id" : os.environ.get('CLIENT_ID'),
"client_secret" : os.environ.get('CLIENT_SECRET'),
"code" : code
}
url = 'https://github.com/login/oauth/access_token'
data = urllib.urlencode(fields)
result = urlfetch.fetch(url=url,
payload=data,
method=urlfetch.POST
)
# Get the query string
query_string = str(result.content)
# Get the access token out of the full query string
access_token = query_string[13:]
end_access = access_token.find('&')
access_token = access_token[:end_access]
# Get the scope out of the full query string
start_scope = query_string.find('scope')
end_scope = query_string.find('token_type')
start_scope = start_scope + 6 # remove the word 'scope='
end_scope = end_scope - 1 # remove the & symobol
scope = query_string[start_scope:end_scope]
scope = scope.split('%2C')
# Store the Access Token in a Session Variable
self.session['access_token'] = access_token
self.session['scope'] = scope
# And redirect to the base URL for neatness and to avoid other issues
self.redirect('/')
access_token = self.session.get('access_token')
scope = self.session.get('scope')
context = {
'access_token' : access_token,
'scope' : scope,
'username' : username,
}
# Template Settings
temp = 'templates/index.html'
template = JINJA_ENVIRONMENT.get_template(temp)
self.response.write(template.render(context))
config = {}
config['webapp2_extras.sessions'] = {
'secret_key': 'the-beatles-will-always-rule',
}
application = webapp2.WSGIApplication([
('/', ConsoleLogin),
], debug=True, config=config)