在实现登陆功能的时候,正常的B/S应用都会使用cookie+session的方式来作身份验证,后台直接向cookie中写数据,可是因为移动端的存在,移动端是没有cookie机制的,因此使用token能够实现移动端和客户端的token通讯。redis
整个基于Token的验证流程以下:算法
构造Token的方法挺多的,能够说只要是客户端和服务器端约定好了格式,是想怎么写就怎么写的,然而还有一些标准写法,例如JWT读做/jot/,表示:JSON Web Tokens.
JWT标准的Token有三个部分:数据库
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
header部分主要是两部份内容,一个是Token的类型,另外一个是使用的算法,好比下面的类型就是JWT,使用的算法是HS256:django
{ "typ": "JWT", "alg": "HS256" }
上面的内容要用 Base64 的形式编码一下,因此就变成这样:缓存
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Payload 里面是 Token 的具体内容,这些内容里面有一些是标准字段,你也能够添加其它须要的内容。下面是标准字段:服务器
JWT的最后一部分是Signature,这部分至关于前两段的摘要,用来防止其余人来篡改Token中的信息,在处理时能够首先将前两段生成的内容使用Base64生成一下再加盐而后利用MD5等摘要算法在生成一遍cookie
在服务端生成Token的时候,须要解决两个问题session
这里的加密算法并非MD5,SHA1这样的哈希算法,由于这种算法是没法解密的,只能用来生成摘要,在Django中内置了一个加密前面模块django.core.signing模块,能够用来加密和解密任何数据,使用签名模块的dumps和load函数来实现分布式
示例:函数
from django.core import signing value = signing.dumps({"foo":"bar"}) src = signing.loads(value) print(value) print(src)
结果:
eyJmb28iOiJiYXIifQ:1NMg1b:zGcDE4-TCkaeGzLeW9UQwZesciI {‘foo’: ‘bar’}
在服务器中Token能够存储在内存中,由于本质是字符串,因此并不会占用很大的内存空间,若是是分布式的存储能够将全部的token信息分段存储在不一样的服务器中,也能够存储在数据库中,在Django中提供了缓存类,能够用来存储Token,Django的缓存能够结合Redis来使用,能够借助django-redis来实现
pip install 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
cache使用的时候基本可使用set和get方法来进行存/取数据
>>> cache.set('my_key', 'hello, world!', 30) >>> cache.get('my_key') 'hello, world!'
因为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