如何请求使用OpenID的网站页面?

18 投票
5 回答
6603 浏览
提问于 2025-04-17 01:07

这个问题之前有人问过。被接受的答案对提问者和回答者来说可能很明显,但对我来说却不然。我在上面的问题下评论想要更详细的信息,但没有得到回复。我也去问了元社区,想知道如何让问题重新被关注,但也没有得到答案。

上面问题的答案是:

从客户端的角度来看,OpenID登录和其他网页登录非常相似。客户端没有一个明确的协议;这只是一个普通的网页会话,具体情况取决于你的OpenID提供者。因此,我怀疑是否有这样的库存在。你可能需要自己编写代码。

我已经知道如何用Python 登录一个网站,使用的是Urllib2模块。但这还不足以让我猜出如何进行OpenID认证。

我实际上是想获取我的StackOverflow收件箱的json格式,为此我需要先登录。

有没有人能提供一个简单的介绍或者链接到一个不错的教程,教我怎么做?

5 个回答

6

我知道这就像考古学一样,挖掘一个两年前的帖子,但我刚写了一个新版本的代码,改进了之前被验证的答案,所以我觉得在这里分享一下可能会很不错,因为这个问题和答案对我实现这个功能帮助很大。

那么,这里有什么不同呢:

  • 它使用了新的 requests 库,这比 urllib2 更好;
  • 它支持使用谷歌和 StackExchange 的 OpenID 进行身份验证。
  • 代码更短、更简单易读,虽然输出的信息少了一些。

下面是代码:

#!/usr/bin/env python

import sys
import urllib
import requests
from BeautifulSoup import BeautifulSoup

def get_google_auth_session(username, password):
    session = requests.Session()
    google_accounts_url = 'http://accounts.google.com'
    authentication_url = 'https://accounts.google.com/ServiceLoginAuth'
    stack_overflow_url = 'http://stackoverflow.com/users/authenticate'

    r = session.get(google_accounts_url)
    dsh = BeautifulSoup(r.text).findAll(attrs={'name' : 'dsh'})[0].get('value').encode()
    auto = r.headers['X-Auto-Login']
    follow_up = urllib.unquote(urllib.unquote(auto)).split('continue=')[-1]
    galx = r.cookies['GALX']

    payload = {'continue' : follow_up,
               'followup' : follow_up,
               'dsh' : dsh,
               'GALX' : galx,
               'pstMsg' : 1,
               'dnConn' : 'https://accounts.youtube.com',
               'checkConnection' : '',
               'checkedDomains' : '',
               'timeStmp' : '',
               'secTok' : '',
               'Email' : username,
               'Passwd' : password,
               'signIn' : 'Sign in',
               'PersistentCookie' : 'yes',
               'rmShown' : 1}

    r = session.post(authentication_url, data=payload)

    if r.url != authentication_url: # XXX
        print "Logged in"
    else:
        print "login failed"
        sys.exit(1)

    payload = {'oauth_version' : '',
               'oauth_server' : '',
               'openid_username' : '',
               'openid_identifier' : ''}
    r = session.post(stack_overflow_url, data=payload)
    return session

def get_so_auth_session(email, password):
    session = requests.Session()
    r = session.get('http://stackoverflow.com/users/login')
    fkey = BeautifulSoup(r.text).findAll(attrs={'name' : 'fkey'})[0]['value']

    payload = {'openid_identifier': 'https://openid.stackexchange.com',
               'openid_username': '',
               'oauth_version': '',
               'oauth_server': '',
               'fkey': fkey,
               }
    r = session.post('http://stackoverflow.com/users/authenticate', allow_redirects=True, data=payload)
    fkey = BeautifulSoup(r.text).findAll(attrs={'name' : 'fkey'})[0]['value']
    session_name = BeautifulSoup(r.text).findAll(attrs={'name' : 'session'})[0]['value']

    payload = {'email': email,
               'password': password,
               'fkey': fkey,
               'session': session_name}

    r = session.post('https://openid.stackexchange.com/account/login/submit', data=payload)
    # check if url changed for error detection
    error = BeautifulSoup(r.text).findAll(attrs={'class' : 'error'})
    if len(error) != 0:
        print "ERROR:", error[0].text
        sys.exit(1)
    return session

if __name__ == "__main__":
    prov = raw_input('Choose your openid provider [1 for StackOverflow, 2 for Google]: ')
    name = raw_input('Enter your OpenID address: ')
    pswd = getpass('Enter your password: ')
    if '1' in prov:
        so = get_so_auth_session(name, pswd)
    elif '2' in prov:
        so = get_google_auth_session(name, pswd)
    else:
        print "Error no openid provider given"

    r = so.get('http://stackoverflow.com/inbox/genuwine')
    print r.json()

这段代码也可以在 GitHub Gist 上找到。

希望对你有帮助!

11

我自己对OpenID了解不多,但你的帖子(还有悬赏!!)让我产生了兴趣。

这个链接详细介绍了OpenID认证的流程(至少是1.0版本,新版本是2.0)。从我理解的内容来看,步骤大概是这样的:

  1. 你打开stackoverflow的登录页面,那里会有一个使用OpenID登录的选项(作为一个表单字段)。
  2. 你发送你的OpenID,实际上它是一种URI,而不是用户名或邮箱(如果是Google账号,那就是你的个人资料ID)。
  3. 然后stackoverflow会连接到你的ID提供商(在这个例子中是Google),并把你重定向到Google的登录页面,同时给你一个链接,稍后你需要重定向到这个链接(假设是a)。
  4. 你可以在Google提供的页面上正常登录(使用Python的POST方法)。
  5. Google会在你登录请求后返回一个加密的令牌(这一步我不是很确定)。
  6. 你用这个令牌向a发送新的请求。
  7. stackoverflow会用这个令牌联系Google。如果验证通过,它会返回一个会话ID。
  8. 之后对StackOverflow的请求都需要带上这个会话ID。
  9. 关于登出我就不太清楚了!!

