一、session方案

1、session原理

Python 框架学习 Django篇 (五) Session与Token认证-LMLPHP

Python 框架学习 Django篇 (五) Session与Token认证-LMLPHP

添加输出响应头

vi Django_demo/mgr/sign_in_out.py

import  requests,pprint

payload = {
    'username': 'root',
    'password': '12345678'
}

response = requests.post('http://127.0.0.1:8000/api/mgr/signin',data=payload)

pprint.pprint(response.json())

#输出相应头
for header, value in response.headers.items():
    print(f'{header}: {value}')

返回

{'ret': 0}
Date: Fri, 20 Oct 2023 05:58:54 GMT
Server: WSGIServer/0.2 CPython/3.9.13
Content-Type: application/json
Content-Length: 10
Vary: Cookie
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
Set-Cookie: sessionid=otfwixmjgfngfc45n8k6efobiff4f3xs; 
expires=Fri, 03 Nov 2023 05:58:53 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax


#这一行
Set-Cookie: sessionid=otfwixmjgfngfc45n8k6efobiff4f3xs; 

cookie数据组成如下

sessionid=6qu1cuk8cxvtf4w9rjxeppexh2izy0hh
username=byhy
favorite=phone_laptop_watch

2、添加主页请求限制

Python 框架学习 Django篇 (五) Session与Token认证-LMLPHP

Set-Cookie: sessionid=otfwixmjgfngfc45n8k6efobiff4f3xs; 

vi Django_demo/mgr/k8s.py

def dispatcher(request):  # 将请求参数统一放入request 的 params 属性中,方便后续处理

    # 根据session判断用户是否是登录的管理员用户
    if 'usertype' not in request.session:
        return JsonResponse({
            'ret': 302,
            'msg': '未登录',
            'redirect': '/mgr/sign.html'},
            status=302)

    if request.session['usertype'] != 'mgr' :
        return JsonResponse({
            'ret': 302,
            'msg': '用户非mgr类型',
            'redirect': '/mgr/sign.html'} ,
            status=302)


...

3、测试访问

http://127.0.0.1:8000/api/mgr/customers/?action=list_customer

Python 框架学习 Django篇 (五) Session与Token认证-LMLPHP

4、修改测试方法

vi main.py

import  requests,pprint

payload = {
    'username': 'root',
    'password': '12345678'
}
#发送登录请求
response = requests.post('http://127.0.0.1:8000/api/mgr/signin',data=payload)


#拿到请求请求响应头中的值
set_cookie = response.headers.get('Set-Cookie')
if set_cookie:
    # 将Set-Cookie字段的值添加到请求头中
    headers = {'Cookie': set_cookie}
    # 发送带有Cookie的新请求
    response = requests.get('http://127.0.0.1:8000/api/mgr/customers/?action=list_customer',headers=headers)
    pprint.pprint(response.json())

返回

{'ret': 0,
 'retlist': [{'ClusterName': 'acp-r1-1',
              'NodeSum': '100',
              'PrometheusAddress': '192.168.1.1',
              'id': 1},
             {'ClusterName': '123123',
              'NodeSum': '123123',
              'PrometheusAddress': '123123',
              'id': 2},
             {'ClusterName': 'gfs-r3-1',
              'NodeSum': '5000',
              'PrometheusAddress': '192.168.1.21',
              'id': 3}]}

二、Token方案

1、session 缺点

1、性能问题   #验证请求是根据sessionid 到数据库中查找session表的
             #而数据库操作是服务端常见的性能瓶颈,尤其是当用户量比较大的时候

2、扩展性问题  #当系统用户特别多的时候,后端处理请求的服务端通常是部署在多个节点上
              #但是多个节点都要访问session表,这样就要求数据库服务能够被多个节点访问
              #不方便切分数据库以提高性能。

2、token机制说明

1、服务端先创建了一个密钥文件(secret key)

2、用户登录成功后,服务端会将"用户的信息数据"和"密钥"一起进行一个哈希计算从而得到一个哈希值

   首先为了哈希算法保证一致性,只能根据同样的数据源获取,如果有人修改了用户信息成其他人的
   除非他也拿到了密钥, 不然他即使再用哈希算法计算也不会得到我们认证的token值, 所以这个哈希值就方便我们用来做数据校验

   当我们拿到了这个独一无二的哈希值后,将哈希值和用户数据再做成一个字符串
   这个字符串也就被称之为token,token里面包含了用户数据和数据校验的哈希值
   服务端接受到请求后返回token值,通常来说token是放在http响应头部中的, 具体那个头部字段没有规定,可以自定义  



