用户登录认证(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 jwtimport datetimefrom jwt import exceptionsSALT = 'iv%x6xo7l7_u9bf_u!9#g#m*)*=ej@bek5)(@u3kh*72+unjv='def create_token(user_id,username):"""生成token"""headers = {'typ': 'jwt','alg': 'HS256'}# 构造payloadpayload = {'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 tokendef get_payload(token):"""token校验"""result = {'status': False, 'data': None, 'error': None}try:verified_payload = jwt.decode(token, SALT, True)result['status'] = Trueresult['data'] = verified_payloadexcept exceptions.ExpiredSignatureError:result['error'] = 'token已失效'except jwt.DecodeError:result['error'] = 'token认证失败'except jwt.InvalidTokenError:result['error'] =
看看drf内置的认证类,一共有五个可以被继承,我们选择继承BasicAuthentication来实现用户认证:
2、定义用户登录认证类继承BasicAuthentication:
from rest_framework import exceptionsfrom rest_framework.authentication import BaseAuthenticationfrom api.models import UserInfofrom api.utils.jwt_createa_and_verified import get_payloadclass 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.authreturn (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)# 用户登录成功,返回这个用户的tokenreturn 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(‘用户认证失败’)
