返回 首页

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生成的流程

2.2 jwt的构成:

jwt有三部分构成: header, payload, signature

2.2.1 header(头部)

jwt头部中承载了两个信息, 完整的头部类似与:

{
  'typ': 'JWT',
  'alg': 'HS256'
}

typ: 类型 alg: 加密算法, 通常为HMAC SHA256

2.2.2 payload(载荷)

载荷就是存放有效信息的地方, 这些有效信息, 包含三个部分: - 标准中注册的声明 - 公共的声明 - 私有的声明

标准中注册的声明 (建议但不强制使用):
公共的声明:

公共的声明可以任意添加信息, 但是不可以包括敏跟信息, 因为可以被解密. 私有的声明:

私有的声明:

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为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

登录