我该如何使用Django OAuth Toolkit和Python Social Auth?

67

我正在使用Django Rest Framework构建API。稍后,iOS和Android设备应该可以使用此API。我希望允许我的用户使用Facebook和Google等oauth2提供程序进行注册。在这种情况下,他们根本不需要在我的平台上创建帐户。但是,即使没有Facebook / Google帐户,用户也应该能够注册,为此我使用django-oauth-toolkit,因此我有自己的oauth2提供程序。

对于外部提供商,我正在使用python-social-auth,它可以很好地工作并自动创建用户对象。

我希望客户端使用令牌来进行身份验证,这对于使用我的提供程序注册的用户非常有效(django-oauth-toolkit为Django REST Framework提供身份验证方案和权限类)。
然而,python-social-auth仅实现基于会话的身份验证,因此无法直接为通过外部oauth2提供程序注册的用户进行身份验证,也就无法在其名义上进行身份验证的API请求。

如果我使用由django-oauth-toolkit生成的access_token,则像这样进行请求是可以的:

curl -v -H "Authorization: Bearer <token_generated_by_django-oauth-toolkit>" http://localhost:8000/api/

然而,以下方法不起作用,因为Django REST Framework没有相应的身份验证方案,并且python-social-auth提供的AUTHENTICATION_BACKENDS仅适用于基于会话的身份验证:

curl -v -H "Authorization: Bearer <token_stored_by_python-social-auth>" http://localhost:8000/api/

在使用python-social-auth进行认证后,使用Django REST Framework提供的可浏览API功能完全正常,但是没有会话cookie的API调用不起作用。

我想知道解决这个问题的最佳方法是什么。我认为,我基本上有两个选择:

A:当用户使用外部oauth2提供程序(由python-social-auth处理)注册时,钩入该过程以创建oauth2_provider.models.AccessToken,并继续使用'oauth2_provider.ext.rest_framework.OAuth2Authentication'来验证已注册外部提供商的用户。建议采用这种方法: https://groups.google.com/d/msg/django-rest-framework/ACKx1kY7kZM/YPWFA2DP9LwJ

B:使用python-social-auth进行API请求身份验证。我可以通过编写自定义后端并使用register_by_access_token将我的用户添加到python-social-auth中。但是,由于API调用无法利用Django会话,这意味着我必须编写一个Django Rest Framework的身份验证方案,该方案利用python-social-auth存储的数据。有关如何执行此操作的一些指针可以在此处找到:
http://psa.matiasaguirre.net/docs/use_cases.html#signup-by-oauth-access-token
http://blog.wizer.fr/2013/11/angularjs-facebook-with-a-django-rest-api/
http://cbdev.blogspot.it/2014/02/facebook-login-with-angularjs-django.html
但是,我理解的方式是python-social-auth仅在进行登录时验证令牌,并在之后依赖于Django会话。这意味着我必须找到一种方法,以防止python-social-auth对每个无状态API请求都执行整个oauth2流程,并对存储在DB中的数据进行检查,这并不适合查询,因为它以JSON格式存储(但我可以使用UserSocialAuth.objects.get(extra_data__contains=))。同时,我还必须注意验证访问令牌的范围,并使用它们来检查权限,django-oauth-toolkit已经处理了此问题(TokenHasScoperequired_scopes等)。

目前,我倾向于使用选项A,因为django-oauth-toolkit与Django Rest Framework有很好的集成,并且我可以从框架中获得所需的所有内容。唯一的缺点是我必须将python-social-auth检索到的访问令牌“注入”到django-oauth-toolkit的AccessToken模型中,这种方式感觉有些不对,但可能是最简单的方法。

是否有人反对这样做或以不同的方式解决了相同的问题?我是否遗漏了任何明显的事情并使生活比必要困难?如果有人已经将django-oauth-toolkit与python-social-auth和外部oauth2提供程序集成,我将非常感激一些指针或意见。

4个回答

111
很多实现OAuth的困难在于理解授权流程应该如何工作。这主要是因为这是登录的“起点”,而且当使用第三方后端(例如Python Social Auth)时,你实际上需要进行两次操作:一次用于你的API,一次用于第三方API。
使用你的API和第三方后端授权请求的过程如下:

Sequence diagram for option A

