【问题标题】:Django-rest-auth + Allauth Twitter. Error 89. Invalid or expired tokenDjango-rest-auth + Allauth Twitter。错误 89。令牌无效或过期
【发布时间】:2018-03-09 17:39:06
【问题描述】:

我正在使用带有 Allauth + REST-Auth 的 Django 进行 SPA 社交登录,并成功设置了 Facebook、VK 和 Google 授权,但在添加 Twitter 时遇到了问题。它以 {"code":89,"message":"Invalid or expired token."} 结尾。 看起来我错过了一些“使用 Twitter 进行标准登录”的功能

这是我的尝试:

首先,我已经按照文档中的描述设置了 Twitter 登录端点:

class TwitterLogin(SocialLoginView):
    serializer_class = TwitterLoginSerializer
    adapter_class = CustomTwitterOAuthAdapter

它具有 post 方法,期望 access_token 和 token_secret 因此创建了重定向视图以接收来自 twitter 的重定向,完成登录并通过模板渲染将内部 django 令牌设置为浏览器 localStorage(带有几行 JS 行):

class TwitterReceiveView(APIView):

    def get(self, request, *args, **kwargs):
        access_token = request.query_params.get('oauth_token')
        token_secret = request.query_params.get('oauth_verifier')
        params = {'access_token': access_token,
                  'token_secret': token_secret}
        try:
            result = requests.post(settings.DOMAIN + reverse('tw_login'), data=params).text
            result = json.loads(result)
        except (requests.HTTPError, json.decoder.JSONDecodeError):
            result = {}
        access_token = result.get('access_token')
        context = {'access_token': access_token}
        return render(request, 'account/local_storage_setter.html',
                      context, content_type='text/html')

不得不提的是,我尝试了两种方法来启动进程(获取初始令牌) 1.使用标准allauth urlhttp://0.0.0.0:8080/accounts/twitter/login 2. 创建了另一个可以从 SPA 使用的视图(使用 lib python oauth2):

class TwitterGetToken(APIView):

    def get(self, request, *args, **kwargs):
        request_token_url = 'https://api.twitter.com/oauth/request_token'
        authorize_url = 'https://api.twitter.com/oauth/authorize'

        app = SocialApp.objects.filter(name='Twitter').first()
        if app and app.client_id and app.secret:
            consumer = oauth.Consumer(app.client_id, app.secret)
            client = oauth.Client(consumer)

            resp, content = client.request(request_token_url, "GET")
            if resp['status'] != '200':
                raise Exception("Invalid response {}".format(resp['status']))

            request_token = dict(urllib.parse.parse_qsl(content.decode("utf-8")))

            twitter_authorize_url = "{0}?oauth_token={1}"\
                .format(authorize_url, request_token['oauth_token'])

            return redirect(twitter_authorize_url)

        raise Exception("Twitter app is not set up")

我什至尝试为 FacebookLoginView 编写 get 方法并直接将 twitter 回调传递给它

class TwitterLogin(SocialLoginView):
    serializer_class = TwitterLoginSerializer
    adapter_class = TwitterOAuthAdapter

    def get(self, request, *args, **kwargs):
        data = {
            'access_token': request.query_params.get('oauth_token'),
            'token_secret': request.query_params.get('oauth_verifier')
        }
        self.request = request
        self.serializer = self.get_serializer(data=data,
                                              context={'request': request})
        self.serializer.is_valid(raise_exception=True)

        self.login()
        return self.get_response()

所有方法都导致我提到了错误。请您就我的情况提出一些建议。提前谢谢!

更新: 以下是它对我的工作方式:

import json
import requests
import urllib.parse
import oauth2 as oauth
from requests_oauthlib import OAuth1Session

from django.urls import reverse
from django.conf import settings
from django.shortcuts import redirect, render
from rest_framework.views import APIView
from allauth.socialaccount.models import SocialApp
from allauth.socialaccount.providers.twitter.views import TwitterOAuthAdapter, TwitterAPI
from rest_auth.social_serializers import TwitterLoginSerializer
from rest_auth.registration.views import SocialLoginView


class TwitterGetToken(APIView):
    '''
    Initiates Twitter login process
    Requests initial token and redirects user to Twitter
    '''

    def get(self, request, *args, **kwargs):
        request_token_url = 'https://api.twitter.com/oauth/request_token'
        authorize_url = 'https://api.twitter.com/oauth/authorize'

        app = SocialApp.objects.filter(name='Twitter').first()
        if app and app.client_id and app.secret:
            consumer = oauth.Consumer(app.client_id, app.secret)
            client = oauth.Client(consumer)

            resp, content = client.request(request_token_url, "GET")
            if resp['status'] != '200':
                raise Exception("Invalid response {}".format(resp['status']))

            request_token = dict(urllib.parse.parse_qsl(content.decode("utf-8")))

            twitter_authorize_url = "{0}?oauth_token={1}"\
                .format(authorize_url, request_token['oauth_token'])

            return redirect(twitter_authorize_url)

        raise Exception("Twitter app is not set up")



