Vue結合Django-Rest-Frameword結合實現登入認證(二)
阿新 • • 發佈:2020-10-12
>作者:小土豆biubiubiu
>
>部落格園:https://www.cnblogs.com/HouJiao/
>
>掘金:https://juejin.im/user/2436173500265335
>
>
>微信公眾號:土豆媽的碎碎念(掃碼關注,一起吸貓,一起聽故事,一起學習前端技術)
>
>作者文章的內容均來源於自己的實踐,如果覺得有幫助到你的話,可以點贊❤️給個鼓勵或留下寶貴意見
# 前言
在上一篇 [Vue結合Django-Rest-Frameword實現登入認證(一)](https://juejin.im/post/6874872436962099208/) 文章中,我們利用`token`實現了一個非常基礎的`使用者登入認證`功能。
那這一節需要對前面實現的內容進行優化:
1. 優化axios:請求封裝、認證資訊的封裝
2. 登出
3. 設定token過期時間
# 優化axios
`axios`的優化就是對`axios`進行一個封裝,單獨抽離出來一個模組,負責編寫請求的`API`,在元件中只需要呼叫這個`API`傳入對應的引數,就能在`請求傳送`的同時實現`認證資訊的設定`。
```javascript
// 程式碼位置:/src/utils/request.js
/*
* @Description: 封裝axios請求 axios官網:http://www.axios-js.com/zh-cn/
* @version: 1.0.0
* @Author: houjiaojiao
* @Date: 2020-07-23 16:32:19
* @LastEditors: houjiaojiao
* @LastEditTime: 2020-09-01 17:30:46
*/
import axios from 'axios'
// 新建一個 axios 例項
let instance = axios.create({
baseURL: '/api/cert/',
});
// 請求攔截器
instance.interceptors.request.use(
// 在傳送請求前做一些事情
request => {
// 在傳送請求前給每個請求頭帶上Authorization欄位
const auth = 'Token ' + localStorage.getItem('token');
request.headers.Authorization
return request;
},
// 請求出現錯誤做一些事情
error => {
console.log('There are some problems with this request');
console.log(error);
return Promise.reject(error);
}
)
//響應攔截器
instance.interceptors.response.use(
response => {
return response;
},
error => {
return Promise.reject(error);
}
)
// 封裝get請求
export function get(url, params){
return new Promise((resolve, reject) => {
instance.get(url, {
params
})
.then(response => {
resolve(response);
}).catch(error => {
reject(error)
})
})
}
// 封裝post請求
export function post(url, params){
return new Promise((resolve, reject) => {
instance.post(url, params)
.then(response => {
resolve(response)
}).catch(error => {
reject(error)
})
})
}
```
可以看到,我們對`axios`的`get`、`post`請求進行了封裝,同時我們將認證需要新增到`請求頭部`的`Authorization`欄位定義在了`axios`的`請求攔截器`中,這樣每一個請求都會攜帶這個`頭部欄位`。
接著我們在對請求的`API`做一個封裝,以`登入`為例。
```javascript
// 程式碼位置:/src/api/login.js
import {get, post} from '@/utils/request.js'
export const login = (loginForm) => post('userAuth/login', loginForm)
```
然後我們在登入元件中呼叫這個`API`發起請求。
```javascript
// 引入前面封裝好的API介面
import {login} from '@/api/login.js'
export default {
name: 'Login',
data() {
return {
loginForm: {
username: '',
password: '',
}
}
},
methods: {
login: function(){
// 直接呼叫API介面
login(this.loginForm).then(res => {
const {result, detail, errorInfo} = res.data;
if(result == true){
// 登入成功 設定token
localStorage.setItem('token', detail.token);
// 跳轉頁面
this.$router.push('/certMake');
}else{
this.$message({
showClose: true,
message: errorInfo,
type: 'error'
});
}
})
}
}
}
```
> 以上省略登入元件中`template`中的程式碼
最後在登入介面輸入`使用者名稱`和`密碼`,就可以正常登陸了。
之後我們在`瀏覽器`中點選其他的頁面,會發現每個發出的`請求頭部`都攜帶了`Authorization`欄位。
![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2dfac90b39f7463a86db96aff4a00292~tplv-k3u1fbpfcp-zoom-1.image)
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b19d7013e0ed4739bfcfd4a8403bfaa5~tplv-k3u1fbpfcp-zoom-1.image)
# 登出
當用戶點選`登出`時,我們應該做的就是清除本地儲存的`token`。
```javascript
logout: function(){
// 清除token
localStorage.removeItem("token");
// 跳轉至登入頁 登入頁面在router.js中的配置的path就是‘/’
this.$router.push("/");
}
```
清除以後呢,如果我們直接在瀏覽器中手動輸入`url`進入某個頁面,就可以看到響應出現`401`。
![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/283e7bac4ccd46769d17510f411d7460~tplv-k3u1fbpfcp-zoom-1.image)
此時使用者只有再次進入`登入頁面`進行登入,才能正常訪問頁面。
那對於上面`登出`之後返回的`401`,實際上比較合理的結果應該是直接跳轉到`登入頁`。因此我們還需要在發起請求前對`token`進行一個判斷,如果沒有`token`存在,則直接跳轉至登入頁。
> 上面描述的功能使用 [守衛導航](https://router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E5%85%A8%E5%B1%80%E5%89%8D%E7%BD%AE%E5%AE%88%E5%8D%AB) 實現
> 程式碼定義在`router.js`中
```javascript
// 給路由定義前置的全域性守衛
router.beforeEach((to, from, next) => {
let token = localStorage.getItem('token');
if(token){
// token存在 訪問login 跳轉至產品證書製作頁面
if(to.path == '/' || to.path == '/login'){
next('/certMake');
}else{
next();
}
}else{
// token不存在 路徑'/'就是登入頁面設定的path
if(to.path === '/'){
next();
}else{
next('/')
}
}
})
```
# 設定Token有效期
前面我們完成的`登入功能`,除了登出後需要登入,其他任何時候只要使用者成功登入過一次,就不需要在此登入了。這樣存在一個很大的安全隱患,那就是當用戶的`token`不慎洩露後,別人是可以沒有限制的操作我們的頁面。
因此最好的辦法就是給`token`設定一個`有效期`,當`有效期`到了以後,強制使用者`退出登入`,在下一次登入的時候重新生成新的`token`。
![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c26cae3debb84a608e19392fc750f4a3~tplv-k3u1fbpfcp-watermark.webp)
那接下來就是這個功能的程式碼實現了。
### 後端配置token有效期
後端在`userAuth`模組下新建一個`auth.py`,自定義一個`使用者認證類`,繼承`TokenAuthentication`,並且實現`token`過期的處理。
```python
# -*- coding: utf-8 -*-
# Create your views here.
from rest_framework.authentication import TokenAuthentication
from rest_framework import exceptions
from django.utils import timezone
from datetime import timedelta
from django.conf import settings
# token過期時間處理
class ExpiringTokenAuthentication(TokenAuthentication):
def authenticate_credentials(self, key):
model = self.get_model()
try:
token = model.objects.select_related('user').get(key=key)
except model.DoesNotExist:
raise exceptions.AuthenticationFailed('Invalid token.')
if not token.user.is_active:
raise exceptions.AuthenticationFailed('User inactive or deleted.')
# 重點就在這句了,這裡做了一個Token過期的驗證
# 如果當前的時間大於Token建立時間+DAYS天,那麼就返回Token已經過期
if timezone.now() > (token.created + timedelta(days=7)):
print "Token has expired"
# 過期以後 響應為401
raise exceptions.AuthenticationFailed('Token has expired')
return (token.user, token)
```
> 這裡設定`token`的有效期是7天
接著修改`setting`中配置的`全域性認證方案`為我們自定義的使用者認證`ExpiringTokenAuthentication`。
```python
# 設定全域性身份認證方案
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'userAuth.auth.ExpiringTokenAuthentication', # token過期時間
# 'rest_framework.authentication.TokenAuthentication', # token認證
)
}
```
接著我們在`userAuth`模組的`views.py`中定義`退出登入`的邏輯:退出登入時刪除資料庫中的`token`。
```python
@api_view(['GET'])
@permission_classes((AllowAny,))
@authentication_classes(())
def logout(request):
"""退出登入"""
result = True
errorInfo = u''
detail = {}
token = ''
authInfo = request.META.get('HTTP_AUTHORIZATION')
if authInfo:
token = authInfo.split(' ')[1]
try:
# 退出登入 刪除token
tokenObj = Token.objects.get(key=token)
tokenObj.delete()
except Exception as e:
traceback.print_exc(e)
print 'token not exist'
result = False
errorInfo = u'退出登入失敗'
return Response({"result": result, "detail": {}, "errorInfo": errorInfo})
```
### 前端設定
當`token`過期以後,後端會返回`401`,因此我們需要在`響應攔截器`中處理這個`401`,即當後端響應為`401`時,就彈框提示使用者登入過期,強制使用者退出登入。
```javascript
//響應攔截器
instance.interceptors.response.use(
response => {
return response;
},
error => {
// 在這裡處理一下token過期的邏輯
// 後端驗證token過期以後 會返回401
if(error.response.status == 401){
MessageBox.confirm('登入過期,請重新登入', '確定登出', {
confirmButtonText: '重新登入'
type: 'warning'
}).then(() => {
// 呼叫介面退出登入
get('/userAuth/logout').then( response => {
// 移除本地快取的token
localStorage.removeItem("token");
location.reload();
})
})
}
return Promise.reject(error);
}
)
```
### 結果演示
到此前後端的邏輯就完成了,我們來演示一下最後的結果。
首先我們先看一下資料庫中已有的`token`的建立時間。
![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bcabf41c5bd3452a9c425d0c04580fd5~tplv-k3u1fbpfcp-watermark.image)
可以看到資料庫中已有的`token`的建立時間是`2020-09-17`,現在的時間是`2020-10-10`號,已經超出`token`的有效期。
> 前面設定`token`的有效期是`7`天
然後我們重新整理一下頁面。
![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3caf447cf833411c9ab5cbaca7ea9f85~tplv-k3u1fbpfcp-watermark.image)
發現已經成功彈出強制使用者`重新登入`。
當我們點選`重新登入`按鈕後,就會請求後端的`logout`介面,資料庫中已有的`token`會被刪除,刪除成功之後本地快取在`localStorage`中的`token`也會被刪除,最後會跳轉到產品的登入頁面。
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b8b6babf1a73458a8475da3500026569~tplv-k3u1fbpfcp-watermark.image)
> 資料庫中的`token`已經被刪除
接著在登入頁面輸入`使用者名稱`和`密碼`重新登入,就會發現資料庫中的`token`已經更新。
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f71c248ae28b4854bc9e2dd6a38e5322~tplv-k3u1fbpfcp-watermark.image)
# 最後
關於 《`vue`結合`Django-Rest-Frameword`結合實現登入認證》這個系列的文章就結束了。
文章基本都是實戰操作,希望可以給大家一個參考。
# 文章索引
[《Vue結合Django-Rest-Frameword實現登入認證(一)》](https://juejin.im/post/6874872436962099208)
[《Vue結合Django-Rest-Frameword實現登入認證(二)》](https://juejin.im/post/6882566665524281357/)
# 關於
### 作者
小土豆biubiubiu
> 一個努力學習的前端小菜鳥,知識是無限的。堅信只要不停下學習的腳步,總能到達自己期望的地方
>
> 同時還是一個喜歡小貓咪的人,家裡有一隻美短小母貓,名叫土豆
### 部落格園
https://www.cnblogs.com/HouJiao/
### 掘金
https://juejin.im/user/2436173500265335
### 微信公眾號
土豆媽的碎碎念
> 微信公眾號的初衷是記錄自己和身邊的一些故事,同時會不定期更新一些技術文章
>
> 歡迎大家掃碼關注,一起吸貓,一起聽故事,一起學習前端技術
### 作者寄語
小小總結,歡迎大家指導~
# 參考文章