Vue+Django进行JWT认证(包含登录及发送请求)

做项目时需要用到jwt认证,简单记录一下~

1、JWT认证简易流程

在这里插入图片描述

2、安装jwt认证包

  • terminal窗口下载包
pip install djangorestframework-jwt
  • 在settings.py中添加代码如下:若无指定权限类则默认执行的权限
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        #'rest_framework.authentication.SessionAuthentication',
        #'rest_framework.authentication.BasicAuthentication',
    ),
}

import datetime
JWT_AUTH = {
	# JWT_EXPIRATION_DELTA 指明token的有效期
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}

3、用户登录

  • 用户登录时不需要认证和权限,所以视图层authentication_classes和permission_classes为空
# views.py
class LoginView(ViewSet):
    '''
    登录视图,用户名与密码匹配返回token
    '''
    authentication_classes = []
    permission_classes = []

    def post(self, request, *args, **kwargs):
        # 实例化得到一个序列化类的对象
        ser = LoginSerializer(data=request.data)
       
        if ser.is_valid(raise_exception=True):
            token = ser.context.get('token')
            # 如果通过,表示登录成功,返回手动签发的token
            # 如果失败,抛异常,就不用管了
            username = ser.context.get('username')
            id = ser.context.get('id')
            return APIResponse(token=token, id=id, username=username)
  • 登录序列化器里签发token
    JWT 包含三部分内容:header、payload 和 signature,其中 header 和 payload 部分为 JSON 格式,而 signature 则是对前两部分进行签名得到的。
from rest_framework_jwt.settings import api_settings
from django.contrib.auth import get_user_model

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

User = get_user_model()  # 获取用户模型

class LoginSerializer(serializers.ModelSerializer):  
    '''登录序列化器'''

    # 设置自定义的反序列化字段usr,pwd
    username = serializers.CharField(write_only=True)  # 重写 username , 否则会它会认为你想存数据
    password = serializers.CharField(write_only=True)

    class Meta:
        model = User
        fields = ['username', 'password']
        extra_kwargs = {
            'password': {'write_only': True}
        }

    # 全局钩子
    def validate(self, attrs):
        # username mobile 都可能是登录账户
        username = attrs.get('username')
        password = attrs.get('password')
        if re.match('^1[0-9]\d{9}$', username):  # 手机号正则
            user = User.objects.filter(mobile=username).first()
        else:  # 用户名登录
            user = User.objects.filter(username=username).first()
        if user and user.check_password(password):  # 如果登录成功,生成token
            payload = jwt_payload_handler(user)  # 通过user拿到payload
            token = jwt_encode_handler(payload)  # 通过payload拿到token
            print('生成token:' + token)
            # 视图类和序列化类之间通过context这个字典来传递数据
            self.context['token'] = token
            self.context['username'] = user.username
            self.context['id'] = user.id
            return attrs
        else:
            raise ValidationError("账户或密码错误")
  • 前端拿到token后可以存到浏览器中
    httptool是我创建的axios实例,后面有代码
