1. 程式人生 > 實用技巧 >20.第三方微博登入

20.第三方微博登入

目錄

1.微博三方登入流程

https://api.weibo.com/oauth2/authorize?
client_id=4122644977 
&response_type=code 
&state=study& 
forcelogin=true& 
redirect_uri=https%3A%2F%2Fstudy.163.com%2Fsns%2Fweibo%2FoAuthCallback.htm%3Foaut hType%3Dlogin%26returnUrl%3DaHR0cHM6Ly9zdHVkeS4xNjMuY29tL3Byb3ZpZGVyLzQwMDAwMDAwM DQ3ODAxMi9pbmRleC5odG0%2FZnJvbT1zdHVkeQ%3D%3D%26nrsstcw%3Dfalse%26nc%3Dtrue### 
https://study.163.com/provider/400000000478012/index.htm?from=study

1.1 前端獲取認證code

  • 1.在Vue頁面載入時 動態傳送請求獲取微博授權url
  • 2.django收到請求的url後,通過微博 應用ID(client_id)和回撥地址(redirect_uri) 動態 生成授權url返回給Vue
  • 3.當用戶點選上面的url進行掃碼,授權成功會 跳轉我們的回撥介面並附加code引數
  • 4.Vue獲取到微博返回的code後,會將code傳送給django後端 (上面的redirect_uri)

1.2 獲取微博access_token

  • 後端獲取code後,結合client_id、client_secret、redirect_uri引數進行傳遞,獲取微博 access_token

1.3 獲取微博使用者基本資訊並儲存到資料庫

  • 使用獲得的access_token呼叫獲取使用者基本資訊的介面, 獲取使用者第三方平臺的基本資訊
  • 使用者基本資訊 儲存到資料庫,然後關聯本地使用者,然後將使用者資訊返回給前端

1.4 生成token給Vue

  • django後端藉助微博認證成功後,可以使用JWT生成token,返回給Vue
  • Vue將token儲存到localStorage中 ,以便使用者訪問其他頁面進行身份驗證

2.第三方登入與本地登入的關聯(三種情況)

2.1 情況1: 本地未登入,第一次登入第三方

此時相當於註冊,直接把第三方資訊拉取來並註冊成本地使用者就可以了,並建立本地使用者與第三方使用者 (openid)的繫結關係

2.2 情況2:本地未登入,再次登入第三方

此時使用者已註冊,獲取到openid後直接找出對應的本地使用者即可

2.3 情況3:本地登入,並繫結第三方

這個只要將獲取到的openid繫結到本地使用者就可以了

3.oauth認證原理

  • OAuth是一個開放標準,允許使用者讓第三方應用訪問該使用者在某一網站上儲存的私密的資源,而無 需將使用者名稱和密碼提供給第三方應用。
  • OAuth允許使用者提供一個令牌,而不是使用者名稱和密碼來訪問他們存放在特定服務提供者的資料。
  • 這個code如果能出三方換取到資料就證明這個使用者是三方真實的使用者

4.為什麼使用三方登入

  • 服務方希望使用者註冊, 而使用者懶得填註冊時的各種資訊(主要是為了保證使用者的唯一性,各種使用者名稱已 佔用,密碼格式限制).
  • 而像微信, QQ, 微博等幾乎每個人都會安裝的應用中使用者肯定會在其中某一個應用中已經註冊過, 證明該使用者在已經註冊的應用中的唯一性.
  • 第三方登入的實質就是在授權時獲得第三方應用提供的代表了使用者在第三方應用中的唯一性的 openid.並將openid儲存在第三方服務控制的本地儲存.

4.微博賬號 註冊

4.1.註冊賬號

  • 註冊新浪微博賬號

新浪微博開放平臺:https://open.weibo.com/

  • 進入首頁。建立網站接入的應用:

  • 選擇立即接入,進入到建立應用頁面

  • 建立應用

  • 高階資訊頁面

4.2介面文件

  • 檢視介面文件

      https://open.weibo.com/wiki/授權機制說明
    

  • OAuth2.0授權認證
介面 說明
OAuth2/authorize 請求使用者授權Token
OAuth2/access_token 獲取授權過的Access Token,UID

5.生成微博授權URL介面

5.1建立apps/oauth模組進行oauth認證

'''1.1 在apps資料夾下新建應用: oauth''' 
cd syl/apps
python ../manage.py startapp oauth   # 切換到apps資料夾下執行建立命令

'''1.2 新增子路由: oauth/urls.py''' 
from django.urls import path 
from . import views
urlpatterns = [

]

'''1.3 在syl/settings.py中新增應用''' 
INSTALLED_APPS = [
	'oauth.apps.OauthConfig', 
]

