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