2010-11-29 113 views
4

我想让我的网站访问者使用他们的Google帐户登录,而不必注册并创建一个新帐户。Django + Google联合登录

有几件事情:

  • 我不使用Django的认证框架,而不是,我做我自己的身份验证和保存有关用户的信息在我自己的一组表
  • 因此,各种的Django的OpenID libs不适用,因为它们都假定使用标准的Django认证框架。

我试图研究python-openid库+谷歌联邦登录API,但我迷路了。我尽可能了解实例化Consumer类,但不理解所需的会话和存储参数。我无法理解那些看起来如此简单的事情可能如此复杂。真的没有一步一步教程如何在纯python或django中做到这一点?

我试着看看python-openid中的examples/consumer.py,但是它再次出现了500行代码,我不明白。

我也不明白如何对我的网站的每个请求完成用户对Google帐户的验证。 Google API仅解释初始登录步骤。每次向我的网站发送的请求都必须通过谷歌服务器验证身份验证时会发生什么?

回答

3

我已成功地使这里demistify问题的解决方案,我希望别人能够从中受益: 1)谷歌帐户验证没有针对您的应用程序的每个请求针对Google帐户服务器完成。例如: 1.1用户使用他们的gmail帐户登录到您的应用程序 1.2用户还导航到gmail.com,在那里他们检查他们的电子邮件 1.3他们注销gmail 1.4他们仍然登录到您的应用程序并可以充分使用它 这意味着您必须在会议结束时处理会话过期,Google帐户不会处理它。

2)I中所用的核心Python代码如下:

from openid.consumer.consumer import Consumer, \ 
    SUCCESS, CANCEL, FAILURE, SETUP_NEEDED 
from openid.consumer.discover import DiscoveryFailure 
from django.utils.encoding import smart_unicode 
from myapp.common.util.openid import DjangoOpenIDStore 

def google_signin(request): 
    """ This is the view where the Google account login icon on your site points to, e.g. http://www.yourdomain.com/google-signin """ 
    consumer = Consumer(request.session, DjangoOpenIDStore()) 

    # catch Google Apps domain that is referring, if any 
    _domain = None 
    if 'domain' in request.POST: 
     _domain = request.POST['domain'] 
    elif 'domain' in request.GET: 
     _domain = request.GET['domain'] 

    try: 
     # two different endpoints depending on whether the using is using Google Account or Google Apps Account 
     if _domain: 
      auth_request = consumer.begin('https://www.google.com/accounts/o8/site-xrds?hd=%s' % _domain) 
     else: 
      auth_request = consumer.begin('https://www.google.com/accounts/o8/id') 
    except DiscoveryFailure as e: 
     return CustomError(request, "Google Accounts Error", "Google's OpenID endpoint is not available.") 

    # add requests for additional account information required, in my case: email, first name & last name 
    auth_request.addExtensionArg('http://openid.net/srv/ax/1.0', 'mode', 'fetch_request') 
    auth_request.addExtensionArg('http://openid.net/srv/ax/1.0', 'required', 'email,firstname,lastname') 
    auth_request.addExtensionArg('http://openid.net/srv/ax/1.0', 'type.email', 'http://schema.openid.net/contact/email') 
    auth_request.addExtensionArg('http://openid.net/srv/ax/1.0', 'type.firstname', 'http://axschema.org/namePerson/first') 
    auth_request.addExtensionArg('http://openid.net/srv/ax/1.0', 'type.lastname', 'http://axschema.org/namePerson/last') 

    return redirect(auth_request.redirectURL('http://www.yourdomain.com', 'http://www.yourdomain.com/google-signin-response'))) 


