用户登录认证(jwt)
有些api需要用户登录之后才能访问,有些不需要登录也能访问。jwt采用加盐非对称加解密,一层套一层,服务器无需存储即可判断此次携带token是否和起始token一致,比老一套的基于token认证或cookie+session认证好太多,还能防止csrf攻击,这里有一篇写得很好的文章。为了完成用户认证,首先你必须要来一张UserInfo表,只需用户名和密码字段,迁移生成表。然后手动加条记录进去模拟注册成功后的用户。
class UserInfo(models.Model):
username = models.CharField(max_length=10,verbose_name='姓名')
password = models.CharField(max_length=10,verbose_name='密码')
1、定义jwt的两个方法用于生成加密token和判断token是否合法:
import jwt
import datetime
from jwt import exceptions
SALT = 'iv%x6xo7l7_u9bf_u!9#g#m*)*=ej@bek5)(@u3kh*72+unjv='
def create_token(user_id,username):
"""生成token"""
headers = {
'typ': 'jwt',
'alg': 'HS256'
}
# 构造payload
payload = {
'user_id': user_id, # 自定义用户ID
'username': username, # 自定义用户名
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30) # 超时时间半小时
}
token = jwt.encode(payload=payload, key=SALT, algorithm="HS256", headers=headers).decode('utf-8')
return token
def get_payload(token):
"""token校验"""
result = {'status': False, 'data': None, 'error': None}
try:
verified_payload = jwt.decode(token, SALT, True)
result['status'] = True
result['data'] = verified_payload
except exceptions.ExpiredSignatureError:
result['error'] = 'token已失效'
except jwt.DecodeError:
result['error'] = 'token认证失败'
except jwt.InvalidTokenError:
result['error'] =
看看drf内置的认证类,一共有五个可以被继承,我们选择继承BasicAuthentication来实现用户认证:
2、定义用户登录认证类继承BasicAuthentication:
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from api.models import UserInfo
from api.utils.jwt_createa_and_verified import get_payload
class Authtication(BaseAuthentication):
"""用户认证类,继承BaseAuthentication重写authenticate方法来进行认证"""
def authenticate(self,request):
token = request.query_params.get('token')
result= get_payload(token)
# 认证失败
if not result['status']:
raise exceptions.AuthenticationFailed(result)
# 如果想要request.user等于用户对象,此处可以根据payload去数据库中获取用户对象。
user = UserInfo.objects.filter(id=result.get('data').get('user_id')).first()
request.user = user
# 一个给request.user,一个给request.auth
return (request.user,token)
3、下面是把刚刚这个登录认证类添加到全局
即所有视图的接口都必须通过登录认证才能访问:(rest框架的全局配置都放在REST_FRAMEWORK 这个字典里,你可以在不需要认证的视图中用authentication_classes = []的方式来覆盖掉这个全局配置,表示该视图不进行用户登录认证。)
REST_FRAMEWORK = {
# 认证
'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.auth.Authtication', ],
}
4、编写登录视图,用于成功登录后返回token给前端:
(登录接口肯定是不能有登录认证的)
class AuthView(APIView):
"""我的认证类是添加到全局代表所有接口必须登录才能访问,根据继承的搜索顺序,我可以把这个置为空,代表该登录接口不进行用户认证"""
authentication_classes = []
def post(self,request,*args,**kwargs):
user = request.data.get('username')
pwd = request.data.get('password')
try:
user_obj = UserInfo.objects.filter(username=user,password=pwd).first()
if not user_obj:
return JsonResponse({'code':1002,'msg':'用户名或密码错误'})
# 登陆成功我就给你jwt形式的token,下次访问你需要带上token才能访问要求登录后才能访问的接口
token = create_token(user_obj.id,user_obj.username)
# 用户登录成功,返回这个用户的token
return JsonResponse({'code':1000,'msg':'登录成功','token':token})
except:
return JsonResponse({'code':1001,'msg':'用户登陆异常'})
以上做完以后我们就完成了登录认证功能,登录认证流程:
增删查改接口肯定是会在各自的类试图里边,请求进来,过中间件,再路由匹配先找到as_view,往前找找到APIView里的as_view,因为是前后端分离(前端不可能知道你的csrf_token),这里边是返回了一个免除csrf验证的view,并且还执行了super().as_view,再往上找到View的as_view,这里边调用了dispatch,再回头从视图类开始找,找到了APIView中的dispatch。drf丰富的功能就是在APIView里的dispatch中完成的,以前View里的dispatch只是做了个反射。
drf的用户认证类的返回值有三种情况
- None,认证通过。
- 返回一个元组,认证通过,第一个是当前用户会自动给request.user,第二个给request.auth,供视图中调用这两个属性。
- 抛异常raise exception.AuthenticationFailed(‘用户认证失败’)