使用Django OAuth2 Toolkit生成单个访问令牌

16

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

在使用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/

创建了一个新的访问令牌和刷新令牌。在令牌超时之前,旧的访问令牌和刷新令牌仍然可用!

我的问题:

  • 我需要的是每次用户请求新的访问令牌时,旧的访问令牌将变得无效,不能使用,并被删除。
  • 此外,是否有一种方法可以使密码grunt类型不会创建刷新令牌。 我的应用中没有任何用处。

我发现的一种解决方案是,REST Framework OAuth 提供了一种一次只允许一个访问令牌的配置。虽然我不太想使用这个提供者,但我可能别无选择。

4个回答

22

如果您想在发放新令牌之前删除所有先前的访问令牌,则有一个简单的解决方案: 创建自己的令牌视图提供程序!

以下代码可能会帮助您实现此类功能:

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块。在那里,我们找到访问令牌并将其删除。所有这些都发生在我们创建新令牌之前。

你可以查看如何使用OAuthLib创建自己的提供者。 此外,这也可能会有所帮助:django-oauth-toolkit中的TokenView。你可以在那里看到原始Apiview。正如你所说,你使用了这个包。

至于refresh_token,如其他答案中先前提到的那样,你不能做你正在请求的事情。当查看oauthlib密码grunt类型的代码时,你会发现在其初始化中,refresh_token设置为True。除非你改变Grunt类型本身,否则无法完成。

但是,你可以像上面操作访问令牌一样进行操作。 创建令牌,然后删除刷新令牌。


我能否使用这种方法完全不需要用户名和密码?而是要求应用程序密码或其他什么东西。 - ss7

6

我需要的是每当用户请求新的访问令牌时,旧的访问令牌将变得无效、无法使用并被删除。

在请求新的访问令牌时提供一个新的似乎是一种预期的行为。您是否无法在请求新令牌之前撤销现有令牌?

更新


如果您决定只保留一个令牌 - 类 OAuth2Validator 继承了 OAuthLib 的 RequestValidator 并覆盖了方法 save_bearer_token。在此方法中,在与 AccessToken model 实例创建及其 .save() 方法相关的代码之前,您可以查询(类似于 this)以查看是否已经为此用户保存了 AccessToken。如果找到现有令牌,则可以从数据库中删除该令牌。

我强烈建议将此更改做成可配置的,以防您将来改变主意(毕竟有多个令牌是出于像this这样的原因)
一个更简单的解决方案是拥有自己的验证器类,可能继承oauth2_provider.oauth2_validators.OAuth2Validator并重写save_bearer_token。 将这个新类给予 settings.py 中的 OAUTH2_VALIDATOR_CLASS
另外,有没有办法使密码grunt类型不创建刷新令牌。在我的应用程序中,我没有任何用途。
Django OAuth Toolkit依赖于OAuthLib。
将refresh_token设为可选的关键在于OAuthLib的BearerToken类的create_token方法(此行)和密码授权的类(此处) 。正如您可以看到的那样,该类的__init__方法接受refresh_token参数,默认设置为True。该值在同一类的create_token_response方法中使用,位于该行
token = token_handler.create_token(request, self.refresh_token)

create_token_response 方法在 Django OAuth 工具包的 OAuthLibCore 类中,我相信它调用了 OAuthLib 中对应的 create_token_response 方法。观察此类的 __init__ 方法中使用 self.server 和其初始化方式,该方法只传递了验证器作为参数,与 refresh_token 无关。

将其与 OAuthLib Implicit 授权类型create_token_response 方法进行比较,后者明确执行了

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

不创建refresh_token

所以,除非我漏掉了什么,简而言之,我认为Django OAuth toolkit没有暴露可选的refresh_token功能。


撤销令牌是我不想发出的请求。 这里的目标是在单台机器上仅允许用户进行一次访问。 我不希望同一用户同时登录到多个地方。 - Gal Silberman
1
@Gal,请查看我的编辑,描述了需要在Django Oauth Toolkit中进行的更改。 - LearnerEarner
我不想改变核心OAuthLib类。使用它作为一个包的意义就在于我不会改变它。另一方面,使用Class validator可能是一个好主意。 - Gal Silberman

1
这是一个直接制作的示例:
from oauthlib.common import generate_token
from oauth2_provider.models import AccessToken, Application
from django.utils import timezone
from dateutil.relativedelta import relativedelta

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

0

被接受的答案仍然无法清除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()

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