Mobile App -> Your API : Authorization redirect
Your API -> Django Login : Displays login page
Django Login -> Facebook : User signs in
Facebook -> Django Login : User authorizes your API
Django Login -> Your API : User signs in
Your API -> Mobile App : User authorizes mobile app

我在这里使用"Facebook"作为第三方后端,但是对于任何后端,过程都是相同的。

从您的移动应用程序的角度来看,您只需重定向到Django OAuth Toolkit提供的/authorize URL。从那里开始,移动应用程序等待回调URL被访问,就像标准OAuth授权流程中一样。几乎所有其他内容(Django登录,社交登录等)都由Django OAuth Toolkit或Python Social Auth在后台处理。

这也将兼容您使用的几乎任何OAuth库,并且无论使用什么第三方后端,授权流程都将是相同的。它甚至可以处理您需要支持Django身份验证后端(电子邮件/用户名和密码)以及第三方登录的常见情况。

Option A without a third-party backend

Mobile App -> Your API : Authorization redirect
Your API -> Django Login : Displays login page
Django Login -> Your API : User signs in
Your API -> Mobile App : User authorizes mobile app

值得注意的是,移动应用程序(可以是任何OAuth客户端)永远不会收到Facebook/第三方OAuth令牌。这非常重要,因为它确保您的API充当OAuth客户端和用户社交帐户之间的中介。

Sequence diagram with your API as the gatekeeper

Mobile App -> Your API : Authorization redirect
Your API -> Mobile App : Receives OAuth token
Mobile App -> Your API : Requests the display name
Your API -> Facebook : Requests the full name
Facebook -> Your API : Sends back the full name
Your API -> Mobile App : Send back a display name

否则,OAuth客户端将能够绕过您的API并代表您向第三方API发出请求。

Sequence diagram for bypassing your API

Mobile App -> Your API : Authorization redirect
Your API -> Mobile App : Receives Facebook token
Mobile App -> Facebook : Requests all of the followers
Facebook -> Mobile App : Sends any requested data

您会注意到,此时您已经失去了对第三方令牌的所有控制。这是非常危险的,因为大多数令牌可以访问各种数据,这打开了滥用的大门,最终以您的名义进行操作。最有可能的是,那些登录您的API/网站的人并不想与OAuth客户端分享他们的社交信息,而是希望您尽可能保密这些信息,但实际上,您正在将这些信息暴露给所有人。
身份验证请求您的API
当移动应用程序使用您的OAuth令牌向您的API发出请求时,所有身份验证都在后台通过Django OAuth Toolkit(或您的OAuth提供程序)完成。您只能看到与您的请求相关联的“用户”。

How OAuth tokens are validated

Mobile App -> Your API : Sends request with OAuth token
Your API -> Django OAuth Toolkit : Verifies the token
Django OAuth Toolkit -> Your API : Returns the user who is authenticated
Your API -> Mobile App : Sends requested data back

这很重要,因为在授权阶段之后,用户是来自Facebook还是Django的认证系统对于API来说没有区别。您的API只需要一个“用户”来使用,并且您的OAuth提供程序应该能够处理令牌的验证和认证。
当使用基于会话的身份验证时,这与Django REST框架对用户进行身份验证的方式并没有太大区别。

Sequence diagram for authenticating using sessions

Web Browser -> Your API : Sends session cookie
Your API -> Django : Verifies session token
Django -> Your API : Returns session data
Your API -> Django : Verifies the user session
Django -> Your API : Returns the logged in user
Your API -> Web Browser : Returns the requested data

再次强调,所有这些都由Django OAuth Toolkit处理,不需要额外的工作来实现。

与本机SDK一起使用

在大多数情况下,您将通过自己的网站对用户进行身份验证,并使用Python Social Auth处理所有内容。但是,唯一的例外是使用本机SDK时,因为身份验证和授权通过本地系统处理,这意味着您完全绕过了API。这对于需要使用第三方登录的应用程序或根本不使用您的API的应用程序非常有用,但是当两者结合在一起时,它会成为一个噩梦

这是因为您的服务器无法验证登录,被迫假定登录是有效和真实的,这意味着它绕过了Python Social Auth提供的任何和所有安全性。

Using a native SDK can cause issues

Mobile App -> Facebook SDK : Opens the authorization prompt
Facebook SDK -> Mobile App : Gets the Facebook token
Mobile App -> Your API : Sends the Facebook token for authorization
Your API -> Django Login : Tries to validate the token
Django Login -> Your API : Returns a matching user
Your API -> Mobile App : Sends back an OAuth token for the user

