JWT 快速入门,并实现登录认证

  • Post author:
  • Post category:其他




一、JWT 简介

jwt(JSON Web Tokens),是一种开发的行业标准

RFC 7519

,用于安全的表示双方之间的声明。目前,jwt广泛应用在系统的用户认证方面,特别是现在前后端分离项目。



1.1 Jwt认证流程

  1. 前端用户填写好用户名和密码,点击登录
  2. 后端对提交的用户名和密码进行校验,校验通过则发送token给前端
  3. 前端将token保存在cookie中,并在每一次请求时都将cookie一并发送给后端
  4. 后端对用户发送过来的token进行校验,并通过token识别是哪个用户。



1.2 session & Token 区别


  • session

    在django中,如果使用session进行认证,会在django_session表中存储用户登录记录,随着用户增加,数据库中的记录也会越来越多,增加了服务器压力


  • token

    传统token

    ​ 用户登录成功后,服务端生成一个随机token给用户,并且在服务端(数据库或缓存)中保存一份token,以后用户再来访问时需携带

    token

    ,服务端接收到token之后,去数据库或缓存中进行校验token的是否超时、是否合法。

    jwt 形式

    ​ 用户登录成功后,服务端通过jwt生成一个随机token给用户(服务端无需保留token),以后用户再来访问时需携带token,服务端 接收到token之后,通过jwt对token进行校验是否超时、是否合法。



1.3 JWT 格式


jwt

是一段由

.(点)

组成的三段式密文

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
  • 第一段称为头部(header)

    头部存储了两部分信息,分别是类别和加密算法。加密算法通常使用sha256(这里指整体加密时采用的算法),将头部进行base64url编码得到一段内容

    # 密文
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    
    # 解密
    {
      "alg": "HS256",
      "typ": "JWT"
    }
    
  • 第二段称为payload(载荷)

    payload 里面包含用户有部分数据,比如用户id和用户名等。第二段内容也是通过base64url进行加密,所以内容中不能包含敏感数据

    # 密文
    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
    
    # 解密
    {
      "sub": "1234567890",
      "name": "John Doe",
      "iat": 1516239022
    }
    
  • 第三段为签名(signature)

    把前两段的

    base64url

    密文通过

    .

    拼接起来,并加入秘钥,然后对其(两段密文和盐)进行

    HS256

    加密(header中定义的类别),再然后对整体密文进行

    base64url

    加密,最终得到token的第三段。

    base64url(
        HMACSHA256(
          base64UrlEncode(header) + "." + base64UrlEncode(payload),
          your-256-bit-secret (秘钥加盐)
        )
    )
    



二、PyJWT 使用



2.1 简单实用


  • 安装

    pip install pyjwt
    

在django系统中使用pyjwt来实现jwt认证。

>>> import jwt
>>> key = "secret"
>>> encoded = jwt.encode({"some": "payload"}, key, algorithm="HS256")
>>> print(encoded)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg
>>> jwt.decode(encoded, key, algorithms="HS256")
{'some': 'payload'}

  • payload中的特殊参数
The JWT specification defines some registered claim names and defines how they should be used. PyJWT supports these registered claim names:

 “exp” (Expiration Time) Claim	
 “nbf” (Not Before Time) Claim
 “iss” (Issuer) Claim
 “aud” (Audience) Claim
 “iat” (Issued At) Claim

  • exp



    指定过期时间

    payload = {
        "id":result.id,
        "username":result.username,
        'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=1)  # 超时时间,其中exp是固定写法
    }
    

    在进行decode时,会对该值进行校验token是否过期



2.2 JWT实现用户token认证案例

以django为例


路由接口

urlpatterns = [
    path('login',views.LoginView.as_view()),	# 生成token,并返回给用户
    path('order',views.OrderView.as_view())		# 验证token
]	


视图书写

# 登录视图类
class LoginView(APIView):
    def post(self,request):
        username = request.POST.get('username')
        password = request.POST.get('password')

        user_obj = models.UserInfo.objects.filter(username=username,password=password).first()
        if not user_obj:
            return JsonResponse({"code":"201","msg":"用户名或密码错误"})
        payload = {"uid":user_obj.id,"username":user_obj.username}
        token = jwt_auth.creata_token(payload)
        return JsonResponse({"code":"200","msg":"post successful","token":token,})

