【问题标题】:How to programmatically retrieve access_token from client-side OAuth flow using Python?如何使用 Python 以编程方式从客户端 OAuth 流中检索 access_token?
【发布时间】:2012-01-08 17:14:59
【问题描述】:

这个问题是posted on StackApps,但这个问题可能更像是一个编程问题而不是身份验证问题,因此它可能应该放在更好的地方。

我正在为 StackOverflow 开发桌面收件箱通知程序,使用 Python 的 API。

我正在编写的脚本首先将用户登录到 StackExchange,然后请求应用程序的授权。假设应用程序已通过用户的 Web 浏览器交互获得授权,应用程序应该能够通过身份验证向 API 发出请求,因此它需要特定于用户的访问令牌。这是通过 URL 完成的:https://stackexchange.com/oauth/dialog?client_id=54&scope=read_inbox&redirect_uri=https://stackexchange.com/oauth/login_success

当通过网络浏览器请求授权时,重定向正在发生,并在# 之后返回访问代码。 但是,当使用 Python (urllib2) 请求相同的 URL 时,响应中不会返回哈希或键。

为什么我的 urllib2 请求与在 Firefox 或 W3m 中发出的相同请求的处理方式不同?我应该如何以编程方式模拟此请求并检索access_token

这是我的脚本(它是实验性的)并记住:它假定用户已经授权了应用程序。

#!/usr/bin/env python

import urllib
import urllib2
import cookielib
from BeautifulSoup import BeautifulSoup
from getpass import getpass    

# Define URLs
parameters = [ 'client_id=54',
               'scope=read_inbox',
               'redirect_uri=https://stackexchange.com/oauth/login_success'
             ]

oauth_url = 'https://stackexchange.com/oauth/dialog?' + '&'.join(parameters)
login_url = 'https://openid.stackexchange.com/account/login'
submit_url = 'https://openid.stackexchange.com/account/login/submit'
authentication_url = 'http://stackexchange.com/users/authenticate?openid_identifier='

# Set counter for requests:
counter = 0

# Build opener
jar = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(jar))

def authenticate(username='', password=''):

    '''
        Authenticates to StackExchange using user-provided username and password
    '''

    # Build up headers
    user_agent = 'Mozilla/5.0 (Ubuntu; X11; Linux i686; rv:8.0) Gecko/20100101 Firefox/8.0'
    headers = {'User-Agent' : user_agent}

    # Set Data to None
    data = None

    # 1. Build up URL request with headers and data    
    request = urllib2.Request(login_url, data, headers)
    response = opener.open(request)

    # Build up POST data for authentication
    html = response.read()
    fkey = BeautifulSoup(html).findAll(attrs={'name' : 'fkey'})[0].get('value').encode()

    values = {'email' : username,
              'password' : password,
              'fkey' : fkey}

    data = urllib.urlencode(values)

    # 2. Build up URL for authentication
    request = urllib2.Request(submit_url, data, headers)
    response = opener.open(request)

    # Check if logged in
    if response.url == 'https://openid.stackexchange.com/user':
        print ' Logged in! :) '
    else:
        print ' Login failed! :( '

    # Find user ID URL    
    html = response.read()
    id_url = BeautifulSoup(html).findAll('code')[0].text.split('"')[-2].encode()

    # 3. Build up URL for OpenID authentication
    data = None
    url = authentication_url + urllib.quote_plus(id_url)
    request = urllib2.Request(url, data, headers)
    response = opener.open(request)

    # 4. Build up URL request with headers and data
    request = urllib2.Request(oauth_url, data, headers)
    response = opener.open(request)

    if '#' in response.url:
        print 'Access code provided in URL.'
    else:
        print 'No access code provided in URL.'

if __name__ == '__main__':
    username = raw_input('Enter your username: ')
    password = getpass('Enter your password: ')
    authenticate(username, password)

回复下面的cmets:

Firefox 中的篡改数据请求具有以下标头的上述 URL(如代码中的 oauth_url):

