重写 simple-jwt 的 TokenObtainPairSerializer 来实现 2FA

Kin*_*ins 5 python django jwt django-rest-framework multi-factor-authentication

我目前正在尝试在我的 Django 应用程序中实现双因素身份验证。我做的第一件事是修改Meta类中的类UserSerializer以添加两个字段enabled(指示用户是否启用 2FA 的布尔值)和secret_key(生成 OTP 的密钥,当用户启用 2FA 时共享给用户)。

为了最大限度地修改登录流程,我修改了发送以生成访问令牌的表单,以包含新字段“otp”。用户可以填写也可以不填写,后端会检查用户是否启用了2FA,如果是,OTP是否正确。

如果没有 2FA,登录只是一个带有 body 的 POST 请求{"username": usr, "password": pwd}。这已成为带有 body 的 POST 请求{"username": usr, "password": pwd, "otp": otp}。如果用户尚未启用 2FA,他只需将该opt字段留空即可。

我的urls.py看起来像这样:

path("api/token/", TokenObtainPairView.as_view(), name="token_obtain_pair")
Run Code Online (Sandbox Code Playgroud)

我的想法是重写 TokenObtainPairView 以适应新的请求。根据我的发现,我必须改变validate方法,但我真的不知道如何做到这一点。enabled我可能必须获取用户的和字段的值secret_key(基于用户名)来生成 OTP(如果相关)并根据字段进行检查otp。问题是,我不知道该怎么做,而且我在 simple-jwt 实现中有点迷失了。

tok*_*ken 6

首先,我不会为您的问题提供完整的解决方案,但这可能是一个好的开始。

创建您的自定义LoginView

class LoginView(TokenObtainPairView):
    serializer_class = LoginSerializer
Run Code Online (Sandbox Code Playgroud)

实现您自己的序列化器,它继承TokenObtainPairSerializer

class LoginSerializer(TokenObtainPairSerializer):
    def validate(self, attrs):
        # implement your logic here
        # data = super().validate(attrs)
        return data
Run Code Online (Sandbox Code Playgroud)

更改url.py

path("api/token/", LoginView.as_view(), name="token_obtain_pair")
Run Code Online (Sandbox Code Playgroud)

这是TokenObtainSerializer的样子:

class TokenObtainSerializer(serializers.Serializer):
    username_field = User.USERNAME_FIELD

    default_error_messages = {
        'no_active_account': _('No active account found with the given credentials')
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.fields[self.username_field] = serializers.CharField()
        self.fields['password'] = PasswordField()

    def validate(self, attrs):
        authenticate_kwargs = {
            self.username_field: attrs[self.username_field],
            'password': attrs['password'],
        }
        try:
            authenticate_kwargs['request'] = self.context['request']
        except KeyError:
            pass

        self.user = authenticate(**authenticate_kwargs)

        if not getattr(login_rule, user_eligible_for_login)(self.user):
            raise exceptions.AuthenticationFailed(
                self.error_messages['no_active_account'],
                'no_active_account',
            )

        return {}
Run Code Online (Sandbox Code Playgroud)

__init___因此,您可以在自己的序列化器中实现并添加您的otp字段并在validate方法内实现您想要的逻辑(您可以在此处访问self.user并检查他是否启用了2FA )。