使用 Django OAuth2 工具包生成单一访问令牌

Generating single access token with Django OAuth2 Toolkit

我正在使用最新的 Django OAuth2 Toolkit (0.10.0) Python 2.7、Django 1.8 和 Django REST 框架 3.3

在使用 grant_type=password 时,我注意到每当用户请求新的访问令牌时都会出现一些奇怪的行为:

curl -X POST -d "grant_type=password&username=<user_name>&password=<password>" -u"<client_id>:<client_secret>" http://localhost:8000/o/token/

访问令牌和刷新令牌已创建 访问和刷新令牌在令牌超时之前仍然可用!

我的问题:

我找到的一个解决方案是 REST Framework OAuth 一次提供一个访问令牌的配置。我不急于使用该提供商,但我可能别无选择。

What I need is that every time a user asks for a new access token, the old one will become invalid, unusable and will be removed.

当你要求一个新令牌时给予一个新令牌似乎是一种预期的行为。在要求新的之前,您不可能 revoke 现有的吗?

更新


如果你决定只保留一个令牌 - class OAuth2Validator inherits OAuthLib's RequestValidator and overrides the method save_bearer_token. In this method before the code related to AccessToken model instance creation and its .save() method you can query (similar to this) 查看数据库中是否已经为该用户保存了AccessToken。如果找到现有令牌,可以从数据库中删除。

我强烈建议将此更改设为可配置,以防您将来改变主意(因为 this 等原因发布了所有多个令牌之后)

一个更简单的解决方案是拥有自己的验证器 class,可能是继承 oauth2_provider.oauth2_validators.OAuth2Validator 并覆盖 save_bearer_token 的验证器。这个新的 class 应该在 settings.py

中为 OAUTH2_VALIDATOR_CLASS 提供

Also, is there a way that the password grunt type wont create refresh token. I don't have any use for that in my application.

Django OAuth 工具包依赖于 OAuthLib。

使 refresh_token 可选归结为 BearerToken class 中 this line and the class for password grant is here 的 oAuthLib 中的 create_token 方法。如您所见,此 class 的 __init__ 方法采用 refresh_token 参数,默认设置为 True。该值在 create_token_response 方法中使用相同的 class 行

token = token_handler.create_token(request, self.refresh_token)

create_token_response 方法在 OAuthLibCore class 的 Django OAuth 工具包是一个,我相信,在 OAuthLib 中调用相应的 create_token_response。观察 self.server 的用法及其在 class 的 __init__ 方法中的初始化,它只是将验证器作为参数传递,但与 refresh_token.[=40= 无关]

将此与 OAuthLib Imlicit grant typecreate_token_response 方法进行比较,后者明确地执行

token = token_handler.create_token(request, refresh_token=False)

根本不创建 refresh_token

所以,除非我在这里遗漏了什么,tldr,我认为 Django OAuth 工具包不会公开可选 refresh_token.

的特性

如果您想在发布新的访问令牌之前删除所有以前的访问令牌,有一个简单的解决方案可以解决此问题:制作您自己的令牌视图提供程序!

下面的代码可能会帮助您实现这种功能:

from oauth2_provider.models import AccessToken, Application
from braces.views import CsrfExemptMixin
from oauth2_provider.views.mixins import OAuthLibMixin
from oauth2_provider.settings import oauth2_settings

class TokenView(APIView, CsrfExemptMixin, OAuthLibMixin):
    permission_classes = (permissions.AllowAny,)

    server_class = oauth2_settings.OAUTH2_SERVER_CLASS
    validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS
    oauthlib_backend_class = oauth2_settings.OAUTH2_BACKEND_CLASS

    def post(self, request):
        username = request.POST.get('username')
        try:
            if username is None:
                raise User.DoesNotExist
            AccessToken.objects.filter(user=User.objects.get(username=username), application=Application.objects.get(name="Website")).delete()
        except Exception as e:
            return Response(e.message,status=400)

        url, headers, body, status = self.create_token_response(request)
        return Response(body, status=status, headers=headers)

您应该注意的部分是 Try-Except 块。在那里我们找到访问令牌并删除它们。在我们创建一个新的之前。

您可以查看如何创建自己的 Provider using OAuthLib。 此外,这也可能有用:TokenView in django-oauth-toolkit。你可以在那里看到原始的 Apiview。正如你所说,你正在使用这个包。

至于refresh_token,正如之前在其他答案中提到的,你不能做你所要求的。查看 oauthlib password grunt type 的代码时,您会看到在其初始化时,refresh_token 设置为 True。除非你自己更改 Grunt 类型,否则无法完成。

但是您可以使用访问令牌执行我们上面所做的相同操作。 创建令牌,然后删除刷新令牌。

接受的答案仍然无法清除RefreshToken。下面的代码应该同时撤销刷新和访问令牌。

from oauth2_provider.models import RefreshToken
def clear_token(user):
"""
Clear all user authorized tokens.
"""
for token in RefreshToken.objects.filter(user=user, revoked__isnull=True):
    token.revoke()

下面是直接制作的例子:

from oauthlib.common import generate_token
from oauth2_provider.models import AccessToken, Application
from django.utils import timezone
from dateutil.relativedelta import relativedeltatok = generate_token()

tok = generate_token()
app = Application.objects.first9)
user = User.objects.first()
access_token = AccessToken.objects.create(user=user, application=app, expires=timezone.now() + relativedelta(hours=1), token=tok)