'''1.4 在syl/urls.py主路由中新增''' 
urlpatterns = [
	path('oauth/', include('oauth.urls')), 
]

5.2生成微博授權URL介面

1.1 新增子路由: oauth/urls.py
urlpatterns = [
	path('weibo/', views.WeiboUrl.as_view()),  # /oauth/weibo/ 返回微博登入地址 
]
1.2 syl/settings.py中配微博地址
1.3 檢視函式: oauth/views.py
from rest_framework.permissions import AllowAny 
from rest_framework.response import Response 
from rest_framework.views import APIView 
from urllib.parse import urlencode

# 生成前端跳轉到微博掃碼頁面的url 
class WeiboUrl(APIView):
	'''
		生成微博的登陸頁面路由地址
		https://api.weibo.com/oauth2/authorize?       # 微博oauth認證地址
		client_id=4152203033&                         # 註冊開發者id
		response_type=code&
		redirect_uri=http://127.0.0.1:8888/oauth/callback/     # 獲取code後將code回 調給後端地址
	'''

	 # 自定義許可權類
	 permission_classes = (AllowAny,)
	 def post(self, request):
	 	url = 'https://api.weibo.com/oauth2/authorize?'              # 微博授權的 url地址
	 	data = {
	 		'client_id': '3516473472',                               # WEIBO_APP_KEY,
	 		'response_type': 'code',
	 		'redirect_uri': 'http://127.0.0.1:8888/oauth/callback/',  # VUE的回 調,微博後臺授權的回撥地址
	 	}
	 	weibo_url = url + urlencode(data)
	 	# https://api.weibo.com/oauth2/authorize? client_id=4152203033&response_type=code&redirect_uri=http://127.0.0.1:8000/api/we ibo_back/
	 	# return Response({'weibo_url': weibo_url})
	 	return Response({'code': '0', 'msg': '成功', 'data': {'url': weibo_url}})

5.3測試生成微博授權URL介面

  • 測試介面獲取新浪微博地址

      http://192.168.56.100:8888/oauth/weibo/
    

  • 在瀏覽器訪問返回地址即可回到新浪掃碼介面

      https://api.weibo.com/oauth2/authorize? 
      client_id=3516473472&response_type=code&redirect_uri=http%3A%2F%2F127.0.0.1%3A808 0%2Fweibo_callback
    

6.在Vue頁面載入時動態傳送請求獲取微博授 權url

6.1 在components\common\lab_header.vue中寫oauth動態獲取微 博授權URL

 // 獲取微博登入地址
 oauth() {
 	// 從後端獲取 微博登入地址
 	oauth_post().then((resp) => {
 		console.log(resp)
 		//{'code': '0', 'msg': '成功', 'data': {'url': url}}
 		let url = resp.data.url;
 		this.weibo_url = url;
 	})
},

6.2在vue的mounted函式中呼叫獲取微博授權url函式

mounted() {
	this.oauth()
},

6.3點選"登入"彈出的form表單中加入url

<form
	action="/login"
	method="post" 
	>

	<div class="form-group widget-signin">
		<a :href="weibo_url"><i class="fa fa-weibo"></i></a>
	</div> 
	
</form>

7.微博回撥介面

7.1oauth/urls.py中新增路由

urlpatterns = [
	 path('weibo/callback/', views.OauthWeiboCallback.as_view()),  # /oauth/weibo/callback/ 
]

7.2oauth/views.py中新增試圖函式

http://192.168.56.100:8888/oauth/weibo/callback/
from .models import OauthUser 
from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler 
from user.utils import jwt_response_payload_handler

# 通過vue前端傳入的code,微博身份驗證 
class OauthWeiboCallback(APIView):
	# 自定義許可權類
	permission_classes = (AllowAny,)

	def post(self, request):
		# 接收vue端傳過來的code(微博的使用者code)
		code = request.data.get('code')
		data = {
			'client_id': '3516473472',
			'client_secret': '7862ee35a0dc6f0345d0464dc34f14fc',
			'grant_type': 'authorization_code',
			'code': code,
			'redirect_uri': 'http://127.0.0.1:8888/oauth/callback/',
		}
		url = 'https://api.weibo.com/oauth2/access_token'
		data = requests.post(url=url, data=data).json()  # 拿取請求的返回結果
		access_token = data.get('uid')         # 獲取到的微博token
		weibo_uid = data.get('access_token')   # 獲取到少碼使用者的id

		#  根據uid 查詢繫結情況
		try:
			oauth_user = OauthUser.objects.get(uid=weibo_uid, oauth_type='1')
		except Exception as e:
			oauth_user = None
		# 返回動作,  登入成功/需要繫結使用者 type 0 登入成功,  1, 授權成功, 需要繫結
		if oauth_user:
			#  如果綁定了, 返回token, 登入成功
			user = oauth_user.user

			payload = jwt_payload_handler(user)
			token = jwt_encode_handler(payload)
			# jwt_response_payload_handler為user模組定義的jwt返回的資訊
			data = jwt_response_payload_handler(token, user)
			data['type'] = '0'  # 指定為登入成功
			return Response({'code': 0, 'msg': '登入成功', 'data': data})
		else:
			# 5. 如果沒繫結, 返回標誌, 讓前端跳轉到繫結頁面
			return Response({'code': 0, 'msg': '授權成功', 'data': {'type': '1', 'uid': weibo_uid}})

