JWT vs Session
一. session
1.1 session对于服务器的开销
在传统的用户登录认证中,都是采用session方式。用户登录成功,服务端会保证一个session,当然会给客户端一个sessionId,客户端会把sessionId保存在cookie中,每次请求都会携带这个sessionId。cookie+session这种模式通常是保存在内存中,而且服务从单服务到多服务会面临的session共享问题,随着用户量的增多,开销就会越大
1.2 session对于服务扩展性的限制
用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
1.3 session+cookie认证方式存在的风险
CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
二. jwt(json web token)
jwt的认证方式只需要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析就可。
2.1 jwt生成的流程
- 客户端使用用户名和密码请求服务器
- 服务器验证后返回一个jwt
- 客户端存储jwt, 每次请求都会携带这个jwt
- 服务器验证jwt, 并返回数据
2.2 jwt的构成:
jwt有三部分构成: header, payload, signature
2.2.1 header(头部)
jwt头部中承载了两个信息, 完整的头部类似与:
{
'typ': 'JWT',
'alg': 'HS256'
}
typ: 类型 alg: 加密算法, 通常为HMAC SHA256
2.2.2 payload(载荷)
载荷就是存放有效信息的地方, 这些有效信息, 包含三个部分: - 标准中注册的声明 - 公共的声明 - 私有的声明
标准中注册的声明 (建议但不强制使用):
- iss: jwt签发者
- sub: jwt所面向的用户
- aud: 接收jwt的一方
- exp: jwt的过期时间,这个过期时间必须要大于签发时间
- nbf: 定义在什么时间之前,该jwt都是不可用的.
- iat: jwt的签发时间
- jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
公共的声明:
公共的声明可以任意添加信息, 但是不可以包括敏跟信息, 因为可以被解密. 私有的声明:
私有的声明:
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
payload的样式形如:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
2.2.3 signature(签名)
签名由base64加密后的header和加密后的payload以'.'链接, 然后顺纯色header中声明的加密方式进行加盐secret
后加密.
2.3 如何使用jwt:
客户端根据接口文档, 一般讲jwt加到请求头内, 比如Authorization
中或者自定义字段x-access-token
等方式.后台程序取出对应部分的信息进行验证即可.
生成过程举例:
if check_password_hash(user.password, auth.password):
payload = {
'public_id': user.public_id,
'exp': datetime.now() + timedelta(minutes=10)
}
token = jwt.encode(payload, app.config['SECRET_KEY'])
return jsonify({'token': token.decode('utf8')})
return make_response(
'Could not verify', 401,
{'WWW-Authencate': 'Basic realm = "Login Required!"'})
验证装饰器举例
客户端请求时将jwt放置在请求头的x-access-token
字段中:
def token_required(func): # decoration
@wraps(func)
def decorated(*args, **kwargs):
token = None
if 'x-access-token' in request.headers:
token = request.headers['x-access-token']
if not token:
return jsonify({'message': 'Token is missing!'}), 401
try:
data = jwt.decode(token, app.config['SECRET_KEY'])
corrent_user = User.query.filter_by(public_id=data['public_id']).first()
except:
return jsonify({'message': 'Token is invalid!'})
return func(corrent_user, *args, **kwargs)
return decorated