@transaction.commit_manually 
def google_signin_response(request): 
    """ Callback from Google Account service with login the status. Your url could be http://www.yourdomain.com/google-signin-response """ 
    transaction.rollback() # required due to Django's transaction inconsistency between calls 
    oidconsumer = Consumer(request.session, DjangoOpenIDStore()) 

    # parse GET parameters submit them with the full url to consumer.complete 
    _params = dict((k,smart_unicode(v)) for k, v in request.GET.items()) 
    info = oidconsumer.complete(_params, request.build_absolute_uri().split('?')[0]) 
    display_identifier = info.getDisplayIdentifier() 

    if info.status == FAILURE and display_identifier: 
     return CustomError(request, _("Google Login Error"), _("Verification of %(user)s failed: %(error_message)s") % {'user' : display_identifier, 'error_message' : info.message}) 

    elif info.status == SUCCESS: 
     try: 
      _email = info.message.args[('http://openid.net/srv/ax/1.0', 'value.email')] 
      _first_name = info.message.args[('http://openid.net/srv/ax/1.0', 'value.firstname')] 
      _last_name = info.message.args[('http://openid.net/srv/ax/1.0', 'value.lastname')] 
      try: 
       _user = User.objects.get(email__iexact=_email) 
      except ObjectDoesNotExist: 
       # create a new account if one does not exist with the authorized email yet and log that user in 
       _new_user = _new_account(_email, _first_name + ' ' + _last_name, _first_name, _last_name, p_account_status=1) 
       _login(request, _new_user, info.message.args[('http://specs.openid.net/auth/2.0', 'response_nonce')]) 
       transaction.commit() 
       return redirect('home') 
      else: 
       # login existing user 
       _login(request, _user, info.message.args[('http://specs.openid.net/auth/2.0', 'response_nonce')]) 
       transaction.commit() 
       return redirect('home') 
     except Exception as e: 
      transaction.rollback() 
      system_log_entry(e, request=request) 
      return CustomError(request, _("Login Unsuccessful"), "%s" % e) 

    elif info.status == CANCEL: 
     return CustomError(request, _("Google Login Error"), _('Google account verification cancelled.')) 

    elif info.status == SETUP_NEEDED: 
     if info.setup_url: 
      return CustomError(request, _("Google Login Setup Needed"), _('<a href="%(url)s">Setup needed</a>') % { 'url' : info.setup_url }) 
     else: 
      # This means auth didn't succeed, but you're welcome to try 
      # non-immediate mode. 
      return CustomError(request, _("Google Login Setup Needed"), _('Setup needed')) 
    else: 
     # Either we don't understand the code or there is no 
     # openid_url included with the error. Give a generic 
     # failure message. The library should supply debug 
     # information in a log. 
     return CustomError(request, _("Google Login Error"), _('Google account verification failed for an unknown reason. Please try to create a manual account on Acquee.')) 


def get_url_host(request): 
    if request.is_secure(): 
     protocol = 'https' 
    else: 
     protocol = 'http' 
    host = escape(get_host(request)) 
    return '%s://%s' % (protocol, host) 

3)I创建和上述(myapp.common.util.openid进口)的附加LIB是从现有的几个的Django的合并OpenID的库这样的荣誉给那些家伙:

from django.db import models 
from django.conf import settings 
from django.utils.hashcompat import md5_constructor 

from openid.store.interface import OpenIDStore 
import openid.store 
from openid.association import Association as OIDAssociation 
import time, base64 

from myapp.common.db.accounts.models import Association, Nonce 

