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

    一、JWT认证:

    1、本质:

    定义:在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制。

    本质: 签发和校验

    签发:根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token

    """
    1)jwt分三段式:头.体.签名 (head.payload.sgin)
    2)头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
    3)头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
    4)头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
    {
        "company": "公司信息",
        ...
    }
    5)体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
    {
        "user_id": 1,
        ...
    }
    6)签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
    {
        "head": "头的加密字符串",
        "payload": "体的加密字符串",
        "secret_key": "安全码"
    }
    """

    校验:根据客户端带token的请求 反解出 user 对象

    """
    1)用基本信息存储json字典,采用base64算法加密得到 头字符串
    2)用关键信息存储json字典,采用base64算法加密得到 体字符串
    3)用头、体加密字符串再加安全码信息存储json字典,采用hash md5算法加密得到 签名字符串
    
    账号密码就能根据User表得到user对象,形成的三段字符串用 . 拼接成token返回给前台
    """

    2:构成(heander、payload、signature):

    2.1、头部header:

    jwt的头部注意放两部分信息:
    {
        'typ':'JWT',  #声明类型,这里是jwt
        'alg':'HS256' #声明加密算法,通常直接使用HMAC SHA256
    #最后对头部进行base64加密,构成第一部分。

    2.2、载荷payload

    payload主要包含三个部分信息
    {
        "sub":"112334",  #标准中注册的声明
         "name":"NQ",     #公共的声明
         "admin":true,    #私有的声明

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

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

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

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

    2.3、签名signature:

    主要由加密后的header、payload、secret三部分组成

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

    注意:ecret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,它本质上来说就是你服务端的私钥,不能泄露。

              header、payload的加密是可以逆的,签名是不可逆的

     二、drf-jwt的简单使用

    1、安装方法:pip install djangorestframework-jwt

    2、使用方法:

    1、创建一个超级用户
    python3 manage.py createsuperuser
    2、配置路由
    from django.conf.urls import url
    from rest_framework_jwt.views import RefreshJSONWebToken,VerifyJSONWebToken,ObtainJSONWebToken
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^login/',ObtainJSONWebToken.as_view()),
    ]
    3、在postman进行测试,可以看到生成一个token
    4、在setting.py中配置认证使用jwt提供的jsonwebtoken
    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework_jwt.authentication.JSONWebTokenAuthentication', # 全局jwt
        ),
    }
    5、postman发起请求必须有tokend的数据,前面要加上jwt+空格

    三、jwt高级用法:

    1、自定义返回数据格式:

    # 控制登录接口返回的数据格式
        -第一种方案,自己写登录接口
        -第二种写法,用内置,控制登录接口返回的数据格式
            -jwt的配置信息中有这个属性
                'JWT_RESPONSE_PAYLOAD_HANDLER':'rest_framework_jwt.utils.jwt_response_payload_handler',
            -重写jwt_response_payload_handler,配置信息中配置成自己的
    #配置文件中配置
    JWT_AUTH={
        'JWT_RESPONSE_PAYLOAD_HANDLER':'app01.utils.new_jwt_response_payload_handler',
    }

    2、自定义基于jwt的权限类

    from rest_framework_jwt.utils import jwt_response_payload_handler,jwt_decode_handler
    from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
    from rest_framework.exceptions import AuthenticationFailed
    import jwt
    from app.views import models
    
    class New_JWT_Authentication(BaseJSONWebTokenAuthentication):
        def authenticate(self, request):
            jwt_value = self.request.META.get('HTTP_AUTHORIZATION')
            if jwt_value is None:
                raise AuthenticationFailed('您没有携带认证信息')
            try:
                payload = jwt_decode_handler(jwt_value)
            except jwt.ExpiredSignature:
                msg = ('签名过期.')
    
                raise AuthenticationFailed(msg)
            except jwt.DecodeError:
                msg = ('签名解码出错')
                raise AuthenticationFailed(msg)
            except jwt.InvalidTokenError:
                raise AuthenticationFailed('非法用户')
            except Exception as e:
                raise AuthenticationFailed(str(e))
            '''
            payload = {
            'user_id': user.pk,
            'username': username,
            'exp': datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA
            }
            '''
            user = self.authenticate_credentials(payload)
            #第二种方法:需要查库
            # user=models.E_User.objects.filter(pk=payload.get('user_id')).first()
            #第三种方法:不需要查库
            # user=models.E_User(pk=payload.get('user_id'),username=payload.get('username'))
    
            return (user, jwt_value)

    3、jwt配置过期时间

    import datetime
    JWT_AUTH={
        'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 过期时间为7天,手动配置
    }

     4、手动签发token(多方式登录)

    (1)序列化多方登录

    #序列化app_ser.py
    from
    rest_framework import serializers from rest_framework.exceptions import ValidationError from rest_framework_jwt.utils import jwt_payload_handler,jwt_encode_handler from app import models import re class Loginser(serializers.ModelSerializer): username = serializers.CharField() # 重新覆盖username字段,数据中它是unique,post,认为你保存数据,自己有校验没过 class Meta: model = models.E_User fields = ['username', 'password'] def validate(self, attrs): username = attrs.get('username') password = attrs.get('password') if re.match('^1[3-9][0-9]{9}$',username): userobj=models.E_User.objects.filter(mobile=username).first() elif re.match('^.*@.*$',username): userobj=models.E_User.objects.filter(email=username).first() else: userobj=models.E_User.objects.filter(username=username).first() if userobj: if userobj.check_password(password): payload=jwt_payload_handler(userobj) token=jwt_encode_handler(payload) attrs['token']=token return attrs else: raise ValidationError('密码错误') else: raise ValidationError('用户不存在')
    #views.py
    class LoginView(ViewSet):
        def login(self,request):
            login_user=app_ser.Loginser(data=request.data)
            login_user.is_valid(raise_exception=True)
            token=login_user.data.get('token')
            username=login_user.data.get('username')
            return Response({
                'statu': 100,
                'msg':'登陆成功',
                'token':token,
                'username':username
            })

    (2)视图类写多方登录

    #序列化类
    class Loginser1(serializers.ModelSerializer):
        username=serializers.CharField()
        class Meta:
            model=models.E_User
            fields=['username','password']
    #视图类
    class Login2View(ViewSet):
        def login(self,request):
            username=request.data.get('username')
            password=request.data.get('password')
    
            if re.match('^1[3-9][0-9]{9}$', username):
    
                userobj = models.E_User.objects.filter(mobile=username).first()
            elif re.match('^.*@.*$', username):
                userobj = models.E_User.objects.filter(email=username).first()
            else:
                userobj = models.E_User.objects.filter(username=username).first()
            if userobj:
                print(userobj)
                if userobj.check_password:
                    payload=jwt_payload_handler(userobj)
                    print('payload',payload)
                    token=jwt_encode_handler(payload)
                    print('token',token)
                    loginser=app_ser.Loginser1(data=request.data)
                    loginser.is_valid(raise_exception=True)
                    #将token添加到validated_data中
                    loginser.validated_data['token']=token
                    #不让密码显示出来
                    loginser.validated_data.pop('password')
                    return Response(loginser.validated_data)
                else:
                    return Response('密码错误')
            else:
                return Response('用户名不存在')
  • 相关阅读:
    MySQL密码复杂度与密码过期策略介绍
    mysql 5.7安装密码校验插件validate_password
    MySQL安装控制插件(Connection-Control)--可有效的防止客户端暴力登录的风险(攻击)
    Liunx 无法杀掉reids进程解决方法
    Linux安装Kafka
    ZooKeeper安装及简单操作
    关于数组的算法题(一)
    集合框架方法(用于算法)
    Spring简答题(一)
    java选择题(三)
  • 原文地址:https://www.cnblogs.com/nq31/p/13963708.html
Copyright © 2011-2022 走看看