methods: {
    // 登录方法
    loginhander() {
    	// 	axios实例
      httptool.post('/login/', {
        "username": this.username,
        "password": this.password,
      }).then(response => {
        console.log(response)         //http响应头
        console.log(response.data)    //http响应的请求体数据

        // 根据用户是否勾选了记住密码来保存用户认证信息
        if (this.remember) {
          // 记住登录
          sessionStorage.clear();  //  清除所有sessionStorage数据
          // localStorage的生命周期是永久的,关闭页面或浏览器之后localStorage中的数据也不会消失。除非主动删除,否则数据永远不会消失
          localStorage.token = response.data.token;     // 存储token
          localStorage.id = response.data.id;
          localStorage.username = response.data.username;
        } else {
          // 未记住登录
          localStorage.clear();
          // sessionStorage的生命周期仅在当前会话下有效,关闭浏览器窗口后就会被销毁
          sessionStorage.token = response.data.token;
          sessionStorage.id = response.data.id;
          sessionStorage.username = response.data.username;
        }

        // this.$router.go(-1);
        // 在登录成功后返回前一个页面
        window.history.back();

      }).catch(error => {
        console.log(error)            // error可以是本地错误信息,也可以是服务端的错误信息
        console.log(error.response)   // 接收来自服务端的响应错误,如果服务端没有错误,则没有response属性
        // 登录错误提示
        this.$alert(error.response.data.msg, '警告');
      })
    },

4、发送请求时加上jwt认证头

  • 我在前端用的是axios发送请求,每次发送请求对请求做一个拦截,加入请求头和token
  • jwt认证要加入Bearer头,格式一般是Bearer[空格]token,后端根据空格切割拿到token(在后面自定义认证类会说)
// aip.js

const httptool = axios.create({
    baseURL: 'http://api.forum.com:8000',   // 服务器后端地址
    timeout: 1000,
})

//请求拦截器
httptool.interceptors.request.use(config => {
    let token = window.localStorage.getItem('token') || window.sessionStorage.getItem('token')
    if (token) {
        config.headers['Authorization'] = 'Bearer ' + token;
    }
    return config;
}, error => {
    console.log(error);
    return Promise.reject(error)
})
  • 此时只要用httptool发送请求就会拦截加入认证头,现在假如有个获取某用户创建的所有帖子的请求
methods: {
	/*获取发布的帖子*/
	getMyPublish() {
      httptool.get(`news/user/${this.userId}/`).then(resp => {
               if (resp) {
                   this.articleList = resp.data.news_list
               }
           })
       },
}	

5、自定义认证类

  • 前端发送获取某用户创建的所有帖子的请求,请求头中包含token,现在就要对token进行获取并认证。首先要定义一个认证类

  • 在你的app下创建authentication.py

# authentication.py
import jwt
from rest_framework import exceptions
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework_jwt.settings import api_settings
from forum.models import User

jwt_decode_handler = api_settings.JWT_DECODE_HANDLER


class JsonAuthentication(JSONWebTokenAuthentication):

    def authenticate_credentials(self, payload):
        username = payload['username']
        if not username:
            msg = 'Invalid payload.'
            raise exceptions.AuthenticationFailed(msg)

        try:
            user = User.objects.get(username=username)
        except Exception:
            msg = 'Invalid signature.'
            raise exceptions.AuthenticationFailed(msg)

        if not user.is_active:
            msg = 'User account is disabled.'
            raise exceptions.AuthenticationFailed(msg)
        return user

    def authenticate(self, request):
        
        # 若前端无token则获取切割后的列表索引1会报索引错误,则用try包围
        try:
            jwt_value = request.META.get('HTTP_AUTHORIZATION', '').split(' ')[1]
        except IndexError:
            jwt_value = None
        print(jwt_value)
        # # 验证签名,验证是否过期
        try:
            payload = jwt_decode_handler(jwt_value)  # 得到载荷
            # 取当前用户,拿到user对象,每登录一个人就要去数据库查一次
            # 这个也要重写,因为它找的是auth的user表,我们是去自己表中查
            user = self.authenticate_credentials(payload)
            # 效率更高一写,不需要查数据库了
            # user=LbUserInfo(id=payload['user_id'],username=payload['username']) # user={'id':payload['user_id'],'username':payload['username']}
        except jwt.ExpiredSignature:
            msg = 'token过期'
            raise exceptions.AuthenticationFailed(msg)
        except jwt.DecodeError:
            msg = '签名错误'
            raise exceptions.AuthenticationFailed(msg)
        except jwt.InvalidTokenError:
            raise exceptions.AuthenticationFailed('错误')

        return (user, jwt_value)

当进入认证类时,authenticate方法获取token,拿到载荷,若token过期或签名错误则报错,再调用authenticate_credentials方法检索数据库是否有该用户,返回用户和token

  • 自定义完认证类,再回到刚才的获取用户发布的帖子请求
# urls.py
path("news/user/<int:userId>/", views.NewsByUser.as_view({'get': 'get_by_userId'})),
# views.py
from rest_framework.decorators import authentication_classes, permission_classes
from 你的app.authentication import JsonAuthentication
from rest_framework.permissions import IsAuthenticatedOrReadOnly

class NewsByUser(ViewSet):
    '''需要权限的帖子视图'''
    authentication_classes = [JsonAuthentication]
    permission_classes = [IsAuthenticatedOrReadOnly] # 按需使用
       
    def get_by_userId(self, request, userId):
    '''查询用户id创建的帖子'''
    	# 业务代码,获取请求数据返回给前端
    	news = xx
    	return 
  • JsonAuthentication是刚才自定义的认证类,在进入NewsByUser方法前,会先进行认证,进入authentication_classes列表中的认证类进行认证,成功后再进入permission_classes中的权限类判断是否有权限。认证和权限都通过后才执行业务方法
  • IsAuthenticatedOrReadOnly是DRF自带的权限类:游客只读
  • 如果需要像登录时那样不进行认证和权限判断,则使之为空,否则默认执行settings.py中设置的默认权限类
authentication_classes = []
permission_classes = []

6、报错401时前端响应,跳转登录界面

// api.js
httptool.interceptors.response.use(
    response => {
        // 对响应数据做处理
        return response;
    },
    error => {
        if (error.response && error.response.status === 401) {
            Message({
                type: 'warning',
                message: '尚未登录,请登录!',
                offset: 54
            })
            router.push('/login');
        } else {
            return Promise.reject(error);
        }
    }
);
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值