HTTP协议是无状态协议,为了解决它产生了cookie和session技术。
session id
,就认为这是第一次请求,会返回一个新的session id
给浏览器端。session id
就会随着每一次请求重新发给服务器端,服务器端查找这个session id
,如果查到,就认为是同一个会话。如果没有查到,就认为是新的请求。session
是会话级的,可以在这个会话session
中创建很多数据,连接断开session
清除,包括session id
。session id
还得有过期的机制,一段时间如果没有发起请求,认为用户已经断开,就清除session
。浏览器端也会清除响应的cookie
信息。服务器端保存着大量session
信息,很消耗服务器内存,而且如果多服务器部署,还要考虑session
共享的问题,比如redis、memcached
等方案。
既然服务端就是需要一个ID
来表示身份,那么不使用session
也可以创建一个ID
返回给客户端。但是,要保证客户端不可篡改。
服务端生成一个标识,并使用某种算法对标识签名. 服务端收到客户端发来的标识,需要检查签名。
这种技术称为JWT(Json Web Token)
JWT的缺点是:
基于token的认证方式相比传统的session认证方式更节约服务器资源,并且对移动端和分布式更加友好。其优点如下:
而JWT
就是上述流程当中token
的一种具体实现方式,其全称是JSON Web Token
,官网地址:https://jwt.io/
通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token
,并且这个JWT token
带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。JWT的认证流程如下:
我们知道HTTP本身是一种无状态的协议,这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,认证通过后HTTP协议不会记录下认证后的状态,
那么下一次请求时,用户还要再一次进行认证,因为根据HTTP协议,我们并不知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,
我们只能在用户首次登录成功后,在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,
这样我们的应用就能识别请求来自哪个用户了,这是传统的基于session认证的过程。
然而,传统的session
认证有如下的问题:
对比传统的session认证方式,JWT的优势是:
因为这些优势,目前无论单体应用还是分布式应用,都更加推荐用JWT token的方式进行用户认证
JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串:
JWTString=Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。
最后,使用Base64 URL算法将上述JSON对象转换为字符串保存
header = {
"alg": "HS256",
"typ": "JWT"
}
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择
# iss:发行人
# exp:到期时间
# sub:主题
# aud:用户
# nbf:在此之前不可用
# iat:发布时间
# jti:JWT ID用于标识该JWT
# 这些预定义的字段并不要求强制使用。除以上默认字段外,我们还可以自定义私有字段,一般会把包含用户信息的数据放到payload中,如下例:
payload = {
"sub": "1234567890",
"name": "Helen",
"admin": True
}
请注意: 默认情况下JWT是未加密的,因为只是采用base64算法,拿到JWT字符串后可以转换回原本的JSON数据,任何人都可以解读其内容,
因此不要构建隐私信息字段,比如用户的密码一定不能保存到JWT中,以防止信息泄露。JWT只是适合在网络中传输一些非敏感的信息
签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。
首先,需要指定一个密钥(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名:
HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用.分隔,就构成整个JWT对象
注意JWT每部分的作用,在服务端接收到客户端发送过来的JWT token
之后:
header``和payload
可以直接利用base64
解码出原文,从header
中获取哈希签名的算法,从payload
中获取有效数据signature
由于使用了不可逆的加密算法,无法解码出原文,它的作用是校验token有没有被篡改。服务端获取header中的加密算法之后,利用该算法加上secretKey
对header、payload
进行加密,比对加密后的数据和客户端发送过来的是否一致。注意secretKey
只能保存在服务端,而且对于不同的加密算法其含义有所不同,一般对于MD5类型的摘要加密算法,secretKey
实际上代表的是盐值其实JWT(JSON Web Token)
指的是一种规范,这种规范允许我们使用JWT
在两个组织之间传递安全可靠的信息,JWT
的具体实现可以分为以下几种:
nonsecure JWT
:未经过签名,不安全的JWTJWS
:经过签名的JWTJWE
:payload
部分经过加密的JWTnonsecure JWT
未经过签名,不安全的JWT。其header部分没有指定签名算法,并且也没有Signature
部分。
header = {
"alg": "none",
"typ": "JWT"
}
JWS
JWS ,也就是JWT Signature
,其结构就是在之前nonsecure JWT
的基础上,在头部声明签名算法,并在最后添加上签名。创建签名,是保证jwt不能被他人随意篡改。我们通常使用的JWT一般都是JWS
为了完成签名,除了用到header
信息和payload
信息外,还需要算法的密钥,也就是secretKey
。加密的算法一般有2类:
secretKey
指加密密钥,可以生成签名与验签secretKey
指私钥,只用来生成签名,不能用来验签(验签用的是公钥)JWT的密钥或者密钥对,一般统一称为JSON Web Key
,也就是JWK
到目前为止,jwt的签名算法有三种:
HMAC
【哈希消息验证码(对称)】:HS256/HS384/HS512RSASSA
【RSA签名算法(非对称)】(RS256/RS384/RS512)ECDSA
【椭圆曲线数据签名算法(非对称)】(ES256/ES384/ES512)pip install PyJWT:
import jwt
from jwt import algorithms
import base64
"""
认证:
HTTP是无状态协议,为了解决产生了cookie和session技术。
jwt生产的token分为三部分
1、header:由数据类型、加密算法构成
2、payload: 负载,就是要传输的数据,一般来说放入python对象即可,会被json序列化
3、signature: 签名部分。是签名2部分数据分别base64编码后,使用点号连接,加密算法使用key计算好一个结果,再被base64编码得到的签名
"""
key = "secret"
token = jwt.encode({"some": "payload"}, key, algorithm="HS256")
print(token, type(token))
header, payload, signature = token.split(".")
print(header, base64.urlsafe_b64decode(header))
print(payload, base64.urlsafe_b64decode(payload))
# 签名部分
rem = len(signature) % 4
if rem > 0:
signature += '=' * (4 - rem)
print(signature, base64.urlsafe_b64decode(signature))
alg = algorithms.get_default_algorithms()["HS256"]
newkey = alg.prepare_key(key)
signing_input, _, _ = token.rpartition(".")
print(signing_input)
使用邮箱 + 密码方式登录。
邮箱要求唯一就行了,但是,密码如何存储?
早期,都是明文的密码存储。
后来,使用MD5存储,但是,目前也不安全,网上有很多MD5的网站,使用反查方式找到密码。
加盐,使用hash(password+salt)的结果存入数据库中,就算拿到数据库的密码反查,也没有用了。如果是固定加盐,还是容易被找到规律,或者从源码中泄露。随机加盐,每一次盐都变,就增加了破解的难度。
暴力破解:什么密码都不能保证不被暴力破解,例如穷举。所以要是慢hash算法,例如bcrypt
,就会让每一次计算都很慢,都是秒级的,这样穷举的时间就会很长,为了一个密码破解的时间在当前CPU或者GPU的计算能力下可能需要几十年以上。
python安装bcrypt模块:pip install bcrypt
import bcrypt
pwd = "hello@123".encode()
# 获取盐值,每次获取的都不一样
print(1, bcrypt.gensalt())
print(2, bcrypt.gensalt())
# 计算生成的密文也不一样
x1 = bcrypt.hashpw(pwd, bcrypt.gensalt())
print(3, x1)
x2 = bcrypt.hashpw(pwd, bcrypt.gensalt())
print(4, x2)
print(bcrypt.checkpw(pwd, x1), len(x1))
print("~~~~~~~~~~~~~~~~")
salt = b'$2b$12$jGGPpdxKeOd/A1vxwSob7.'
print(salt)
print(bcrypt.hashpw(pwd, salt))
# 计算值:b'$2b$12$jGGPpdxKeOd/A1vxwSob7.lnWJuSP9ZRYAkATmXP.eMeZnIjMLg/e'
# 可以看出:$是分隔符。 $2b$ -- 加密算法,其中12 表示2^12 key expansion rounds