Vue结合Django-Rest-Frameword实现登录认证(二)
作者:小土豆
博客园:https://www.cnblogs.com/HouJiao/
掘金:https://juejin.im/user/2436173500265335
微信公众号:不知名宝藏程序媛(关注"不知名宝藏程序媛"免费领取前端电子书籍。文章公众号首发,关注公众号第一时间获取最新文章。)
作者文章的内容均来源于自己的实践,如果觉得有帮助到你的话,可以点赞❤️给个鼓励或留下宝贵意见
前言
在上一篇 Vue结合Django-Rest-Frameword实现登录认证(一) 文章中,我们利用token
实现了一个非常基础的用户登录认证
功能。
那这一节需要对前面实现的内容进行优化:
1. 优化axios:请求封装、认证信息的封装
2. 注销
3. 设置token过期时间
优化axios
axios
的优化就是对axios
进行一个封装,单独抽离出来一个模块,负责编写请求的API
,在组件中只需要调用这个API
传入对应的参数,就能在请求发送
的同时实现认证信息的设置
。
// 代码位置:/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
做一个封装,以登录
为例。
// 代码位置:/src/api/login.js
import {get, post} from '@/utils/request.js'
export const login = (loginForm) => post('userAuth/login', loginForm)
然后我们在登录组件中调用这个API
发起请求。
// 引入前面封装好的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
字段。
注销
当用户点击注销
时,我们应该做的就是清除本地保存的token
。
logout: function(){
// 清除token
localStorage.removeItem("token");
// 跳转至登录页 登录页面在router.js中的配置的path就是‘/’
this.$router.push("/");
}
清除以后呢,如果我们直接在浏览器中手动输入url
进入某个页面,就可以看到响应出现401
。
此时用户只有再次进入登录页面
进行登录,才能正常访问页面。
那对于上面注销
之后返回的401
,实际上比较合理的结果应该是直接跳转到登录页
。因此我们还需要在发起请求前对token
进行一个判断,如果没有token
存在,则直接跳转至登录页。
上面描述的功能使用 守卫导航 实现
代码定义在router.js
中
// 给路由定义前置的全局守卫
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
。
那接下来就是这个功能的代码实现了。
后端配置token有效期
后端在userAuth
模块下新建一个auth.py
,自定义一个用户认证类
,继承TokenAuthentication
,并且实现token
过期的处理。
# -*- 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
。
# 设置全局身份认证方案
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
。
@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
时,就弹框提示用户登录过期,强制用户退出登录。
//响应拦截器
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
的创建时间。
可以看到数据库中已有的token
的创建时间是2020-09-17
,现在的时间是2020-10-10
号,已经超出token
的有效期。
前面设置
token
的有效期是7
天
然后我们刷新一下页面。
发现已经成功弹出强制用户重新登录
。
当我们点击重新登录
按钮后,就会请求后端的logout
接口,数据库中已有的token
会被删除,删除成功之后本地缓存在localStorage
中的token
也会被删除,最后会跳转到产品的登录页面。
数据库中的
token
已经被删除
接着在登录页面输入用户名
和密码
重新登录,就会发现数据库中的token
已经更新。
最后
关于 《vue
结合Django-Rest-Frameword
结合实现登录认证》这个系列的文章就结束了。
文章基本都是实战操作,希望可以给大家一个参考。
文章索引
《Vue结合Django-Rest-Frameword实现登录认证(一)》
《Vue结合Django-Rest-Frameword实现登录认证(二)》
作者:小土豆
博客园:https://www.cnblogs.com/HouJiao/
掘金:https://juejin.im/user/2436173500265335
微信公众号:不知名宝藏程序媛(关注"不知名宝藏程序媛"免费领取前端电子书籍。文章公众号首发,关注公众号第一时间获取最新文章。)
作者文章的内容均来源于自己的实践,如果觉得有帮助到你的话,可以点赞❤️给个鼓励或留下宝贵意见