zoukankan      html  css  js  c++  java
  • JWT认识

    Python JWT

    session认证

    我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,并不能确定是哪个用户发出的请求,所以为了能识别是哪个用户发出的请求,只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给服务器,这样我们服务器就能识别请求来自哪个用户了,这就是传统的基于session认证。

    但是这种基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来.

    session认证问题

    Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。

    扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。

    CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

    token鉴权机制

    基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。

    流程上是这样的:

    • 用户使用用户名密码来请求服务器
    • 服务器进行验证用户的信息
    • 服务器通过验证发送给用户一个token
    • 客户端存储token,并在每次请求时附送上这个token值
    • 服务端验证token值,并返回数据

    这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端端要做跨域支持。

    JWT的构成

    第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

    jwt的头部承载两部分信息:

    • 声明类型,这里是jwt
    • 声明加密的算法 通常直接使用 HMAC SHA256

    完整的头部就像下面这样的JSON:

    {
      'typ': 'JWT',
      'alg': 'HS256'
    }
    

    然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
    

    playload

    载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

    • 标准中注册的声明
    • 公共的声明
    • 私有的声明

    标准中注册的声明 (建议但不强制使用) :

    • iss: jwt签发者
    • sub: jwt所面向的用户
    • aud: 接收jwt的一方
    • exp: jwt的过期时间,这个过期时间必须要大于签发时间
    • nbf: 定义在什么时间之前,该jwt都是不可用的.
    • iat: jwt的签发时间
    • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

    公共的声明
    公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

    私有的声明
    私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

    定义一个payload:

    {
      "phone": "13331153360",
      "name": "admin",
    }
    

    然后将其进行base64加密,得到Jwt的第二部分。

    dy63zcIiOiIxMjMdTY3ODkwIiwiaFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
    

    signature

    jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

    • header (base64后的)
    • payload (base64后的)
    • secret

    这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

    // javascript
    var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
    
    var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    

    将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

      eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    

    Python JWT实现

    python 本身有携带JWT模块,我们可以采用jwt模块自己进行基于django的JWT登陆:

    在这里插入图片描述

    抛开登陆和数据部分,这两个部分是视图部分,我们自己需要编写的以下功能:

    用户名密码校验

    token生成

    token校验

    搭建自己的用户表

    class PUser(models.Model):
        username = models.CharField(max_length = 32)
        password = models.CharField(max_length = 32)
    
        user_group = models.ManyToManyField(UserGroup)
    

    用户名密码校验

    def check_user(username,password):
        """
        校验用户是否存在
        :param username: 用户名
        :param password: 密码
        :return:
        """
        result = {
            "state": 0,
            "message": "",
            "bool": False,
            "data": {
    
            }
        }
        user = PUser.objects.filter(username = username,password = password)
        if user:
            result["state"] = 1
            result["message"] = "当前用户存在!"
            result["bool"] = True
            result["data"].update(
                {
                    "username": user.username,
                    "user_id": user.id
                }
            )
        else:
            result["state"] = 0
            result["message"] = "当前用户不存在!"
            result["bool"] = False
        return result
    

    原生JWT_Token

    按照jwt模块

    (d_37) C:Usersdell>pip install jwt
    Collecting jwt
      Downloading jwt-1.1.0-py3-none-any.whl (14 kB)
    Requirement already satisfied: cryptography<=3.2.1,>2.9.2 in d:anaconda3envsd_37libsite-packages (from jwt) (3.2.1)
    Requirement already satisfied: cffi!=1.11.3,>=1.8 in d:anaconda3envsd_37libsite-packages (from cryptography<=3.2.1,>2.9.2->jwt) (1.14.3)
    Requirement already satisfied: six>=1.4.1 in d:anaconda3envsd_37libsite-packages (from cryptography<=3.2.1,>2.9.2->jwt) (1.15.0)
    Requirement already satisfied: pycparser in d:anaconda3envsd_37libsite-packages (from cffi!=1.11.3,>=1.8->cryptography<=3.2.1,>2.9.2->jwt) (2.20)
    Installing collected packages: jwt
    Successfully installed jwt-1.1.0
    
    (d_37) C:Usersdell>
    

    jwt的生成token格式如下,即:由点(.)连接的三段字符组成

    HEADER部分

    第一段HEADER部分,固定包含算法和token类型,对此json进行base64url加密,这就是token的第一段

    {
        "alg": "HS256",
        "typ": "JWT"
    }
    

    使用python的内置模块进行简单的实现

    import json
    import base64
    
    
    header = {
        "alg": "HS256",
        "typ": "JWT",
    }
    
    json_header = json.dumps(header).encode()
    string_headers = base64.b64encode(json_header)
    
    print(string_headers)
    print(base64.b64decode(string_headers)) #这里是解码
    

    PAYLOAD部分

    第二段PAYLOAD部分,包含一些数据,对此json进行base64url加密,这就是token的第二段,这个部分是可以被解密出来的,所以建议大家放数据,但是不建议大家放敏感数据,比如密码。当然除了校验之外,我们也可以使用这里的数据进行数据查询或者处理

    依然,我们采用使用python的内置模块进行简单的实现

    import json
    import base64
    
    payload = {
        "username": "admin",
        "userid": 1,
    }
    
    json_payload = json.dumps(payload).encode()
    string_payload = base64.b64encode(json_payload)
    
    print(string_payload)
    print(base64.b64decode(string_payload)) #这里是解码
    

    SIGNATURE部分

    第三段SIGNATURE部分,把前两段的base秘闻通过.拼接起来,然后对其进行HS256加密,再然后对hs256秘闻进行base64url加密,最终得到token的第三段。

    注意整个流程当中,需要在hash256加密的过程当中添加盐值,来保证加密不会被很轻易的暴力破解出来。

    import hashlib
    
    that_string = string_headers+b"."+string_payload
    hash256 = hashlib.sha256(b"helloworld")
    hash256.update(that_string)
    hash_string = hash256.hexdigest()
    
    string_signature = base64.b64encode(hash_string.encode())
    print(string_signature)
    

    然后按照格式,使用.进行拼接,代码如下:

    import json
    import base64
    import hashlib
    
    header = {
        "alg": "HS256",
        "typ": "JWT",
    }
    
    json_header = json.dumps(header).encode()
    string_headers = base64.b64encode(json_header)
    
    print(string_headers)
    
    payload = {
        "username": "admin",
        "userid": 1,
    }
    
    json_payload = json.dumps(payload).encode()
    string_payload = base64.b64encode(json_payload)
    
    print(string_payload)
    
    that_string = string_headers+b"."+string_payload
    hash256 = hashlib.sha256(b"helloworld")
    hash256.update(that_string)
    hash_string = hash256.hexdigest()
    
    string_signature = base64.b64encode(hash_string.encode())
    print(string_signature)
    
    
    print(string_headers+b"."+string_payload+b"."+hash_string.encode())
    

    然后编写解析token的代码

    import json
    import base64
    import hashlib
    
    header,payload,signature = result.split(b".")
    header_json = base64.b64decode(header) #这里是解码
    payload_json = base64.b64decode(payload) #这里是解码
    print(json.loads(header_json))
    print(json.loads(payload_json))
    print(signature)
    

    sha256加密是没有办法进行逆向解密的,所以signature没有办法解密查看内容,可以对获取的前两段再次进行加密新成新的第三段然后和当前的第三段进行比对来判断这个第三段是否正常,这里有个关键的点就是盐值,所以还是要强调盐值问题,如果盐值泄露,用户就可以很轻松的模仿出token来了。

    上面可以实现jwt的生成和校验功能的简单功能了,但是有点复杂,并且很难保证代码的一致性(当然通过代码封装可以解决这一点),python也提供了jwt模块:

    Python Jwt模块

    安装

    (D_37) C:Usersdell>pip install pyjwt
    

    使用

    有用户签名部分:

    import jwt
    import datetime
    
    dic = {
        'exp': datetime.datetime.now() + datetime.timedelta(days=1),  # 过期时间
        'iat': datetime.datetime.now(),  #  开始时间
        'iss': 'lianzong',  # 签名
        'data': {  # 内容,一般存放该用户id和开始时间
            'a': 1,
            'b': 2,
        },
    }
    
    s = jwt.encode(dic, 'secret', algorithm='HS256')  # 加密生成字符串
    print(s)
    s = jwt.decode(s, 'secret', issuer='lianzong', algorithms=['HS256'])  # 解密,校验签名
    print(s)
    print(type(s))
    

    restframework-jwt

    restframework-jwt是基于django 的restframework的登陆验证部分,可以快速的实现登陆和校验部分,当然也提供了开发者自定义的方法

    基本配置

    安装

    pip install djangorestframework-jwt
    

    settings配置

    ....
    
    # Application definition
    
    INSTALLED_APPS = [
        ....
        'rest_framework_jwt'
    ]
    
    ......
    #全局配置
    REST_FRAMEWORK = {
        #permission_classes
        "DEFAULT_PERMISSION_CLASSES": (
            "rest_framework.permissions.IsAuthenticated",
        ),
        "DEFAULT_AUTHENTICATION_CLASSES":(
            "rest_framework_jwt.authentication.JSONWebTokenAuthentication",
        )  # 全局设置的方法,也可在单个视图中设置
    }
    

    其他配置

    import datetime
    
    JWT_AUTH = {
        # token过期时间
        'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
        # 允许刷新token
        'JWT_ALLOW_REFRESH': True,
        # 刷新的过期时间
        'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
    	#这个很重要,是设置token开头的一种
        'JWT_AUTH_HEADER_PREFIX': 'JWT'
    }
    

    局部使用

    from rest_framework.permissions import IsAuthenticated
    from rest_framework_jwt.authentication import JSONWebTokenAuthentication
    
    
    class CompanyModelViewSet(ModelViewSet):
        authentication_classes = [JSONWebTokenAuthentication]
        queryset = Company.objects.all()
        serializer_class = CompanySerializers
        permission_classes = [OurPermission,IsAuthenticated]
    

    访问测试

    首先提交用户名和密码
    在这里插入图片描述

    这里要注意的是restframewrok_jwt默认采用系统的库作为用户识别的库,所以需要提前使用createsuper来注册用户,然后进行测试。

    发送数据的时候,需要把数据携带到请求的头部,这里携带请求的字段叫做: Authorization,如果你采用的是postman的话,可以补全出来的。

    然后是注意发送token的格式,是需要遵循在settings当中的JWT_AUTH_HEADER_PREFIX配置的格式的,也就是说需要是

    JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjA5MjEwNjQ0LCJlbWFpbCI6ImFkbWluQHFxLmNvbSJ9.5OadYhBVuAQg-vveQA7o-SyF25HjeosOW_rIFGyfesY
    

    这个格式。

    postman 进行如下设置
    在这里插入图片描述

    axios 案例

    <script>
        vue = new Vue({
            el: "#container",
            data: {
                per_data: []
            },
            created: function(){
                axios.defaults.headers.common['Authorization'] = "JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjA5Mjk0NTczLCJlbWFpbCI6ImFkbWluQHFxLmNvbSJ9.6sBQexTiv54wGezNGOYevMbr50tMJM3qQ8p8w6aGz30";
                axios.get("http://127.0.0.1/Permission/").then(
                    function(data){
                        vue.per_data = data["data"]
                        console.log(data)
                    },
                    function(error){
                        console.log(error)
                    }
                )
            }
        })
    </script>
    

    自定义开发

    rest_framework本身会加载自己的这个配置文件:

    $python_path/lib/site-package/restframework-jwt/settings.py

    import datetime
    
    from django.conf import settings
    from rest_framework.settings import APISettings
    
    
    USER_SETTINGS = getattr(settings, 'JWT_AUTH', None)
    
    DEFAULTS = {
        'JWT_ENCODE_HANDLER':
        'rest_framework_jwt.utils.jwt_encode_handler',
    
        'JWT_DECODE_HANDLER':
        'rest_framework_jwt.utils.jwt_decode_handler',
    
        'JWT_PAYLOAD_HANDLER':
        'rest_framework_jwt.utils.jwt_payload_handler',
    
        'JWT_PAYLOAD_GET_USER_ID_HANDLER':
        'rest_framework_jwt.utils.jwt_get_user_id_from_payload_handler',
    
        'JWT_PRIVATE_KEY':
        None,
    
        'JWT_PUBLIC_KEY':
        None,
    
        'JWT_PAYLOAD_GET_USERNAME_HANDLER':
        'rest_framework_jwt.utils.jwt_get_username_from_payload_handler',
    
        'JWT_RESPONSE_PAYLOAD_HANDLER':
        'rest_framework_jwt.utils.jwt_response_payload_handler',
    
        'JWT_SECRET_KEY': settings.SECRET_KEY,
        'JWT_GET_USER_SECRET_KEY': None,
        'JWT_ALGORITHM': 'HS256',
        'JWT_VERIFY': True,
        'JWT_VERIFY_EXPIRATION': True,
        'JWT_LEEWAY': 0,
        'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300),
        'JWT_AUDIENCE': None,
        'JWT_ISSUER': None,
    
        'JWT_ALLOW_REFRESH': False,
        'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
    
        'JWT_AUTH_HEADER_PREFIX': 'JWT',
        'JWT_AUTH_COOKIE': None,
    }
    
    # List of settings that may be in string import notation.
    IMPORT_STRINGS = (
        'JWT_ENCODE_HANDLER',
        'JWT_DECODE_HANDLER',
        'JWT_PAYLOAD_HANDLER',
        'JWT_PAYLOAD_GET_USER_ID_HANDLER',
        'JWT_PAYLOAD_GET_USERNAME_HANDLER',
        'JWT_RESPONSE_PAYLOAD_HANDLER',
        'JWT_GET_USER_SECRET_KEY',
    )
    
    api_settings = APISettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS)
    

    从这个方法里面我们可以抽取出很多有用的方法:

    from django.http import JsonResponse
    def login(request):
        user = PUser.objects.get(id = 1)
        payload = jwt_payload_handler(user) #从用户表当中抽取出数据,然后编译成一个字典结构的数据
        jwt_token = jwt_encode_handler(payload) #生成一个JWT token
        decode_token = jwt_decode_handler(jwt_token) #解码一个JWT token
        print(payload)
        print(jwt_token)
        print(decode_token)
        return JsonResponse(payload)
    

    那么基于上述的方法和Django restful我们完成下面的接口视图

    class RegisterModelViewSet(ViewSet):
        """
        注册接口
            只有保存接口
        """
        authentication_classes = [] #登录部分
        permission_classes = [] #权限部分
    
        def __init__(self, **kwargs):
            super(RegisterModelViewSet,self).__init__(**kwargs)
            self.result = {
                "code": 200,
                "detail": [
    
                ],
                "token": ""
            }
    
        def serializers(self,obj):
            is_one = isinstance(obj,PUser)
            if is_one:
                result = [AuthSerializers(obj).data]
            else:
                result = [AuthSerializers(o).data for o in obj]
            self.result["detail"] = result
    
        def set_password(self,password):
            md5 = hashlib.md5()
            md5.update(password.encode())
            result = md5.hexdigest()
            return result
    
        def send_token(self,user):
            jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
            jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
    
            payload = jwt_payload_handler(user)  # 从用户表当中抽取出数据,然后编译成一个字典结构的数据
            jwt_token = jwt_encode_handler(payload)  # 生成一个JWT token
            return jwt_token
    
        def get(self,request,id = 0):
            if id:
                user = PUser.objects.filter(id = int(id)).first()
            else:
                username = request.GET.get("username")
                if username:
                    user = PUser.objects.filter(username = username).first() #检索数据
                else:
                    user = PUser.objects.all() #返回所有数据
            self.serializers(user)
            return Response(self.result)
    
        def post(self,request):
            username = request.POST.get("username")
            password = request.POST.get("password")
            request_type = request.POST.get("request_type")
            if username and password:
                if request_type == "register":
                    self.regsiter(username,password)
                else:
                    self.login(username,password)
            else:
                self.result["code"] = 404
                self.result["detail"].append("用户名和密码不可以为空!")
            return Response(self.result)
    
    
        def regsiter(self,username,password):
            """
            注册
            """
            user = PUser()
            user.username = username
            user.password = self.set_password(password)
            user.save()
            self.serializers(user)
    
        def login(self,username,password):
            """
            登录
            """
            pwd = self.set_password(password)
            user = PUser.objects.filter(username = username,password = pwd).first()
            if user:
                token = self.send_token(user)
                self.serializers(user)
                self.result["token"] = token
            else:
                self.result["code"] = 500
                self.result["detail"].append("用户和密码有误")
    

    使用如下:
    在这里插入图片描述

  • 相关阅读:
    python中的__init__方法
    hosts文件是做什么的
    自动化测试--环境搭建python+pytest+selenium
    什么是Netty服务器
    Java 基础原理一
    Python MongoDB 基本操作
    Mysql 数据库的查询操作
    Git 笔记二
    Git 使用笔记
    最全正则表达式
  • 原文地址:https://www.cnblogs.com/bianjinhui/p/14182613.html
Copyright © 2011-2022 走看看