# 订单类
class OrderView(APIView):
    def get(self,request):
        token = request.query_params.get("token")
        result = jwt_auth.verify_token(token)
        return JsonResponse(result)


token创建&认证函数

# utils/jwt_auth
import jwt
import datetime
from django.conf import settings

def creata_token(payload,):
    """
    生成token
    :param payload: 用于生成token的部分用户信息
    :return: 生成的token
    """
    # 1.构造headers
    headers = {
        'typ': 'jwt',
        'alg': 'HS256'
    }
    # 2.构造payload
    payload['exp'] = datetime.datetime.utcnow() + datetime.timedelta(minutes=1) # 指定过期时间为1分钟

    # 3.生成token并返回
    token = jwt.encode(payload=payload, key=settings.SECRET_KEY, algorithm="HS256")
    return token

def verify_token(token):
    """
    验证token的有效性
    :param token:
    :return:
    """
    result = {"code": "202"}
    try:
        # true 表示集成了对时间等校验
        verified_payload = jwt.decode(token, settings.SECRET_KEY, algorithms="HS256")
        print(verified_payload)
        result['msg'] = verified_payload
        result['code'] = 200
    except jwt.exceptions.ExpiredSignatureError:
        result['error'] = "身份信息已失效,请重新登录"
    except jwt.DecodeError:
        result['error'] = "Token认证失败"
    except jwt.InvalidTokenError:
        result['error'] = "非法token"
    return result


实验测试结果

  • 生成token

​     [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7VOtk5wm-1646886501342)(Z:\Study Note\Typora笔记图库\image-20211204202403800.png)]

  • 有效期token结果

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dtRVqMw3-1646886501343)(Z:\Study Note\Typora笔记图库\image-20211204202447317.png)]

  • 过期token

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Hcq3Wld-1646886501343)(Z:\Study Note\Typora笔记图库\image-20211204202618484.png)]



三、利用DRF设置用户认证


自定义认证类

# utils/jwt_auth.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed

class JwtGlobalAuth(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get("token")
        result = {"code": "202"}
        try:
            # true 表示集成了对时间等校验
            verified_payload = jwt.decode(token, settings.SECRET_KEY, algorithms="HS256")
            result['msg'] = verified_payload
            result['code'] = 200

        except jwt.exceptions.ExpiredSignatureError:
            result['error'] = "身份信息已失效,请重新登录"
            raise AuthenticationFailed(result)
        except jwt.DecodeError:
            result['error'] = "Token认证失败"
            raise AuthenticationFailed(result)
        except jwt.InvalidTokenError:
            result['error'] = "非法token"
            raise AuthenticationFailed(result)

        # drf 认证类可以返回三种类型值
        # 1.抛出异常
        # 2.返回一个元组
        return (verified_payload,token)
        # 3.返回None


视图中加载用户认证 – 局部认证

# api/views.py
class OrderView(APIView):
    authentication_classes = [jwt_auth.JwtGlobalAuth,]
    def get(self,request):
        ret = "这里是订单详情"
        return JsonResponse(ret,safe=False)


全局token认证

# settings.py
import api
REST_FRAMEWORK = {	
    "DEFAULT_AUTHENTICATION_CLASSES":['api.utils.jwt_auth.JwtGlobalAuth']	 # 默认认证类,全局有效
}
  • 视图类

    # api/views.py
    
    class LoginView(APIView):
        authentication_classes = []		# 解除登录时的token认证
        def post(self,request):
            username = request.POST.get('username')
            password = request.POST.get('password')
    
            user_obj = models.UserInfo.objects.filter(username=username,password=password).first()
            if not user_obj:
                return JsonResponse({"code":"201","msg":"用户名或密码错误"})
            payload = {"uid":user_obj.id,"username":user_obj.username}
            token = jwt_auth.creata_token(payload)
            return JsonResponse({"code":"200","msg":"post successful","token":token,})
    
    class OrderView(APIView):
        def get(self,request):
            ret = "这里是订单详情"
            return JsonResponse(ret,safe=False)
    



版权声明:本文为m0_56966142原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。