class DjangoOpenIDStore(OpenIDStore): 
    """ 
The Python openid library needs an OpenIDStore subclass to persist data 
related to OpenID authentications. This one uses our Django models. 
""" 

    def storeAssociation(self, server_url, association): 
     assoc = Association(
      server_url = server_url, 
      handle = association.handle, 
      secret = base64.encodestring(association.secret), 
      issued = association.issued, 
      lifetime = association.issued, 
      assoc_type = association.assoc_type 
     ) 
     assoc.save() 

    def getAssociation(self, server_url, handle=None): 
     assocs = [] 
     if handle is not None: 
      assocs = Association.objects.filter(
       server_url = server_url, handle = handle 
      ) 
     else: 
      assocs = Association.objects.filter(
       server_url = server_url 
      ) 
     if not assocs: 
      return None 
     associations = [] 
     for assoc in assocs: 
      association = OIDAssociation(
       assoc.handle, base64.decodestring(assoc.secret), assoc.issued, 
       assoc.lifetime, assoc.assoc_type 
      ) 
      if association.getExpiresIn() == 0: 
       self.removeAssociation(server_url, assoc.handle) 
      else: 
       associations.append((association.issued, association)) 
     if not associations: 
      return None 
     return associations[-1][1] 

    def removeAssociation(self, server_url, handle): 
     assocs = list(Association.objects.filter(
      server_url = server_url, handle = handle 
     )) 
     assocs_exist = len(assocs) > 0 
     for assoc in assocs: 
      assoc.delete() 
     return assocs_exist 

    def useNonce(self, server_url, timestamp, salt): 
     # Has nonce expired? 
     if abs(timestamp - time.time()) > openid.store.nonce.SKEW: 
      return False 
     try: 
      nonce = Nonce.objects.get(
       server_url__exact = server_url, 
       timestamp__exact = timestamp, 
       salt__exact = salt 
      ) 
     except Nonce.DoesNotExist: 
      nonce = Nonce.objects.create(
       server_url = server_url, 
       timestamp = timestamp, 
       salt = salt 
      ) 
      return True 
     nonce.delete() 
     return False 

    def cleanupNonce(self): 
     Nonce.objects.filter(
      timestamp__lt = (int(time.time()) - nonce.SKEW) 
     ).delete() 

    def cleaupAssociations(self): 
     Association.objects.extra(
      where=['issued + lifetimeint < (%s)' % time.time()] 
     ).delete() 

    def getAuthKey(self): 
     # Use first AUTH_KEY_LEN characters of md5 hash of SECRET_KEY 
     return md5_constructor.new(settings.SECRET_KEY).hexdigest()[:self.AUTH_KEY_LEN] 

    def isDumb(self): 
     return False 

4),并且是为了保持谷歌帐户会话标识符需要和验证端点模式:

class Nonce(models.Model): 
    """ Required for OpenID functionality """ 
    server_url = models.CharField(max_length=255) 
    timestamp = models.IntegerField() 
    salt = models.CharField(max_length=40) 

    def __unicode__(self): 
     return u"Nonce: %s for %s" % (self.salt, self.server_url) 


class Association(models.Model): 
    """ Required for OpenID functionality """ 
    server_url = models.TextField(max_length=2047) 
    handle = models.CharField(max_length=255) 
    secret = models.TextField(max_length=255) # Stored base64 encoded 
    issued = models.IntegerField() 
    lifetime = models.IntegerField() 
    assoc_type = models.TextField(max_length=64) 

    def __unicode__(self): 
     return u"Association: %s, %s" % (self.server_url, self.handle) 

祝你好运! Rok

10

我认为你的问题源于对OpenID和/或OAuth的工作原理的基本误解。

看起来你只是想验证,所以让我们暂时坚持使用OpenID。你是正确的看现有的库。如果您只需要OpenID而不是OAuth,并且您没有使用Django的内置身份验证框架,则可以使用python-openid。

使用OpenID和OAuth进行联合登录的完整文档位于:http://code.google.com/apis/accounts/docs/OpenID.html。尤其要看“交互序列”下的图表。

首先,这里是来自Facebook的龙卷风Web服务器的身份验证模块一个很好的工作例如:(。grep的,对于“GoogleHandler”我已经取得了巨大成功使用它)

https://github.com/facebook/tornado/blob/master/tornado/auth.py 这是独立的Django和Django验证,并且应该给你一个如何实现你想要的东西的好例子。如果还不够,请继续阅读......

你说过,django-openid是无关紧要的,但实际上它演示了你想要的实现,但是对于Django的认证系统而不是你自己。实际上,您应该看看类似的插件Django-SocialAuth,它为几个不同的提供商(Google,Facebook,Twitter等)实施OpenID + OAuth。特别是,看:

https://github.com/agiliq/Django-Socialauth/blob/master/socialauth/lib/oauthgoogle.pyhttps://github.com/agiliq/Django-Socialauth/tree/master/openid_consumerhttps://github.com/agiliq/Django-Socialauth/tree/master/example_project

...使用Django的权威性框架的完整工作示例,并且可以适应您的自定义身份验证框架。

祝你好运。我鼓励你记录任何最终为你工作的内容,并为像你这样的其他人建立一个分步指南。

+0

修复了链接,对于SO障碍感到抱歉。 – Tobu 2010-12-05 01:47:51