【问题标题】:Adding two factor authentication in Django/Django Rest在 Django/Django Rest 中添加两因素身份验证
【发布时间】:2021-06-26 23:05:56
【问题描述】:

我知道这个话题已被广泛讨论,但大多数示例都是关于标准 Django 模板中的两因素身份验证,而在我的情况下,我想将两因素身份验证添加到一个项目中,其中 Django 用作 API后端,而前端是原生 VueJS 应用程序。

对于所有相关的身份验证,我使用的是内置的 Django 会话身份验证,因为前端和后端都部署在同一台服务器上。

我的问题是:如何在 django 用作 API 的项目中添加两因素身份验证(使用 google 身份验证器或 yubikey)?

问题出在这里:最简单的方法是让用户从前端登录,一旦用户从/accounts/login 登录(内置 django 身份验证视图),提交用户必须输入代码的表单。这种方法的问题是,一旦用户登录,Django 将创建一个会话,因此request.user.is_authenticated 将返回True,即使用户尚未提交两因素代码,所以一切都取决于前端。我不喜欢这种方法,因为我担心有人可能会找到一种方法来避免提交两因素表单并在网站的其余部分导航(因为根据 Django,该会话将被验证)而无需两因素身份验证

我尝试了什么:我仍然需要为此编写大部分代码,因为我想先了解它的安全性。但这是我的方法:

第一种方法

  1. 用户提交登录表单
  2. 提交登录表单后,带有凭据的 POST 请求将发送到我的 Django 应用程序中名为 /authenticate 的端点。该端点将使用 Django 内置的 authenticate() 方法,该方法将检查这些凭据是否属于用户,而无需创建会话。
  3. 如果凭据属于用户,它将返回True 给用户。此时用户将提交带有 2FA 代码的表单,如果代码正确,请求将发送到 /accounts/login,它将再次检查密码和电子邮件,并实际登录用户并创建会话。

第二种方法 另一种更好的方法是覆盖 Django-Allauth 登录视图,以便我可以添加对令牌的检查,例如(警告:伪代码):

if provided_code == user_code:
   login()
   return HttpResponse({'Result': 'Logged in!'})
else:
   return HttpResponse({'Result': 'incorrect code'})

provided_code 是用户提供的 2FA 代码,user_code 是我将从 get_user_2fa_code(user) 等另一个函数中检索到的正确代码。

我不是安全专家,但我没有想出更好的方法。会有多安全?有没有更好的方法将 2FA 身份验证添加到 Django 项目中,其中 django 作为后端 API?

Here is the login view:

class LoginView(
    RedirectAuthenticatedUserMixin, AjaxCapableProcessFormViewMixin, FormView
):
    form_class = LoginForm
    template_name = "account/login." + app_settings.TEMPLATE_EXTENSION
    success_url = None
    redirect_field_name = "next"

    @sensitive_post_parameters_m
    def dispatch(self, request, *args, **kwargs):
        return super(LoginView, self).dispatch(request, *args, **kwargs)

    def get_form_kwargs(self):
        kwargs = super(LoginView, self).get_form_kwargs()
        kwargs["request"] = self.request
        return kwargs

    def get_form_class(self):
        return get_form_class(app_settings.FORMS, "login", self.form_class)

    def form_valid(self, form):
        success_url = self.get_success_url()
        try:
            return form.login(self.request, redirect_url=success_url)
        except ImmediateHttpResponse as e:
            return e.response

    def get_success_url(self):
        # Explicitly passed ?next= URL takes precedence
        ret = (
            get_next_redirect_url(self.request, self.redirect_field_name)
            or self.success_url
        )
        return ret

    def get_context_data(self, **kwargs):
        ret = super(LoginView, self).get_context_data(**kwargs)
        signup_url = passthrough_next_redirect_url(
            self.request, reverse("account_signup"), self.redirect_field_name
        )
        redirect_field_value = get_request_param(self.request, self.redirect_field_name)
        site = get_current_site(self.request)

        ret.update(
            {
                "signup_url": signup_url,
                "site": site,
                "redirect_field_name": self.redirect_field_name,
                "redirect_field_value": redirect_field_value,
            }
        )
        return ret

【问题讨论】:

    标签: python django django-rest-framework


    【解决方案1】:

    我不会尝试自己实施多因素身份验证,而是考虑 SAML 或 OAuth。这些方法的基本思想是您的站点根本不询问凭据,但是您将身份验证重定向到安全提供商系统,并且您的站点获取用户信息,您可以使用该信息来检查用户是否存在于您的系统中(通常是电子邮件)和身份验证您可以验证的令牌。用户在所有身份验证过程通过后获得令牌,而不仅仅是第一阶段。

    似乎有大量用于 SAML / OAuth 身份验证的 Django 插件:https://djangopackages.org/grids/g/authentication/

    以及强制多因素身份验证的原因是在身份验证提供程序系统中设置,而不是在您的代码中。

    【讨论】:

    • 我想我已经在使用你的意思了,因为该链接上提到了 Django-Allauth,并且我正在将该模块用于与身份验证相关的所有内容
    【解决方案2】:

    这可能会有所帮助..我不确定。大多数现有的 django 2fa 项目都按照您描述的方式工作 - 我建议检查它们的工作方式以获得更好的提示。

    1. 首先使用django.contrib.auth.authenticate 检查用户名/密码。此身份验证是凭据,但不会创建会话。
    2. 如果用户有 2FA - 将他们路由到页面以执行验证步骤
    3. 验证步骤完成后 - 使用django.contrib.auth.login 登录

    对于登录身份验证视图和发布凭据 - 我做了这样的事情:

    class TwoFactorAwareLoginView(TemplateView):
        def post(self, request, *args, **kwargs):
            form = self.form_class(data=request.POST, request=request)
            if form.is_valid():
                username = form.cleaned_data.get("username")
                password = form.cleaned_data.get("password")
                user = authenticate(request, username=username, password=password)
                if user is not None:
                    devices = <something to get your 2FA device blobs>
                    if not devices: #no user devices- log them in
                        login(request, user)
                        return HttpResponseRedirect(reverse("cool-page"))
                    else: #2FA devices exist- route them to verification view and perform auth_login once complete
                        # add some context needed for verification view in session or context
                        return HttpResponseRedirect(reverse("two-factor:verify-login"))
            return render(request, self.template_name, {"form": form})
    

    【讨论】:

    • 非常感谢!这非常有帮助,而且确实与我的处理方式非常相似。我只有一个疑问:重定向到 2FA 表单后,如何检查凭据?我的意思是,你怎么知道是谁在提交 2FA 代码?
    • 我认为有几个不同的选择。您也许可以生成一些缓存键来传递,或者将其添加到会话中,如request.session["user"] = user 以将用户隐藏在会话数据中,并在您进行检查的视图中获取它。
    • 是的,这是有道理的。这是我发现的另一种方法:我在登录名中添加了第三个名为“2fa_code”的字段;然后在登录表单中,如果凭据正确并且用户启用了 2fa,django 将返回 JSON 响应“需要 2fa”,此时我的前端将打开一个带有单个输入的弹出表单,用户在其中提交相同的登录形式,但这次只有 2FA 代码。它与你所做的非常相似,只是它不会重定向到另一个页面,但也许我会像你一样做
    猜你喜欢
    • 2021-09-09
    • 2019-07-21
    • 2019-07-21
    • 2020-05-29
    • 2021-04-08
    • 2021-07-18
    • 2013-06-06
    • 2012-03-17
    • 1970-01-01
    相关资源
    最近更新 更多