3、用户的后续操作如果除非http api请求则会在请求消息中带上token值

   服务端接收到请求后,会根据数据信息和密钥使用哈希再次生成哈希值,如果用户修改了数据
   因为不知道密钥没有办法得到正确的新的哈希值,那么服务端根据篡改后的数据和密钥得到的新的哈希值一定不同就知道数据被修改了

   如果客户端没有修改数据,服务端根据原来的数据加上密钥得到的哈希值
   与保存在otken中的哈希值一对比,校验通过后就知道没有被修改,可以放心使用token中的用户数据了

三、实现token认证

1、安装库

pip install djangorestframework==3.14.0
pip install djangorestframework-simplejwt 

 2、加载库配置

vi Django_demo/Django_demo/settings.py

INSTALLED_APPS = [
    'simpleui',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'paas',
    'mgr',

    #添加下面的配置
    'rest_framework',
    'rest_framework.authtoken',
    'rest_framework_simplejwt.token_blacklist',
]

3、添加全局配置

vi Django_demo/Django_demo/settings.py

#找个空地方直接贴上,其中包含了认证方式和Token的过期时间等
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',  # 使用Token进行身份验证
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',  # 需要进行身份验证的默认权限类
    ],
    'DEFAULT_THROTTLE_RATES': {
        'user': '100/day',  # 每个用户每天最多可以进行100次请求
        'anon': '10/day'  # 匿名用户每天最多可以进行10次请求
    },
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',  # 默认使用JSON渲染器来渲染响应
    ],
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',  # 默认使用JSON解析器来解析请求数据
    ],
    'DEFAULT_METADATA_CLASS':
        'rest_framework.metadata.SimpleMetadata',  # 默认使用简单元数据类
    'DEFAULT_VERSIONING_CLASS':
        'rest_framework.versioning.URLPathVersioning',  # 使用URL路径版本控制
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend'  # 默认使用Django Filter后端来进行过滤
    ],
    'DEFAULT_PAGINATION_CLASS':
        'rest_framework.pagination.PageNumberPagination',  # 默认的分页类为PageNumberPagination
    'PAGE_SIZE': 10,  # 默认的每页返回的对象数量为10
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',  # 默认使用匿名速率限制类
        'rest_framework.throttling.UserRateThrottle'  # 默认使用用户速率限制类
    ],
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',  # 默认使用AutoSchema来生成API文档
    'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation',  # 默认使用DefaultContentNegotiation进行内容协商
    'COERCE_DECIMAL_TO_STRING': False,  # 数字类型是否强制转换为字符串
    'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S',  # 默认的日期时间格式
    'DATETIME_INPUT_FORMATS': [
        '%Y-%m-%d %H:%M:%S',
        '%Y-%m-%d %H:%M',
        '%Y-%m-%d',
        '%Y%m%dT%H%M%S%fz'
    ],  # 接受的日期时间输入格式的列表
    'UNICODE_JSON': False,  # 是否使用Unicode编码的JSON
}

4、同步库数据库操作

python manage.py makemigrations
python manage.py migrate

5、注释前面session认证

vi Django_demo/mgr/k8s.py

    # 根据session判断用户是否是登录的管理员用户
    if 'usertype' not in request.session:
        return JsonResponse({
            'ret': 302,
            'msg': '未登录',
            'redirect': '/mgr/sign.html'},
            status=302)

    if request.session['usertype'] != 'mgr' :
        return JsonResponse({
            'ret': 302,
            'msg': '用户非mgr类型',
            'redirect': '/mgr/sign.html'} ,
            status=302)
http://127.0.0.1:8000/api/mgr/customers/?action=list_customer

Python 框架学习 Django篇 (五) Session与Token认证-LMLPHP

 6、开启主页函数token验证

vi Django_demo/mgr/k8s.py

#导入模块
from rest_framework.authentication import TokenAuthentication  # 导入TokenAuthentication类,用于基于令牌进行身份验证
from rest_framework.permissions import IsAuthenticated  # 导入IsAuthenticated类,用于检查用户是否已通过身份验证的权限
from rest_framework.decorators import api_view, permission_classes, authentication_classes  # 导入装饰器函数,用于设置视图函数的身份验证和权限控制



#添加装饰器
@api_view(['GET'])
@authentication_classes([TokenAuthentication])  # 使用TokenAuthentication类进行身份验证
@permission_classes([IsAuthenticated])  # 要求用户已通过身份验证
def dispatcher(request): 
    ...
http://127.0.0.1:8000/api/mgr/customers/?action=list_customer