您会注意到,在身份验证阶段中,此方法跳过了您的 API,然后强制要求您的 API 对传递的令牌进行假设。但是,在某些情况下,这种风险可能是值得的,因此在放弃之前应该评估一下。这是在为用户提供快速和本地化的登录体验处理错误或恶意令牌之间的权衡。

嗨,Kevin,非常感谢你的回答。我可能没有表述清楚,但我的目标是为我的用户提供使用Facebook或Google等提供商注册的能力,就像stackoverflow一样。我不想从这些提供商获取任何其他信息,只允许用户使用它们登录。如果他们使用例如Google进行注册,他们根本不需要在我的提供商处注册。你建议使用django-oauth-toolkit作为代理的想法很有道理,我需要仔细考虑并稍后再与你联系。 - jeverling
这应该可以工作,但在这种情况下,我仍然有一个问题,如何在不使用类似cookie-jar的东西的情况下使用iOS应用程序进行API调用。使用django-oauth-toolkit,我只需使用带有bearer token的Authorization标头,而目前似乎无法使用python-social-auth - jeverling
这就是使用基于令牌的身份验证方案有用的地方,因此您可以将其用作不同身份验证方法的代理。这样,您只需要存储令牌而不是 cookie。 - Kevin Brown-Silva
是的,我肯定会尝试按照你建议的方式去做,即使在这种情况下将访问令牌从Google/Facebook传递给客户端也不会有问题,但你的建议很有道理。 - jeverling
1
@jeverling 这个问题解决了吗?我想了解最佳操作方法的更多细节。 - bchang
显示剩余2条评论

10

我使用了你提供的A选项,解决了它。

我所做的是通过第三方访问令牌注册使用第三方注册的用户。

url(r'^register-by-token/(?P<backend>[^/]+)/$',
    views.register_by_access_token),

这样,我可以像这样发出GET请求:

GET http://localhost:8000/register-by-token/facebook/?access_token=123456

然后会调用register_by_access_tokenrequest.backend.do_auth将从令牌查询提供者的用户信息,根据该信息自动注册用户帐户或在已经注册的情况下登录用户。

接着,我手动创建一个令牌,并将其作为JSON返回,以便让客户端查询我的API。

from oauthlib.common import generate_token
...
@psa('social:complete')
def register_by_access_token(request, backend):
    # This view expects an access_token GET parameter, if it's needed,
    # request.backend and request.strategy will be loaded with the current
    # backend and strategy.
    third_party_token = request.GET.get('access_token')
    user = request.backend.do_auth(third_party_token)

    if user:
        login(request, user)

        # We get our app!   
        app = Application.objects.get(name="myapp")

        # We delete the old token
        try:
            old = AccessToken.objects.get(user=user, application=app)
        except:
            pass
        else:
            old.delete()

        # We create a new one
        my_token = generate_token()

        # We create the access token 
        # (we could create a refresh token too the same way) 
        AccessToken.objects.create(user=user,
                                   application=app,
                                   expires=now() + timedelta(days=365),
                                   token=my_token)

        return "OK" # you can return your token as JSON here

    else:
        return "ERROR"

我只是不确定我生成令牌的方式是否符合良好实践?不过,目前它可以工作!


在哪里或者更好的说法是如何获取access_token?是谁发送它的?我正在使用satellizer,但是遇到了很多困难。 - Igor Pejic
你可以从前端获取访问令牌。对于iOS应用和Facebook,可以使用Facebook iOS SDK获取。 - Felix D.
你能发布完整的代码吗?另外,你是在使用 https://django-oauth-toolkit. 来存储令牌还是自己构建的东西?谢谢。 - beddamadre
使用这个库来获得一个现成的解决方案。 - Felix D.
你说得对,这取决于OAuth提供程序可以提供给你的用户信息。你可能会得到一个电子邮件、一个用户名或一个卷饼食谱。你应该研究一下我提到的库,以获得比自己实现更可靠的解决方案。 - Felix D.
显示剩余2条评论

6
也许 django-rest-framework-social-oauth2 是您正在寻找的东西。该软件包依赖于您已经使用的 python-social-authdjango-oauth-toolkit。我快速浏览了文档,它似乎实现了您想要做的事情。

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接