• 单点登录,sessions,cokkie,JWT


     

    什么是单点登录:

    单点登录SSO(Single Sign On)说得简单点就是在一个多系统共存的环境下,用户在一处登录后,就不用在其他系统中登录,也就是用户的一次登录能得到其他所有系统的信任。

    如何解决单点登录:

    1.session+cookie+redis

    2.设置登录服务器

    3.JWT

    JWT:

    JWT长什么样?

      JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

    JWT的构成

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

      header

    j  wt的头部承载两部分信息:

    • 声明类型,这里是jwt

    • 声明加密的算法 通常直接使用 HMAC SHA256

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

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

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

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

     payload

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

    • 标准中注册的声明

    • 公共的声明

    • 私有的声明

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

    • iss: jwt签发者

    • sub: jwt所面向的用户

    • aud: 接收jwt的一方

    • exp: jwt的过期时间,这个过期时间必须要大于签发时间

    • nbf: 定义在什么时间之前,该jwt都是不可用的.

    • iat: jwt的签发时间

    • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

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

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

      定义一个payload:

    {
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true
    }

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

    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9 

     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

      注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

    如何应用

      一般是在请求头里加入Authorization,并加上Bearer标注:

    fetch('api/user/1', {
    headers: {
      'Authorization': 'Bearer ' + token
    }
    })  

      服务端会验证token,如果验证通过就会返回相应的资源。整个流程就是这样的:

    img

     

     

    总结

      优点

    • 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。

    • 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。

    • 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。

    • 它不需要在服务端保存会话信息, 所以它易于应用的扩展

      安全相关

    • 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。

    • 保护好secret私钥,该私钥非常重要。

    • 如果可以,请使用https协议

    JWT使用:

    安装配置

    pip install djangorestframework-jwt

    配置setting

    setting.py

    配置全局路由

    from django.contrib import admin
    from django.urls import path,re_path,include
    ​
    urlpatterns = [
        path('admin/', admin.site.urls),
              
       re_path(r'users/',include(('users.urls','users'),namespace='users'))
    ]
    urls.py

    配置局部路由

    #! /usr/bin/env python
    # -*- coding: utf-8 -*-
    from django.urls import path,re_path,include
    from users import views
    from rest_framework_jwt.views import obtain_jwt_token  # 验证密码后返回token
    ​
    urlpatterns = [
        path('v1/register/', views.RegisterView.as_view(), name='register'),  # 注册用户
        path('v1/login/', obtain_jwt_token,name='login'),  # 用户登录后返回token
        path('v1/list/', views.UserList.as_view(), name='register'),  # 测试需要携带token才能访问
    ]
    urls.py

    重写User表

    from django.db import models
    from django.contrib.auth.models import AbstractUser
    ​
    ​
    class User(AbstractUser):
        username = models.CharField(max_length=64, unique=True)
        password = models.CharField(max_length=255)
        phone = models.CharField(max_length=64)
        token = models.CharField(max_length=255)
    model.py

    设置序列化器

    #! /usr/bin/env python
    # -*- coding: utf-8 -*-
    from rest_framework_jwt.settings import api_settings
    from rest_framework import serializers
    from users.models import User
    ​
    class UserSerializer(serializers.Serializer):
        username = serializers.CharField()
        password = serializers.CharField()
        phone = serializers.CharField()
        token = serializers.CharField(read_only=True)
    ​
        def create(self, data):
            user = User.objects.create(**data)
            user.set_password(data.get('password'))
            user.save()
            # 补充生成记录登录状态的token
            jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
            jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            user.token = token
            return user
    ser.py

    代码实现

    from django.shortcuts import render
    import json
    from rest_framework.views import APIView
    from rest_framework.views import Response
    from rest_framework.permissions import IsAuthenticated
    from rest_framework_jwt.authentication import JSONWebTokenAuthentication
    from users.serializers import UserSerializer
    ​
    ​
    # 用户注册
    class RegisterView(APIView):
        def post(self, request, *args, **kwargs):
            serializer = UserSerializer(data=request.data)
            if serializer.is_valid():
                serializer.save()
                return Response(serializer.data, status=201)
            return Response(serializer.errors, status=400)
    ​
    ​
    # 重新用户登录返回函数
    def jwt_response_payload_handler(token, user=None, request=None):
        '''
        :param token: jwt生成的token值
        :param user: User对象
        :param request: 请求
        '''
        return {
            'token': token,
            'user': user.username,
            'userid': user.id
        }
    ​
    ​
    # 测试必须携带token才能访问接口
    class UserList(APIView):
        permission_classes = [IsAuthenticated]  # 接口中加权限
        authentication_classes = [JSONWebTokenAuthentication]
    ​
        def get(self,request, *args, **kwargs):
            print(request.META.get('HTTP_AUTHORIZATION', None))
            return Response({'name':'zhangsan'})
        def post(self,request, *args, **kwargs):
            return Response({'name':'zhangsan'})
    view.py

    img img

    img

    区别和优缺点:

    基于session和基于jwt的方式的主要区别就是用户的状态保存的位置,session是保存在服务端的,而jwt是保存在客户端的。

    jwt的优点:

    1. 可扩展性好

    应用程序分布式部署的情况下,session需要做多机数据共享,通常可以存在数据库或者redis里面。而jwt不需要。

    1. 无状态

    jwt不在服务端存储任何状态。RESTful API的原则之一是无状态,发出请求时,总会返回带有参数的响应,不会产生附加影响。用户的认证状态引入这种附加影响,这破坏了这一原则。另外jwt的载荷中可以存储一些常用信息,用于交换信息,有效地使用 JWT,可以降低服务器查询数据库的次数。

    jwt的缺点:

    1. 安全性

    由于jwt的payload是使用base64编码的,并没有加密,因此jwt中不能存储敏感数据。而session的信息是存在服务端的,相对来说更安全。

    1. 性能

    jwt太长。由于是无状态使用JWT,所有的数据都被放到JWT里,如果还要进行一些数据交换,那载荷会更大,经过编码之后导致jwt非常长,cookie的限制大小一般是4k,cookie很可能放不下,所以jwt一般放在local storage里面。并且用户在系统中的每一次http请求都会把jwt携带在Header里面,http请求的Header可能比Body还要大。而sessionId只是很短的一个字符串,因此使用jwt的http请求比使用session的开销大得多。

    1. 一次性

    无状态是jwt的特点,但也导致了这个问题,jwt是一次性的。想修改里面的内容,就必须签发一个新的jwt。

  • 相关阅读:
    C#数组的Map、Filter、Reduce操作
    Safari中的input、textarea无法输入的问题
    小练手:用HTML5 Canvas绘制谢尔宾斯基三角形
    Web前端页面的浏览器兼容性测试心得(三)总结一些IE8兼容问题的解决方案
    大杀器Bodymovin和Lottie:把AE动画转换成HTML5/Android/iOS原生动画
    Web前端页面的浏览器兼容性测试心得(二)搭建原汁原味的IE8测试环境
    Web前端页面的浏览器兼容性测试心得(一)搭建测试用本地静态服务器
    CSS3、SVG、Canvas、WebGL动画精选整理
    利用JS代码快速获得知网论文作为参考文献的引用文本
    使用Node.js快速搭建简单的静态文件服务器
  • 原文地址:https://www.cnblogs.com/my-soul-my-heart/p/13916262.html
Copyright © 2020-2023  润新知