采用JWT方式在客户端安全的存储数据

不管是在PC还是移动应用中都需要缓存来自服务端返回的数据,例如用户登录标识、应用中某些状态数据等等,目的是为了下次还原这些数据,并且能够验证这些数据并没有经过篡改,也就是数据来源可信问题。

要解决这类问题的基本思路有两个,一是服务端返回给客户端数据时,客户端将拿到的数据加密后存储,下次客户端读取到这些数据还原出来。第二种方式是对写入的数据签名,在客户端读取时通过签名验证数据是否有被篡改。

第一种方式可以保证敏感数据写入客户端的安全性,但同时也增加了数据还原的性能开销,同时加密后的数据体积也会成倍的增加。

第二种方式不能保存敏感信息,但能够检查数据是否有被篡改,也就是这个数据是否是客户端自己公布出去的,优点在于检查速度快,签名后组成的数据和原数据相比,没有显著的体积增加。

两种方式可以根据业务数据的特性自由选择,不过第二种方式适合处理大多数业务场景下的各类数据,下面来介绍实现方案。

思路

可以采用类似于HTTP接口参数签名的方式,将写入的数据全部转换成一串符串,并附加密钥后MD5签名,最后组合原字符串和签名的方式写入到客户端存储。

1
2
3
base64UrlEncode(
data + '|' + MD5(data + secret)
)

客户端获取之前写入的数据,base64解码后通过 | 分隔数据和签名字段,然后重新按照上面的逻辑对数据签名,对比客户端此时生成的签名与分隔线右边部分的数据,如果一致则表示数据自写入后到目前为止没有经过篡改。

实现

按照上面的思路可以很简单的写一个工具类实现这个业务,任何语言完成它都是轻松的。下面介绍一下市面上的开源方案,这和上面的介绍的解决思路是类似的,都旨在实现同一目标,不过它的协议更丰富一些。

开源解决方案

JSON Web Token(JWT)是一个开放式标准(RFC 7519),它定义了一个紧凑且自包含的方式,用于在各方之间以JSON对象安全传输信息。这些信息可以通过数字签名进行验证和信任。可以使用秘密(使用HMAC算法)或使用RSA的公钥/私钥对对JWT进行签名。

什么是JSON Web Token结构?

在紧凑的形式中,JSON Web Tokens包含三个由点 . 分隔的部分,它们是:

  • Header
  • Pyaload
  • Signature

因此,JWT通常看起来如下所示。

1
xxxxx.yyyyy.zzzzz

分别介绍这三部分

1. Header

通常由两部分组成:Token的类型,即JWT和正在使用的散列算法,如 HMAC SHA256RSA

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

然后这个JSON被Base64Url编码,形成JWT的第一部分。

2. Payload

令牌的第二部分是包含声明的有效负载。声明是关于实体和其他元数据的声明。有三种类型的声明: registered、public和private声明。

  • registered:这些是一组预先定义的声明,这些声明不是强制性的,但建议提供一组有用的,可互操作的声明。其中一些是: iss(发行者), exp(到期时间), sub(主题), aud(受众)等。
    请注意,声明名称只有三个字符长,因为JWT旨在紧凑。

  • public:这些可以由使用JWT的人员随意定义。但为避免冲突,应在IANA JSON Web令牌注册表中定义它们,或将其定义为包含防冲突命名空间的URI。

  • private:这些声明是为了同意使用它们并且既未注册也未公开声明的各方共享信息而创建的。* * * 一个pyaload可以是:

    1
    2
    3
    4
    5
    {
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true
    }

    然后将有效载荷Base64Url进行编码以形成JSON Web令牌的第二部分。

请注意,对于已签名的token尽管受到防篡改保护,但任何人都可以查看。除非加密,否则不要将秘密信息放在JWT中。

3. Signature

例如,如果你想使用HMAC SHA256算法,签名将按照以下方式创建:

1
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

该签名用于验证消息在一路上没有改变,并且在使用私钥签名的令牌的情况下,它还可以验证JWT是哪个客户端写入的。

组合

输出是三个由点分隔的Base64-URL字符串,可以在HTML和HTTP环境中轻松传递,而与基于XML的标准(如SAML)相比,它更加紧凑。

以下显示了一个JWT,它具有先前的标头和有效负载编码,并且使用秘密进行签名。

小结

在客户端写入数据到本地时采用这些安全手段是必要的,不管是PC应用还是移动端应用,不管写在Cookie中还是浏览器本地缓存中。因为这些保存在用户端的数据能够轻易的被修改,客户端如何依赖这些数据来完成自身业务时,一定要确认来源,也就是说必须是自己曾经写入的,否则将给整个系统造成非常大的安全漏洞。