zoukankan      html  css  js  c++  java
  • 基于django框架的cookie,session,token认证方式

    HTTP协议本身是”无状态”的,在一次请求和下一次请求之间没有任何状态保持,服务器无法识别来自同一用户的连续请求。有了cookie和session、token,服务器就可以利用它们记录客户端的访问状态了,这样用户就不用在每次访问不同页面都需要登录了,也叫单点登录。

    1.cookie

    cookie的应用场景及缺点

    cookie是一种数据存储技术, 它是将一段文本保存在客户端(浏览器或本地电脑)的一种技术,并且可以长时间的保存。当用户首次通过客户端访问服务器时,web服务器会发送给客户端的一小段信息。客户端浏览器会将这段信息以cookie形式保存在本地某个目录下的文件内。当客户端下次再发送请求时会自动将cookie也发送到服务器端,这样服务器端通过查验cookie内容就知道该客户端早访问过了。

    cookie的常见应用场景包括:

    • 判断用户是否已经登录
    • 记录用户登录信息(比如用户名,上次登录时间)
    • 记录用户搜索关键词

    ookie的缺点在于其并不可靠不安全,主要原因如下:

    • 浏览器不一定会保存服务器发来的cookie,用户可以通过设置选择是否保存cookie。
    • cookie是有生命周期的(通过Expire设置),如果超过周期,cookie就会被清除。
    • HTTP数据通过明文发送,容易受到攻击,因此不能在cookie中存放敏感信息(比如信用卡号,密码等)。
    • cookie以文件形式存储在客户端,用户可以随意修改的。

    Django中使用cookies

    设置cookies(保存数据到客户端)
    response.set_cookie(key,value,expires)
    key : cookie的名称
    value : 保存的cookie的值
    expires : 保存的时间,以秒为单位

    例子: response.set_cookie('username','John',60*60*24)

    一般在Django的视图中先生成不含cookie的response,然后set_cookie, 最后把response返回给客户端(浏览器)。

    下面是3个设置cookie的例子:

    例子1、不使用模板

    response = HttpResponse("hello world")
    response.set_cookie(key,value,expires)
    return response
    

    例子2、使用模板

    response = render(request,'xxx.html', context)
    response.set_cookie(key,value,expires)
    return response
    

    例子3、重定向

    response = HttpResponseRedirect('/login/')
    response.set_cookie(key,value,expires)
    return response
    

    获取cookies,获取用户发来请求中的cookies

    request.COOKIES['username']
    request.COOKIES.get('username')
    

    检查cookies是否已经存在

    request.COOKIES.has_key('<cookie_name>')
    

    删除cookies

    response.delete_cookie('username')
    

    django中使用场景简介:

    通常来说,有些功能的实现需要通过认证。

    举个例子,如用户点击某一块链接时,如果需要先登录认证的,这个请求会以post方式请求后台资源,后台接收到post请求后,先get用户请求的cookies,看cookies是否有相应信息,如果有,后台经过处理后返回响应数据。
    如果没有,这个时候应该做的逻辑就是跳转或重定向到login界面,通过用户再次post过来的数据validate后,后台先到数据库里查找有没这个用户,如果有,接下来应该为用户设置cookies后返回数据。
    之后还有请求过来,重复上面操作,即有cookies处理响应,无cookies认证后再响应重定向。

    2.session

    session及session的工作原理

    session又名会话,其功能与应用场景与cookie类似,用来存储少量的数据或信息。但由于数据存储在服务器上,而不是客户端上,所以比cookie更安全。

    Session工作的流程如下:

    • 客户端向服务器发送请求时,看本地是否有cookie文件。如果有,就在HTTP的请求头(Request Headers)中,包含一行cookie信息。
    • 服务器接收到请求后,根据cookie信息,得到sessionId,根据sessionId找到对应的session,用这个session就能判断出用户是否登录等等。

    使用Session的好处在于,即使用户关闭了浏览器,session仍将保持到会话过期。

    django使用session

    设置session的值

    request.session['key'] = value
    request.session.set_expiry(time):设置过期时间,0表示浏览器关闭则失效
    

    获取session的值

    request.session.get('key',None)
    

    删除 session 的值

    del request.session['key']
    

    判断是否在session里

    'fav_color' in request.session
    

    获取所有session的key和value

    request.session.keys()
    request.session.values()
    request.session.items()
    

    settings.py 有关session的设置

    1、SESSION_COOKIE_AGE = 60 * 30
    2、SESSION_EXPIRE_AT_BROWSER_CLOSE = True
    

    django中使用场景简介:

    参照cookie认证,发送请求到后台先看请求get session信息,如果都没有则重定向login,然后经过validate后查看数据库有没该用户,没有就提示注册,有的话因为这次登陆没有携带session,后台通过request设置生成session后处理返回数据。
    当然,也可以有其他的处理思路,后台收到请求时可以查看后台缓存有没有相应数据,有的话就继续下步操作,没有自然login,然后设置生成session,缓存也可以保存一份,下次ttl内访问直接处理下步请求。

    另外,通过在session中设置其他字段也可以完成一些额外的功能,如多次评论限制问题的实现。

    3.token

    在实现登录功能的时候,正常的B/S应用都会使用cookie+session的方式来做身份验证,后台直接向cookie中写数据,但是由于移动端的存在,移动端是没有cookie机制的,所以使用token可以实现移动端和客户端的token通信。

    验证流程

    • 整个基于Token的验证流程如下:
    • 客户端使用用户名跟密码请求登录
    • 服务器收到请求,去验证用户名和密码
    • 验证成功后,服务端会签发一个Token,再把这个Token发送到客户端
    • 客户端收到的Token以后可以把它存储起来,比如放在Cookie或LocalStorage里
    • 客户端每次向服务器发送其他请求的时候都要带着服务器签发的Token
    • 服务器收到请求,去验证客户端请求里面带着的Token,如果验证成功,就像客户端返回请求的数据

    JWT

    构造Token的方法挺多的,可以说只要是客户端和服务器端约定好了格式,是想怎么写就怎么写的,然而还有一些标准写法,例如JWT表示:JSON Web Tokens。

    JWT标准的Token有三个部分:

    • header
    • payload
    • signature
      三个部分会用点分割开,并且都会使用Base64编码, Header.Payload.Signature, 所以真正的Token看起来像这样:
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
    

    Header
    header部分主要是两部分内容,一个是Token的类型,另一个是使用的算法,比如下面的类型就是JWT,使用的算法是HS256:

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

    上面的内容要用 Base64 的形式编码一下,所以就变成这样:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    

    Payload
    Payload 里面是 Token 的具体内容,这些内容里面有一些是标准字段,你也可以添加其它需要的内容。下面是标准字段:

    iss:Issuer,发行者
    sub:Subject,主题
    aud:Audience,观众
    exp:Expiration time,过期时间
    nbf:Not before
    iat:Issued at,发行时间
    jti:JWT ID
    

    Signature
    JWT的最后一部分是Signature,这部分相当于前两段的摘要,用来防止其他人来篡改Token中的信息,在处理时可以首先将前两段生成的内容使用Base64生成一下再加盐然后利用MD5等摘要算法在生成一遍。

    服务端生成Token
    在服务端生成Token的时候,需要解决两个问题:

    • 使用什么加密算法
    • Token如何存储

    加密算法
    这里的加密算法并不是MD5,SHA1这样的哈希算法,因为这种算法是无法解密的,只能用来生成摘要,在Django中内置了一个加密前面模块django.core.signing模块,可以用来加密和解密任何数据,使用签名模块的dumps和load函数来实现。

    Token如何存储
    用什么存储
    在服务器中Token可以存储在内存中,因为本质是字符串,所以并不会占用很大的内存空间,如果是分布式的存储可以将所有的token信息分段存储在不同的服务器中,也可以存储在数据库中,在Django中提供了缓存类,可以用来存储Token,Django的缓存可以结合Redis来使用,可以借助django-redis来实现。

    配置
    为了使用django-redis,需要将django cache setting修改,修改settings.py,默认是没有Cache的配置信息的,在其中添加:

    CACHES = {
        "default": {
            "BACKEND": "django_redis.cache.RedisCache",
            "LOCATION": "redis://127.0.0.1:6379",
            "OPTIONS": {
                "CLIENT_CLASS": "django_redis.client.DefaultClient",
            }
        }
    }
    

    如何存
    由于redis是使用k-v模式来进行存储数据的,我们可以使用用户名作为key,而token信息作为value,相较于直接使用token作为key的方式,好处是我们可以使用更少的空间实现一些功能,例如当用户修改了密码或点击注销之后,它的token可以直接失效,直接将该用户名所对应的数据删除就好了,或者用户在一次登录成功后,又一次请求了登录接口,我们可以很简单的更新该用户的token信息,而这样存储所依赖于我们的token可以进行解密,如果你是直接生成了一串无法解密的数据作为token,不能使用用户名作为token了。

    代码演示

    import time
    from django.core import signing
    import hashlib
    from django.core.cache import cache
    
    HEADER = {'typ': 'JWP', 'alg': 'default'}
    KEY = 'CHEN_FENG_YAO'
    SALT = 'www.lanou3g.com'
    TIME_OUT = 30 * 60  # 30min
    
    
    def encrypt(obj):
        """加密"""
        value = signing.dumps(obj, key=KEY, salt=SALT)
        value = signing.b64_encode(value.encode()).decode()
        return value
    
    
    def decrypt(src):
        """解密"""
        src = signing.b64_decode(src.encode()).decode()
        raw = signing.loads(src, key=KEY, salt=SALT)
        print(type(raw))
        return raw
    
    
    def create_token(username):
        """生成token信息"""
        # 1. 加密头信息
        header = encrypt(HEADER)
        # 2. 构造Payload
        payload = {"username": username, "iat": time.time()}
        payload = encrypt(payload)
        # 3. 生成签名
        md5 = hashlib.md5()
        md5.update(("%s.%s" % (header, payload)).encode())
        signature = md5.hexdigest()
        token = "%s.%s.%s" % (header, payload, signature)
        # 存储到缓存中
        cache.set(username, token, TIME_OUT)
        return token
    
    
    def get_payload(token):
        payload = str(token).split('.')[1]
        payload = decrypt(payload)
        return payload
    
    
    # 通过token获取用户名
    def get_username(token):
        payload = get_payload(token)
        return payload['username']
        pass
    
    
    def check_token(token):
        username = get_username(token)
        last_token = cache.get(username)
        if last_token:
            return last_token == token
        return False
    

    pyjwt

    当然python的库很强大,不用自己造轮子,直接用现成的模块即可,其中pyjwt可以考虑。

    安装模块:

    pip install pyjwt
    

    利用 PyJWT 生成 Token:

    >>> import jwt
    >>> encoded_jwt = jwt.encode({'username':'运维咖啡吧','site':'https://ops-coffee.cn'},'secret_key',algorithm='HS256')
    

    这里传了三部分内容给 JWT:

    • 第一部分是一个 Json 对象,称为 Payload,主要用来存放有效的信息,例如用户名,过期时间等等所有你想要传递的信息
    • 第二部分是一个秘钥字串,这个秘钥主要用在下文 Signature 签名中,服务端用来校验 Token 合法性,这个秘钥只有服务端知道,不能泄露
    • 第三部分指定了 Signature 签名的算法

    JWT 是不加密的,任何人都可以读的到其中的信息,其中第一部分 Header 和第二部分 Payload 只是对原始输入的信息转成了 base64 编码,第三部分 Signature 是用 header+payload+secret_key 进行加密的结果。

    因为 JWT 不会对结果进行加密,所以不要保存敏感信息在 Header 或者 Payload 中,服务端也主要依靠最后的 Signature 来验证 Token 是否有效以及有无被篡改。

    服务端在有秘钥的情况下可以直接对 JWT 生成的 Token 进行解密,解密成功说明 Token 正确,且数据没有被篡改。
    当然我们前文说了 JWT 并没有对数据进行加密,如果没有 secret_key 也可以直接获取到 Payload 里边的数据,只是缺少了签名算法无法验证数据是否准确,pyjwt 也提供了直接获取 Payload 数据的方法,如下:

    >>> jwt.decode(encoded_jwt, verify=False)
    {'username': '运维咖啡吧', 'site': 'https://ops-coffee.cn'}
    

    下面看看在具体的django项目中是可以怎样实现生成用户token的方法。

    通过给 User model 添加一个 token 的静态方法来处理

    class User(AbstractBaseUser, PermissionsMixin):
        create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
        update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
        username = models.EmailField(max_length=255, unique=True, verbose_name='用户名')
        fullname = models.CharField(max_length=64, null=True, verbose_name='中文名')
        phonenumber = models.CharField(max_length=16, null=True, unique=True, verbose_name='电话')
        is_active = models.BooleanField(default=True, verbose_name='激活状态')
    
        objects = UserManager()
    
        USERNAME_FIELD = 'username'
        REQUIRED_FIELDS = []
    
        def __str__(self):
            return self.username
    
        @property
        def token(self):
            return self._generate_jwt_token()
    
        def _generate_jwt_token(self):
            token = jwt.encode({
                'exp': datetime.utcnow() + timedelta(days=1),
                'iat': datetime.utcnow(),
                'data': {
                    'username': self.username
                }
            }, settings.SECRET_KEY, algorithm='HS256')
    
            return token.decode('utf-8')
    
        class Meta:
            default_permissions = ()
    
            permissions = (
                ("select_user", "查看用户"),
                ("change_user", "修改用户"),
                ("delete_user", "删除用户"),
            )
    

    另外,思考下这些能不能不直接影响视图层的处理,通过中间件处理是否可以呢。
    思路:

    • 自己写处理认证凭据的中间件,然后加到settings.py

    参考:
    https://mp.weixin.qq.com/s?__biz=MjM5OTMyODA4Nw==&mid=2247483911&idx=1&sn=cb66b1179996ca1107af279cf40eb5ff&chksm=a73c623f904beb29ebd1c4934ac1cc94e936b6615ed9dd9e258fa1d92d0c04b08c765c6b5d86&scene=21#wechat_redirect
    http://www.bubuko.com/infodetail-3141395.html
    https://www.v2ex.com/t/530103

  • 相关阅读:
    uboot主Makefile之1——HOSTARCH&HOSTOS
    uboot主Makefile之3——BUILD_DIR(Line 78-93)
    Makefile的ifeq逻辑或,逻辑与的变通实现
    uboot主Makefile解析第二篇
    uboot主Makefile解析第一篇
    mkdir -p X 中的“-p”是的意思
    uboot主Makefile中的origin函数
    原生javascript实现call、apply和bind的方法
    js如何判断数组是Array类型
    三栏布局的5种解决方案及优缺点
  • 原文地址:https://www.cnblogs.com/davis12/p/14585390.html
Copyright © 2011-2022 走看看