用户登录认证(jwt)

有些api需要用户登录之后才能访问,有些不需要登录也能访问。jwt采用加盐非对称加解密,一层套一层,服务器无需存储即可判断此次携带token是否和起始token一致,比老一套的基于token认证或cookie+session认证好太多,还能防止csrf攻击,这里有一篇写得很好的文章。为了完成用户认证,首先你必须要来一张UserInfo表,只需用户名和密码字段,迁移生成表。然后手动加条记录进去模拟注册成功后的用户。

  1. class UserInfo(models.Model):
  2. username = models.CharField(max_length=10,verbose_name='姓名')
  3. password = models.CharField(max_length=10,verbose_name='密码')

1、定义jwt的两个方法用于生成加密token和判断token是否合法:

  1. import jwt
  2. import datetime
  3. from jwt import exceptions
  4. SALT = 'iv%x6xo7l7_u9bf_u!9#g#m*)*=ej@bek5)(@u3kh*72+unjv='
  5. def create_token(user_id,username):
  6. """生成token"""
  7. headers = {
  8. 'typ': 'jwt',
  9. 'alg': 'HS256'
  10. }
  11. # 构造payload
  12. payload = {
  13. 'user_id': user_id, # 自定义用户ID
  14. 'username': username, # 自定义用户名
  15. 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30) # 超时时间半小时
  16. }
  17. token = jwt.encode(payload=payload, key=SALT, algorithm="HS256", headers=headers).decode('utf-8')
  18. return token
  19. def get_payload(token):
  20. """token校验"""
  21. result = {'status': False, 'data': None, 'error': None}
  22. try:
  23. verified_payload = jwt.decode(token, SALT, True)
  24. result['status'] = True
  25. result['data'] = verified_payload
  26. except exceptions.ExpiredSignatureError:
  27. result['error'] = 'token已失效'
  28. except jwt.DecodeError:
  29. result['error'] = 'token认证失败'
  30. except jwt.InvalidTokenError:
  31. result['error'] =

看看drf内置的认证类,一共有五个可以被继承,我们选择继承BasicAuthentication来实现用户认证:
image.png

2、定义用户登录认证类继承BasicAuthentication:

  1. from rest_framework import exceptions
  2. from rest_framework.authentication import BaseAuthentication
  3. from api.models import UserInfo
  4. from api.utils.jwt_createa_and_verified import get_payload
  5. class Authtication(BaseAuthentication):
  6. """用户认证类,继承BaseAuthentication重写authenticate方法来进行认证"""
  7. def authenticate(self,request):
  8. token = request.query_params.get('token')
  9. result= get_payload(token)
  10. # 认证失败
  11. if not result['status']:
  12. raise exceptions.AuthenticationFailed(result)
  13. # 如果想要request.user等于用户对象,此处可以根据payload去数据库中获取用户对象。
  14. user = UserInfo.objects.filter(id=result.get('data').get('user_id')).first()
  15. request.user = user
  16. # 一个给request.user,一个给request.auth
  17. return (request.user,token)

3、下面是把刚刚这个登录认证类添加到全局

即所有视图的接口都必须通过登录认证才能访问:(rest框架的全局配置都放在REST_FRAMEWORK 这个字典里,你可以在不需要认证的视图中用authentication_classes = []的方式来覆盖掉这个全局配置,表示该视图不进行用户登录认证。)

  1. REST_FRAMEWORK = {
  2. # 认证
  3. 'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.auth.Authtication', ],
  4. }

4、编写登录视图,用于成功登录后返回token给前端:

(登录接口肯定是不能有登录认证的)

  1. class AuthView(APIView):
  2. """我的认证类是添加到全局代表所有接口必须登录才能访问,根据继承的搜索顺序,我可以把这个置为空,代表该登录接口不进行用户认证"""
  3. authentication_classes = []
  4. def post(self,request,*args,**kwargs):
  5. user = request.data.get('username')
  6. pwd = request.data.get('password')
  7. try:
  8. user_obj = UserInfo.objects.filter(username=user,password=pwd).first()
  9. if not user_obj:
  10. return JsonResponse({'code':1002,'msg':'用户名或密码错误'})
  11. # 登陆成功我就给你jwt形式的token,下次访问你需要带上token才能访问要求登录后才能访问的接口
  12. token = create_token(user_obj.id,user_obj.username)
  13. # 用户登录成功,返回这个用户的token
  14. return JsonResponse({'code':1000,'msg':'登录成功','token':token})
  15. except:
  16. 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(‘用户认证失败’)