这个链接介绍了OpenID中的各种响应及其含义。所以当你编写客户端代码时,可能会很有用。

来自维基页面的链接 OpenID Explained

编辑:使用Firefox的Tamper Data插件,可以构建以下事件序列。

  1. 用户向SO登录页面发送请求。在表单字段中输入OpenID后,结果页面会发送一个302重定向到Google页面。重定向的URL包含很多OpenID参数(这些是给Google服务器的)。其中一个参数是return_to=https://stackoverflow.com/users/authenticate/?s=some_value
  2. 用户看到Google的登录页面。登录后会有几个302重定向,用户在Google的环境中被重定向。
  3. 最后收到一个302重定向,用户被引导到之前在'return_to'中指定的stackoverflow页面。
  4. 在整个操作过程中,会生成很多cookie,必须正确存储。
  5. 访问SO页面(之前被Google重定向的),SO服务器处理你的请求,并在响应头中发送一个“Set-Cookie”字段,设置名为gauth和usr的cookie,并再重定向到stackoverflow.com。这一步完成了你的登录。
  6. 你的客户端只需存储cookie usr。
  7. 只要记得在任何请求中发送Cookie usr,你就已经登录了。
  8. 现在你可以请求你的收件箱,只需记得在请求中发送usr cookie。

我建议你开始编写Python客户端,并仔细研究响应。在大多数情况下,这将是一系列302重定向,用户干预很少(除了填写Google的用户名和密码以及允许网站页面)。

不过为了简化,你可以直接在浏览器中登录SO,复制所有cookie值,然后使用urllib2发送请求,设置这些cookie值。

当然,如果你在浏览器中登出,你需要再次登录并在Python程序中更改cookie值。

3

这个回答总结了下面其他人说的内容,特别是RedBaron的观点,并且添加了一种我用来通过谷歌账户访问StackOverflow收件箱的方法。

使用Firefox的Tamper Data开发者工具并登录StackOverflow,可以看到OpenID的工作原理如下:

  1. StackOverflow向一个指定的服务(这里是谷歌)请求认证,这个服务在发送的数据中定义;
  2. 谷歌账户接管这个过程,检查是否已经存在一个cookie来证明认证;
  3. 如果没有找到cookie,谷歌会请求认证并设置一个cookie;
  4. 一旦cookie被设置,StackOverflow就会确认用户的认证。

以上总结了这个过程,实际上要复杂得多,因为确实会有很多重定向和cookie的交换。

因为用程序重复这个过程有点困难(可能是我不太懂),特别是要找到所有带有地区特定信息的URL。我选择先登录谷歌账户,获取一个合适的cookie,然后再登录StackOverflow,这样就可以用这个cookie进行认证。

这个过程可以简单地使用以下Python模块来完成:urllib、urllib2、cookielib和BeautifulSoup。

这里是(简化版的)代码,虽然不完美,但能达到目的。更详细的版本可以在Github上找到。

#!/usr/bin/env python

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

# Define URLs
google_accounts_url = 'http://accounts.google.com'
authentication_url = 'https://accounts.google.com/ServiceLoginAuth'
stack_overflow_url = 'https://stackoverflow.com/users/authenticate'
genuwine_url = 'https://stackoverflow.com/inbox/genuwine'

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

def request_url(request):    
    '''
        Requests given URL.
    '''     
    try:
        response = opener.open(request)
    except:
        raise
    return response


def authenticate(username='', password=''):        
    '''
        Authenticates to Google Accounts using user-provided username and password,
        then authenticates to StackOverflow.
    '''
    # 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

    # Build up URL request with headers and data    
    request = urllib2.Request(google_accounts_url, data, headers)
    response = request_url(request)

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

    auto = response.headers.getheader('X-Auto-Login')

    follow_up = urllib.unquote(urllib.unquote(auto)).split('continue=')[-1]

    galx = jar._cookies['accounts.google.com']['/']['GALX'].value

    values = {'continue' : follow_up,
              'followup' : follow_up,
              'dsh' : dsh,
              'GALX' : galx,
              'pstMsg' : 1,
              'dnConn' : 'https://accounts.youtube.com',
              'checkConnection' : '',
              'checkedDomains' : '',
              'timeStmp' : '',
              'secTok' : '',
              'Email' : username,
              'Passwd' : password,
              'signIn' : 'Sign in',
              'PersistentCookie' : 'yes',
              'rmShown' : 1}

    data = urllib.urlencode(values)

    # Build up URL for authentication
    request = urllib2.Request(authentication_url, data, headers)
    response = request_url(request)

    # Check if logged in
    if response.url != request._Request__original:
        print '\n Logged in :)\n'
    else:
        print '\n Log in failed :(\n'

    # Build OpenID Data    
    values = {'oauth_version' : '',
              'oauth_server' : '',
              'openid_username' : '',
              'openid_identifier' : 'https://www.google.com/accounts/o8/id'}

    data = urllib.urlencode(values)

    # Build up URL for OpenID authetication
    request = urllib2.Request(stack_overflow_url, data, headers)
    response = request_url(request)

    # Retrieve Genuwine
    data = None
    request = urllib2.Request(genuwine_url, data, headers)
    response = request_url(request)
    print response.read()


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

撰写回答