Host=stackexchange.com
User-Agent=Mozilla/5.0 (Ubuntu; X11; Linux i686; rv:9.0.1) Gecko/20100101 Firefox/9.0.1
Accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language=en-us,en;q=0.5
Accept-Encoding=gzip, deflate
Accept-Charset=ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection=keep-alive
Cookie=m=2; __qca=P0-556807911-1326066608353; __utma=27693923.1085914018.1326066609.1326066609.1326066609.1; __utmb=27693923.3.10.1326066609; __utmc=27693923; __utmz=27693923.1326066609.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); gauthed=1; ASP.NET_SessionId=nt25smfr2x1nwhr1ecmd4ok0; se-usr=t=z0FHKC6Am06B&s=pblSq0x3B0lC

在 urllib2 请求中,标头仅提供用户代理值。 cookie 没有显式传递,但se-usr 在请求时在 cookie jar 中可用。

响应头将首先是重定向:

Status=Found - 302
Server=nginx/0.7.65
Date=Sun, 08 Jan 2012 23:51:12 GMT
Content-Type=text/html; charset=utf-8
Connection=keep-alive
Cache-Control=private
Location=https://stackexchange.com/oauth/login_success#access_token=OYn42gZ6r3WoEX677A3BoA))&expires=86400
Set-Cookie=se-usr=t=kkdavslJe0iq&s=pblSq0x3B0lC; expires=Sun, 08-Jul-2012 23:51:12 GMT; path=/; HttpOnly
Content-Length=218

然后重定向将通过另一个带有来自该标头的新 se-usr 值的请求进行。

我不知道如何在 urllib2 中捕获 302,它自己处理它(这很棒)。不过,最好看看位置标头中提供的访问令牌是否可用。

最后一个响应头没有什么特别的,Firefox 和 Urllib 都返回类似:

Server: nginx/0.7.65
Date: Sun, 08 Jan 2012 23:48:16 GMT
Content-Type: text/html; charset=utf-8
Connection: close
Cache-Control: private
Content-Length: 5664

希望我没有提供机密信息,如果我提供了请告诉我:D

【问题讨论】:

  • 在黑暗中拍摄,但如果您复制您通过 Firefox 发送的标头,它是否有效?只是想也许他们正在认识到您不是通过浏览器来的。顺便说一句,您通过 urllib2 收到的响应是什么? voidspace.org.uk/python/articles/urllib2.shtml#headers
  • 感谢您的评论:请参阅编辑后的回复。注意:我在标题中提供了用户代理 user_agent = 'Mozilla/5.0 (Ubuntu; X11; Linux i686; rv:8.0) Gecko/20100101 Firefox/8.0'

标签: python oauth urllib2


【解决方案1】:

由于 urllib2 处理重定向的方式,令牌没有出现。具体细节我不熟悉,这里就不细说了。

解决方案是在 urllib2 处理重定向之前捕获 302。这可以通过对urllib2.HTTPRedirectHandler 进行子类化来获得带有其标签和令牌的重定向来完成。这是处理程序子类化的一个简短示例:

class MyHTTPRedirectHandler(urllib2.HTTPRedirectHandler):
    def http_error_302(self, req, fp, code, msg, headers):
        print "Going through 302:\n"
        print headers
        return urllib2.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers)

在标头中,location 属性将提供完整的重定向 URL,即包括主题标签和令牌:

输出摘录:

...
Going through 302:

Server: nginx/0.7.65
Date: Mon, 09 Jan 2012 20:20:11 GMT
Content-Type: text/html; charset=utf-8
Connection: close
Cache-Control: private
Location: https://stackexchange.com/oauth/login_success#access_token=K4zKd*HkKw5Opx(a8t12FA))&expires=86400
Content-Length: 218
...

更多关于在StackOverflow 上使用 urllib2 捕获重定向(当然)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-06-03
    • 2019-07-08
    • 1970-01-01
    • 1970-01-01
    • 2011-03-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多