class TwitterLogin(SocialLoginView):
    '''
    Takes the final twitter access token, secret
    Returns inner django Token
    '''
    serializer_class = TwitterLoginSerializer
    adapter_class = TwitterOAuthAdapter


class TwitterReceiveView(APIView):
    '''
    Receives Twitter redirect, 
    Requests access token
    Uses TwitterLogin to logn and get django Token
    Renders template with JS code which sets django Token to localStorage and redirects to SPA login page
    '''

    def get(self, request, *args, **kwargs):
        access_token_url = 'https://api.twitter.com/oauth/access_token'
        callback_uri = settings.DOMAIN + '/accounts/twitter/login/callback/'

        app = SocialApp.objects.filter(name='Twitter').first()
        client_key = app.client_id
        client_secret = app.secret

        oauth_session = OAuth1Session(client_key,
                                      client_secret=client_secret,
                                      callback_uri=callback_uri)

        redirect_response = request.get_full_path()
        oauth_session.parse_authorization_response(redirect_response)
        token = oauth_session.fetch_access_token(access_token_url)

        params = {'access_token': token['oauth_token'],
                  'token_secret': token['oauth_token_secret']}
        try:
            result = requests.post(settings.DOMAIN + reverse('tw_login'),
                                   data=params).text
            result = json.loads(result)
        except (requests.HTTPError, json.decoder.JSONDecodeError):
            result = {}
        access_token = result.get('access_token')
        context = {'access_token': access_token,
                   'domain': settings.DOMAIN}
        return render(request, 'account/local_storage_setter.html',
                      context, content_type='text/html')

【问题讨论】:

    标签: python django django-allauth django-rest-auth


    【解决方案1】:

    很棒的代码,感谢您发布!

    我想补充一点,用户身份验证可以直接从前端完成,并且鉴于您正在编写 SPA,这样做似乎合乎逻辑,而不是在后端重定向您(哪种打破了 RESTful 的概念)进行身份验证,然后发送响应。

    我使用了以下基于 vue-authenticate 的 JS 助手类。弹出并从回调 url 获取信息

    export default class OAuthPopup {
      constructor(url, name, redirectURI) {
        this.popup = null
        this.url = url
        this.name = name
        this.redirectURI = redirectURI
      }
    
      open() {
        try {
          this.popup = window.open(this.url, this.name)
          if (this.popup && this.popup.focus) {
            this.popup.focus()
          }
          return this.pooling()
        } catch(e) {
          console.log(e)
        }
      }
    
      pooling() {
        return new Promise((resolve, reject) => {
          let poolingInterval = setInterval(() => {
            if (!this.popup || this.popup.closed || this.popup.closed === undefined) {
              clearInterval(poolingInterval)
              poolingInterval = null
              reject(new Error('Auth popup window closed'))
            }
    
            try {
              var popupLocation = this.popup.location.origin + this.popup.location.pathname
              if (popupLocation == this.redirectURI) {
                if (this.popup.location.search || this.popup.location.hash ) {
    
                  const urlParams = new URLSearchParams(this.popup.location.search);
    
                  var params = {
                    oauth_token: urlParams.get('oauth_token'),
                    oauth_verifier: urlParams.get('oauth_verifier'),
                    url: this.popup.location.href
                  }
    
                  if (params.error) {
                    reject(new Error(params.error));
                  } else {
                    resolve(params);
                  }
    
                } else {
                  reject(new Error('OAuth redirect has occurred but no query or hash parameters were found.'))
                }
    
                clearInterval(poolingInterval)
                poolingInterval = null
                this.popup.close()
              }
            } catch(e) {
              // Ignore DOMException: Blocked a frame with origin from accessing a cross-origin frame.
            }
          }, 250)
        })
      }
    }
    

    我遵循的方法与您的相似:

    1. 我向 TwitterGetToken 发出 GET 请求,并将 Twitter auth url 作为响应返回
    2. 我使用前端响应中的 url 打开一个允许用户进行身份验证的弹出窗口
    3. 我向 TwitterReceiveView 发出 POST 请求,并在 twitter auth 之后附加响应 url

    其他一切都到位了,用户会得到一个访问密钥。

    无论如何, 谢谢,我在 js 和 python 中处理了大量的库,但这只是做事的最佳方式

    【讨论】:

      猜你喜欢
      • 2020-08-17
      • 1970-01-01
      • 1970-01-01
      • 2018-02-14
      • 1970-01-01
      • 2013-08-18
      • 1970-01-01
      • 2016-05-24
      • 1970-01-01
      相关资源
      最近更新 更多