python-social-auth 是否需要会话

2022-01-10 00:00:00 python django jwt python-social-auth

问题描述

我正在构建一个带有 API 后端(使用 DRF 构建)和 angularjs 客户端的 django 应用程序.我的目标是使用 JWT 代替会话完全解耦服务器和客户端.我正在尝试将 python-social-auth(PSA) 与 django-rest-framework-jwt(DRFJWT),所以我的目标是让身份验证流向这个:

I'm building a django app with an API backend(built with DRF) and angularjs client. My goal is to completely decouple the server and client using JWT in place of sessions. I'm attempting to integrate python-social-auth(PSA) with django-rest-framework-jwt(DRFJWT), so my goal is to have an auth flow something to this:

用户通过 Angular 客户端使用电子邮件/facebook 登录 -> 客户端将表单发布到 PSA 的 url -> PSA 登录/创建用户 ->[!] DRFJWT 创建令牌,然后将其发送回客户端 -> 客户端将令牌存储在本地存储中然后在每个请求中使用令牌

User logs with Email/facebook via angular client -> client posts form to PSA's url -> PSA login/create user ->[!] DRFJWT creates token that it then sends back to client -> client stores token in local storage then uses token each request

[!]:这是我目前正在苦苦挣扎的地方.我的想法是我可以修改 PSA 中的 do_complete 方法像这样

[!]: This is currently where I'm struggling. My thinking is that I can modify the do_complete method in PSA like so

from rest_framework_jwt.utils import jwt_payload_handler, jwt_encode_handler


def do_complete(backend, login, user=None, redirect_name='next',
            *args, **kwargs):
  # pop redirect value before the session is trashed on login()
  data = backend.strategy.request_data()
  redirect_value = backend.strategy.session_get(redirect_name, '') or 
                 data.get(redirect_name, '')

  is_authenticated = user_is_authenticated(user)
  user = is_authenticated and user or None

  partial = partial_pipeline_data(backend, user, *args, **kwargs)
  if partial:
      xargs, xkwargs = partial
      user = backend.continue_pipeline(*xargs, **xkwargs)
  else:
      user = backend.complete(user=user, *args, **kwargs)

  if user_is_active(user):
      # catch is_new/social_user in case login() resets the instance
      is_new = getattr(user, 'is_new', False)
      social_user = user.social_user
      login(backend, user, social_user)

  payload = jwt_payload_handler(user)
  return { 'token': jwt_encode_handler(payload) }

这是我想要完成的唯一方法吗?

Is this the only way of doing what I'm trying to accomplish?

我还想知道,从最佳实践的角度来看,是否可以使用会话来管理管道和 JWT 进行身份验证?

I'm also wondering if its okay from a best-practices standpoint to use sessions to manage the pipeline and JWT for auth?


解决方案

我也在用 python-social-auth 和 django-rest-framework-jwt 用于用户身份验证.

I'm also using python-social-auth and django-rest-framework-jwt for user authentication.

我能够将两个身份验证系统集成在一起的方法是创建一个自定义视图,该视图接受 oAuth 提供者提供的access_token"并尝试用它创建一个新用户.创建用户后,我不会返回经过身份验证的用户/会话,而是返回 JWT 令牌.

The way I was able to integrate the two authentication systems together was by creating a custom view that takes in the 'access_token' provided by the oAuth provider and attempts to create a new user with it. Once the user is created, instead of returning the authenticated user/session I return the JWT token.

以下代码片段解释了解决方案.

The following code snippets explain the solution.

在我的 views.py 文件中,我包含以下内容:

In my views.py file I included the following:

@psa()
def auth_by_token(request, backend):
    """Decorator that creates/authenticates a user with an access_token"""
    token = request.DATA.get('access_token')
    user = request.user
    user = request.backend.do_auth(
            access_token=request.DATA.get('access_token')
        )
    if user:
        return user
    else:
        return None

class FacebookView(views.APIView):
    """View to authenticate users through Facebook."""

    permission_classes = (permissions.AllowAny,)

    def post(self, request, format=None):
        auth_token = request.DATA.get('access_token', None)
        backend = request.DATA.get('backend', None)
        if auth_token and backend:
            try:
                # Try to authenticate the user using python-social-auth
                user = auth_by_token(request, backend)
            except Exception,e:
                return Response({
                        'status': 'Bad request',
                        'message': 'Could not authenticate with the provided token.'
                    }, status=status.HTTP_400_BAD_REQUEST)
            if user:
                if not user.is_active:
                    return Response({
                        'status': 'Unauthorized',
                        'message': 'The user account is disabled.'
                    }, status=status.HTTP_401_UNAUTHORIZED)

                # This is the part that differs from the normal python-social-auth implementation.
                # Return the JWT instead.

                # Get the JWT payload for the user.
                payload = jwt_payload_handler(user)

                # Include original issued at time for a brand new token,
                # to allow token refresh
                if api_settings.JWT_ALLOW_REFRESH:
                    payload['orig_iat'] = timegm(
                        datetime.utcnow().utctimetuple()
                    )

                # Create the response object with the JWT payload.
                response_data = {
                    'token': jwt_encode_handler(payload)
                }

                return Response(response_data)
        else:
            return Response({
                    'status': 'Bad request',
                    'message': 'Authentication could not be performed with received data.'
            }, status=status.HTTP_400_BAD_REQUEST)

在我的 urls.py 中,我包含了以下路线:

In my urls.py I included the following route:

urlpatterns = patterns('',
    ...
    url(r'^api/v1/auth/facebook/', FacebookView.as_view()),
    ...
)

前端

现在后端身份验证已连接,您可以使用任何前端库发送 access_token 并对用户进行身份验证.在我的例子中,我使用了 AngularJS.

在控制器文件中,我这样调用 API:

In a controller file I call the API like so:

/**
* This function gets called after successfully getting the access_token from Facebook's API.
*/
function successLoginFbFn(response) {
    var deferred = $q.defer();
    $http.post('/api/v1/auth/facebook/', {
        "access_token": response.authResponse.accessToken, 
        "backend": "facebook"
    }).success(function(response, status, headers, config) {
        // Success
        if (response.token) {
            // Save the token to localStorage and redirect the user to the front-page.
            Authentication.setToken(response.token);
            window.location = '/';
        }
        deferred.resolve(response, status, headers, config);
    }).error(function(response, status, headers, config) {
        // Error
        console.error('Authentication error.');
        deferred.reject(response, status, headers, config);
    });
}

通过这种方法,您可以混合使用这两个插件.所有发送的令牌都将来自 django-rest-framework-jwt,即使用户仍然可以使用 Facebook、Google、Twitter 等网站提供的令牌进行身份验证.

With this approach you can mix the two plugins. All sent tokens will be coming from django-rest-framework-jwt even though users can still authenticate themselves with the ones provided by sites such as Facebook, Google, Twitter, etc.

我只展示了通过 Facebook 进行身份验证的方法,但是您可以对其他提供商采用类似的方法.

I only showed the approach to authenticate through Facebook, however you can follow a similar approach for other providers.

相关文章