drf-day10

Cookie、Session、Token

  • Cookie:存在客户端浏览器的键值对
  • Session:存在于服务端的键值对
  • Token:三段式,服务端生成的,存放在客户端(浏览器就放在cookie中,移动端:存在移动端中)

Cookie、Session

cookie 是一个非常具体的东西,指的就是浏览器里面能永久存储的一种数据,仅仅是浏览器实现的一种数据存储功能。

cookie由服务器生成,发送给浏览器,浏览器把cookie以kv形式保存到某个目录下的文本文件内,下一次请求同一网站时会把该cookie发送给服务器。由于cookie是存在客户端上的,所以浏览器加入了一些限制确保cookie不会被恶意使用,同时不会占据太多磁盘空间,所以每个域的cookie数量是有限的

Session

session 从字面上讲,就是会话。这个就类似于你和一个人交谈,你怎么知道当前和你交谈的是张三而不是李四呢?对方肯定有某种特征(长相等)表明他就是张三。

session 也是类似的道理,服务器要知道当前发请求给自己的是谁。为了做这种区分,服务器就要给每个客户端分配不同的“身份标识”,然后客户端每次向服务器发请求的时候,都带上这个“身份标识”,服务器就知道这个请求来自于谁了。至于客户端怎么保存这个“身份标识”,可以有很多种方式,对于浏览器客户端,大家都默认采用 cookie 的方式。

服务器使用session把用户的信息临时保存在了服务器上,用户离开网站后session会被销毁。这种用户信息存储方式相对cookie来说更安全,可是session有一个缺陷:如果web服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候session会丢失。

cookie和session的区别

  • session是存储服务器端,cookie是存储在客户端,所以session的安全性比cookie高

    • 获取session里的信息是通过存放在会话cookie里的session id获取的。而session是存放在服务器的内存中里,所以session里的数据不断增加会造成服务器的负担,所以会把很重要的信息存储在session中,而把一些次要东西存储在客户端的cookie里。
  • session的信息是通过sessionid获取的,而sessionid是存放在会话cookie中

    • 当浏览器关闭的时候会话cookie消失,所以sessionid也就消失了,但是session的信息还存在服务器端,只是查不到所谓的session,但它并不是不存在

Token

简介

在Web领域基于Token的身份验证随处可见。在大多数使用Web API的互联网公司中,tokens 是多用户下处理认证的最佳方式。

以下几点特性会让你在程序中使用基于Token的身份验证

  • 无状态、可扩展
  • 支持移动设备
  • 跨程序调用
  • 安全

token三段式:

  • 第一段:头:公司信息,加密方式……
  • 第二段:荷载:真正的数据
  • 第三段:签名,通过第一段和第二段,通过某种加密方式加密得到的 aafasdfas

token的使用分两个阶段:

  • 登录成功后的签发token阶段:生成三段
  • 登录成功访问某个接口的验证阶段:验证token是否合法

那些使用基于Token的身份验证的大佬们:大部分你见到过的API和Web应用都使用tokens。例如Facebook, Twitter, Google+, GitHub等

Token的优势

  1. 无状态、可扩展
  2. 安全性
  3. 可扩展性
  4. 多平台与跨域
  5. 基于标准

jwt原理介绍

简介

Json web token (JWT), token的应用于web方向的称之为jwt

jwt使用流程最核心的是:

  • 签发:登录接口签发
  • 认证:认证类认证

构成和工作原理

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

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

  • 声明类型:jwt
  • 声明加密算法:通常直接使用HMAC SHA256

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

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

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

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

payload

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

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

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

  • exp:jwt的过期时间,这个过期时间必须要大于签发时间
  • iat:jwt的签发时间
  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • nbf: 定义在什么时间之前,该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了

base64编码和解码

base64编码:base64 可以把字符串编码成base64的编码格式:(大小写字母,数字和 =)

base64解码:base64可以把base64编码的字符串,解码回原来的格式

应用场景:

  • jwt中使用
  • 网络中传输字符串就可以使用base64编码
  • 网络中传输图片,也可能使用base64的编码
import base64
import json
 
 
dic_info={
    "sub": "1234567890",
    "name": "lqz",
    "admin": True
}
byte_info=json.dumps(dic_info).encode('utf-8')
 
# base64编码
base64_str=base64.b64encode(byte_info)
print(base64_str)
 
# base64解码
base64_str='eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogImxxeiIsICJhZG1pbiI6IHRydWV9'
str_url = base64.b64decode(base64_str).decode("utf-8")
print(str_url)

drf-jwt快速使用

官网

http://getblimp.github.io/django-rest-framework-jwt/

安装

pip install djangorestframework-jwt

快速使用

  1. 迁移表:jwt默认使用auth的user表签发token

  2. 创建超级用户(auth的user表中要有记录)

  3. 不需要写登录接口了,如果是使用auth的user表作为用户表,它可以快速签发

  4. 签发(登录):只需要在路由中配置(因为它帮咱们写好登录接口了)

    from rest_framework_jwt.views import obtain_jwt_token
     
     
    urlpatterns = [
        path('login/', obtain_jwt_token),
    ]
    
  5. 认证(认证类):导入,配置在视图类上

    class TestView(APIView):
        authentication_classes = [JSONWebTokenAuthentication,]
        permission_classes = [IsAuthenticated,]
    
  6. 前端访问时,token需要放在请求头中,postman发送访问请求(必须带jwt空格)

    Authorization:jwt token串
    

drf-jwt修改返回格式

登录成功后,前端看到的格式,太固定了,只有token,我们想做成

{code:100,msg:'登录成功',token:adfasdfasdf}

固定写法:写一个函数,函数返回什么,前端就看到什么,配置在配置文件中

使用步骤:

  1. 写一个函数

    def jwt_response_payload_handler(token, user=None, request=None):
        return {
            'code':100,
            'msg':'登录成功',
            'username':user.username,
            'token':token
        }
    
  2. 把函数配置在配置文件

    JWT_AUTH={
        'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.response.jwt_response_payload_handler',
    }
    

注意:以后登录接口返回的格式就是咱们写的函数的返回值

自定义user表,签发token

# models.py 中定义UserInfo表
 
class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
 
# 写一个登录接口
from rest_framework.exceptions import APIException
 
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
 
 
from .models import UserInfo
class UserView(APIView):
    def post(self, request):
        try:
            username = request.data.get('username')
            password = request.data.get('password')
            user=UserInfo.objects.get(username=username,password=password)
            # 根据user,签发token---》三部分:头,荷载,签名
            # 使用djagnorestframework-jwt模块提供的签发token的函数,生成token
            payload = jwt_payload_handler(user) # 通过user对象---》{username:lqz,id:1,过期时间}
            token=jwt_encode_handler(payload) # 根据payload---》得到token:头.荷载.签名
            print(payload)
            print(token)
 
            return Response({'code':100,'msg':'登录成功','token':token})
        except Exception as e:
            raise APIException('用户名或密码错误')
posted @ 2022-10-12 20:19  梦想有双休  阅读(14)  评论(0)    收藏  举报