Python 框架学习 Django篇 (五) Session与Token认证-LMLPHP

from rest_framework.authtoken.models import Token  # 导入 Token 模型,用于创建和管理用户的 Token
from django.contrib.auth.models import User  # 导入 User 模型,用于获取用户对象

# 获取用户
user = User.objects.get(username='root')  # 根据用户名获取用户对象

# 创建 Token
token, created = Token.objects.get_or_create(user=user)  # 创建或获取与用户关联的 Token

7、登录函数添加token获取

Django_demo/mgr/sign_in_out.py

def signin( request):
    userName = request.POST.get('username')
    passWord = request.POST.get('password')

    user = authenticate(username=userName, password=passWord)

    if user is not None:
        if user.is_active:
            if user.is_superuser:
                login(request, user)
                request.session['usertype'] = 'mgr'




                #添加返回用户的token
                from rest_framework.authtoken.models import Token
                from django.contrib.auth.models import User
                user = User.objects.get(username=userName)

                # 创建或获取 Token
                token, created = Token.objects.get_or_create(user=user)
                return JsonResponse({'token': str(token), 'ret': 0})



            else:
                return JsonResponse({'ret': 1, 'msg': '请使用管理员账户登录'})
        else:
            return JsonResponse({'ret': 0, 'msg': '用户已经被禁用'})

    # 否则就是用户名、密码有误
    else:
        return JsonResponse({'ret': 1, 'msg': '用户名或者密码错误'})

8、测试检查是否返回token值

vi main.py

import  requests,pprint

payload = {
    'username': 'root',
    'password': '12345678'
}
#发送登录请求
response = requests.post('http://127.0.0.1:8000/api/mgr/signin',data=payload)
print(response.headers.get('Authorization'))

返回

Token 1568e26d12af8a5a6489603763e662cf0c65c73a

9、携带token值请求主页

vi main.py

import  requests,pprint

payload = {
    'username': 'root',
    'password': '12345678'
}
#发送登录请求
response = requests.post('http://127.0.0.1:8000/api/mgr/signin',data=payload)
token = response.headers.get('Authorization')


#携带token请求
if token:
    # 将Set-Cookie字段的值添加到请求头中
    headers = {'Authorization': f'{token}'}
    response = requests.get('http://127.0.0.1:8000/api/mgr/customers/?action=list_customer',headers=headers)
    pprint.pprint(response.json())

返回

{'ret': 0,
 'retlist': [{'ClusterName': 'acp-r1-1',
              'NodeSum': '100',
              'PrometheusAddress': '192.168.1.1',
              'id': 1},
             {'ClusterName': '123123',
              'NodeSum': '123123',
              'PrometheusAddress': '123123',
              'id': 2},
             {'ClusterName': 'gfs-r3-1',
              'NodeSum': '5000',
              'PrometheusAddress': '192.168.1.21',
              'id': 3}]}

10、数据库内的token查询

 Python 框架学习 Django篇 (五) Session与Token认证-LMLPHP

四、session和token的区别详解

1、存储位置

Token:Token通常存储在服务器端的数据库中,或者可以存储在客户端的cookie或本地存储中。每次发起请求时,Token将通过请求头或请求参数进行传递。

Session:Session通常存储在服务器端的数据库或缓存中。服务器将为每个会话分配一个唯一的Session ID,并将Session ID存储在客户端的cookie中。客户端在发送请求时会自动携带Session ID。

2、无状态性

Token    Token是无状态的,服务器不需要在后端存储任何关于用户会话的状态信息。
         每个请求都包含所有必要的信息(通常是在Token本身中)来进行身份验证和授权。

Session:Session是有状态的,服务器需要在后端存储有关用户会话的状态信息。客户端的每个请求都需要携带Session ID,服务器根据Session ID检索并验证对应的会话状态。

3、扩展性和跨域支持

Token  由于Token是无状态的,因此易于扩展和支持跨域请求。
        服务器不需要存储每个用户的会话状态,因此可以更好地支持负载均衡和分布式系统。

Session  由于Session是有状态的,需要在服务器端存储状态信息,
         因此在处理大量并发请求或跨域请求时可能存在一些挑战。

4、过期和失效机制

Token   Token可以通过设置过期时间来自动失效,或者可以通过撤销Token的方式来手动使其失效。
        客户端需要负责在失效前获取新的Token。

Session   Session可以通过设置过期时间来自动失效,但服务器也可以更主动地管理会话状态
          例如在用户注销或一段时间内无活动后自动销毁会话。

总结

10-21 07:01