Django-rest-auth + Allauth Twitter。错误89.令牌无效或过期

时间:2018-03-09 17:39:07

标签: python django django-rest-auth allauth

我使用Django和Allauth + REST-Auth进行SPA社交登录并成功设置了Facebook,VK和Google授权,但在添加Twitter时遇到了问题。它最终以{"代码":89,"消息":"无效或过期的令牌。"} 看起来我错过了一些东西&标准登录与Twitter的工作原理

以下是我的尝试:

首先,我按照doc:

中的描述设置了Twitter登录端点
class TwitterLogin(SocialLoginView):
    serializer_class = TwitterLoginSerializer
    adapter_class = CustomTwitterOAuthAdapter

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

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网址http://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')

1 个答案:

答案 0 :(得分:0)

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

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

我使用了以下基于vue-authenticate的JS helper类。要弹出并从回调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身份验证网址作为响应
  2. 我使用前端响应中的url打开一个弹出窗口,允许用户进行身份验证
  3. 我向TwitterReceiveView发出POST请求,并在twitter auth之后附加响应URL

其他所有内容都放置到位,并向用户返回访问密钥。

无论如何, 谢谢,我弄乱了js和python中的库负载,但这只是做事的最佳方法