7.3oauth/models.py中新增使用者繫結模型

# 把三方的使用者資訊,和本地的使用者資訊進行繫結 
class OauthUser(models.Model):
	OAUTHTYPE = (
		('1', 'weibo'),
		('2', 'weixin'),
	)
	uid = models.CharField('三方使用者id', max_length=64)
	# 三方使用者id
	user = models.ForeignKey('user.User', on_delete=models.CASCADE)
	# 本地使用者外來鍵,關聯User表
	oauth_type = models.CharField('認證型別', max_length=10, choices=OAUTHTYPE)
	

7.4遷移資料庫

python manager.py makemigrations 
python manager.py migrate

8.vue微博回撥空頁面

注:微博回撥空頁面為: http://127.0.0.1:8888/oauth/callback/

8.1 頁面路徑components\oauth.vue

<template>
	<div>
		<p>跳轉中...</p>
	</div>
</template>
<script>
  import { oauth_callback_post, } from './axios_api/api'
  export default {
    mounted() {
      this.getCode()
    },
    methods: {
    // 獲取微博傳過來的code,傳送給django後端進行驗證
      getCode() {
        // 獲取url中的code 資訊
        // 當前url 是  http://mysyl.com:8080/oauth/callback/?code=fe6cbe07708aecf4a2b3d942ed692c4c
        let code = this.$route.query.code
        console.log(this.$route.query)
        // 給後端傳送code
        let params = { code: code }
        oauth_callback_post(params).then((resp) => {
          console.log(resp)
          // code: 0
          // msg: "授權成功"
          // data: {type: "1", uid: "7410919278"}
          if (resp.data.type == '0') {
            // code: 0
            // msg: "登入成功"
            // data: {
            // authenticated: "true"
            // email: ""
            // id: 1
            // name: "admin"
            // role: null
            // token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTk3OTAwNTcyLCJlbWFpbCI6IiIsIm9yaWdfaWF0IjoxNTk3ODE0MTcyfQ.aQT7GSR_xQBPMlB4_k8-zTHnx0ow3OC2KHa3C8MgilY"
            // type: "0"
            // username: "admin"}
            let res = resp.data
            localStorage.setItem('username', res.username)
            // localStorage.setItem('img', res.img)
            localStorage.setItem('token', res.token)
            localStorage.setItem('uid', res.id)
            this.login_username = res.username
            this.opened = false
            // alert(res.message)
            this.$router.push('/')

          }
          if (resp.data.type == '1') {
            this.visiable = true
            this.uid = resp.data.uid
          }
        })
      },
</script>

9.繫結微博使用者介面

9.1oauth/urls.py中新增路由

urlpatterns = [
	 path('weibo/binduser/', views.OauthWeiboBindUser.as_view()),  # /oauth/weibo/callback/  
]

9.2oauth/views.py中新增試圖函式

class OauthWeiboBindUser(APIView):

	permission_classes = (AllowAny,)

	def post(self, request):
		# 繫結使用者, 1. 已註冊使用者, 2. 未註冊使用者
		# 1.1 獲取使用者名稱, 密碼, weibo_uid
		username = request.data.get('username')
		password = request.data.get('password')
		weibo_uid = request.data.get('weibo_uid')
		if not all([username, password, weibo_uid]):
			return Response({'code': 999, 'msg': '引數不全'})
		# 0.判斷是否存在此使用者
		try:
			user = User.objects.get(username=username)
		except Exception as e:
			user = None
		# 1. 已註冊使用者
		if user:
			# 1.2 , 如果存在就驗證 密碼, 驗證通過,就繫結, 返回token,登入成功
			if user.check_password(password):
				ou = OauthUser(uid=weibo_uid, user=user, oauth_type='1')
				ou.save()
				payload = jwt_payload_handler(user)   # 通過user物件獲取到jwt的 payload資訊
				token = jwt_encode_handler(payload)   # 生成token
				data = jwt_response_payload_handler(token, user)
				data['type'] = '0'  # 指定為登入成功
				return Response({'code': 0, 'msg': '登入成功', 'data': data})
			else:
				return Response({'code': 999, 'msg': '密碼錯誤'})
		else:
			# 2. 未註冊使用者
			# 2.1 生成新使用者, 設定使用者名稱密碼, 儲存, 然後繫結, 返回token, 登入成功
			user = User(username=username)
			user.set_password(password)
			user.save()
			ou = OauthUser(uid=weibo_uid, user=user, oauth_type='1')
			ou.save()
			payload = jwt_payload_handler(user)
			token = jwt_encode_handler(payload)
			data = jwt_response_payload_handler(token, user)
			data['type'] = '0'  # 指定為登入成功
			return Response({'code': 0, 'msg': '登入成功', 'data': data})

10.vue微博回撥頁面

注:微博回撥頁面為: http://127.0.0.1:8888/oauth/callback/

10.1頁面路徑components\oauth.vue

<template>
  <div>
    <div v-show='visiable'>
      繫結使用者
      使用者名稱: <input
      type="text"
      v-model="username"
      @blur="check_username"
    >
      <span>{{username_message}}</span>
      密碼: <input
      type="password"
      v-model="password"
    >
      <button @click="bindUser">繫結</button>
    </div>
  </div>
</template>
<script>
  import { oauth_callback_post, oauth_binduser_post, user_count } from './axios_api/api'
  export default {
    data() {
      return {
        visiable: false, // 繫結使用者視窗
        uid: '', // weibo_uid
        username: '',
        password: '',
        username_message: '',
        username_error: false
      }
    },
    mounted() {
      this.getCode()
    },
    methods: {
      // 判斷使用者名稱
      check_username() {
        console.log('判斷使用者名稱')
        console.log(this.username == '')
        var reg = new RegExp(/^[a-zA-Z0-9_-]{4,16}$/); //字串正則表示式 4到14位(字母,數字,下劃線,減號)
        if (this.username == '') {
          this.username_message = '使用者名稱不能為空'
          this.username_error = true
          return false
        }
        if (!reg.test(this.username)) {
          this.username_message = '使用者名稱格式不正確'
          this.username_error = true
          return false
        } else {
          // 去後端檢查使用者名稱使用數量
          user_count({ type: 'username', data: this.username }).then((res) => {
            console.log(res)
            if (res.data.count > 0) {
              this.username_message = '使用者名稱已存在, 請輸入密碼'
              this.username_error = false
            } else {
              this.username_message = '使用者名稱可用, 將建立新使用者,請輸入密碼'
              this.username_error = false
            }
          })
        }
      },
      getCode() {
        // 獲取url中的code 資訊
        // 當前url 是  http://mysyl.com:8080/oauth/callback/?code=fe6cbe07708aecf4a2b3d942ed692c4c
        let code = this.$route.query.code
        console.log(this.$route.query)
        // 給後端傳送code
        let params = { code: code }
        oauth_callback_post(params).then((resp) => {
          console.log(resp)
          // code: 0
          // msg: "授權成功"
          // data: {type: "1", uid: "7410919278"}
          if (resp.data.type == '0') {
            // code: 0
            // msg: "登入成功"
            // data: {
            // authenticated: "true"
            // email: ""
            // id: 1
            // name: "admin"
            // role: null
            // token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTk3OTAwNTcyLCJlbWFpbCI6IiIsIm9yaWdfaWF0IjoxNTk3ODE0MTcyfQ.aQT7GSR_xQBPMlB4_k8-zTHnx0ow3OC2KHa3C8MgilY"
            // type: "0"
            // username: "admin"}
            let res = resp.data
            localStorage.setItem('username', res.username)
            // localStorage.setItem('img', res.img)
            localStorage.setItem('token', res.token)
            localStorage.setItem('uid', res.id)
            this.login_username = res.username
            this.opened = false
            // alert(res.message)
            this.$router.push('/')

          }
          if (resp.data.type == '1') {
            this.visiable = true
            this.uid = resp.data.uid
          }
        })
      },
      bindUser() {
        if(this.username_error){
          return
        }
        // 傳送  使用者名稱, 密碼, weibo_uid 到後端介面, 進行繫結
        let params = { username: this.username, password: this.password, weibo_uid: this.uid }
        oauth_binduser_post(params).then((resp) => {
          console.log(resp)
          let res = resp.data
          localStorage.setItem('username', res.username)
          // localStorage.setItem('img', res.img)
          localStorage.setItem('token', res.token)
          localStorage.setItem('uid', res.id)
          this.login_username = res.username
          this.opened = false
          // alert(res.message)
          this.$router.push('/')
        })
      }
    }
  }
</script>