基于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

原文地址:https://www.cnblogs.com/davis12/p/14585390.html