JWT
背景
在 B/S 系统中,登录功基本都是依靠 Cookie 来实现的,用户登录成功之后主要需要客户端和服务端完成以下两项工作:
(1)服务端将登录状态记录到 Session 中,或者签发Token;
(2)客户端利用Cookie保存于服务端对应的 Session ID 或 Token。之后每次请求都会带上Cookie信息(包含Session ID或者Token),当服务端收到请求后,通过验证 Cookie 中的信息来判断用户是否登录 。
单点登录: 单点登录(Single Sign On, SSO)是指在同一帐号平台下的多个应用系统中,用户只需登录一次,即可访问所有相互信任的应用系统。单点登录的本质就是在多个应用系统*享登录状态。 SSO是目前比较流行的企业业务整合的解决方案之一。
如果用户的登录状态是记录在 Session 中的,要实现共享登录状态,就要先共享 Session,比如可以将 Session 序列化到 Redis 中,让多个应用系统共享同一个 Redis,直接读取 Redis 来获取 Session。当然仅此是不够的,因为不同的应用系统有着不同的域名,尽管 Session 共享了,但是由于 Session ID 是往往保存在浏览器 Cookie 中的,因此存在作用域的限制,无法跨域名传递,也就是说当用户在 app1.com 中登录后,Session ID 仅在浏览器访问 app1.com 时才会自动在请求头中携带,而当浏览器访问 app2.com 时,Session ID 是不会被带过去的。实现单点登录的关键在于,如何让 Session ID(或 Token)在多个域*享。
目前而言,主要有以下3种方式:
(1)父域 Cookie:例如:baike.baidu.com、wenku.baidu.com、zhida.baidu.com可以将认证的cookie放入baidu.com这个父级域名中,从而实现登录信息的共享。此种实现方式比较简单,但不支持跨主域名。
(2)认证中心:我们可以部署一个认证中心,认证中心就是一个专门负责处理登录请求的独立的 Web 服务。用户统一在认证中心进行登录,登录成功后,认证中心记录用户的登录状态,并将 Token 写入 Cookie。(注意这个 Cookie 是认证中心的,应用系统是访问不到的。)应用系统检查当前请求有没有 Token,如果没有,说明用户在当前系统中尚未登录,那么就将页面跳转至认证中心。由于这个操作会将认证中心的 Cookie 自动带过去,因此,认证中心能够根据 Cookie 知道用户是否已经登录过了。如果认证中心发现用户尚未登录,则返回登录页面,等待用户登录,如果发现用户已经登录过了,就不会让用户再次登录了,而是会跳转回目标 URL ,并在跳转前生成一个 Token,拼接在目标 URL 的后面,回传给目标应用系统。
应用系统拿到 Token 之后,还需要向认证中心确认下 Token 的合法性,防止用户伪造。确认无误后,应用系统记录用户的登录状态,并将 Token 写入 Cookie,然后给本次访问放行。(注意这个 Cookie 是当前应用系统的,其他应用系统是访问不到的。)当用户再次访问当前应用系统时,就会自动带上这个 Token,应用系统验证 Token 发现用户已登录,于是就不会有认证中心什么事了。
(3)LocalStorage 跨域:在前后端分离的情况下,完全可以不使用 Cookie,我们可以选择将 Session ID (或 Token )保存到浏览器的 LocalStorage 中,让前端在每次向后端发送请求时,主动将 LocalStorage 的数据传递给服务端。这些都是由前端来控制的,后端需要做的仅仅是在用户登录成功后,将 Session ID (或 Token )放在响应体中传递给前端。在这样的场景下,单点登录完全可以在前端实现。前端拿到 Session ID (或 Token )后,除了将它写入自己的 LocalStorage 中之外,还可以通过特殊手段将它写入多个其他域下的 LocalStorage 中。
JWT概念说明
从分布式认证流程中,起最关键作用的就是token,token的安全与否,直接关系到系统的健壮性,这里我们选择使用JWT来实现token的生成和校验。JWT,全称JSON Web Token,是一款出色的分布式身份校验方案。可以生成token,也可以解析检验token。
JWT生成的token由三部分组成:
该对象为一个很长的字符串,字符之间通过"."分隔符分为三个子串。 每一个子串表示了一个功能块,总共有以下三个部分:JWT 头、有效载荷和签名。
头部:JWT 头部分是一个描述 JWT 元数据的 JSON 对象,主要设置一些规范信息,签名部分的编码格式就在头部中声明。
载荷:token中存放有效信息的部分,是 JWT 的主体内容部分,是一个 JSON 对象,包含需要传递的数据。比如用户名,用户角色,过期时间等,但是不要放密码,会泄露!
签名:将头部与载荷分别采用base64编码后,用“.”相连,再加入盐,最后使用头部声明的编码类型进行编码,就得到了签名。通过指定的算法生成哈希,以确保数据不会被篡改。 首先,需要指定一个密码(secret)。该密码仅仅为保存在服务器中,并且不能向用户公 开。然后,使用标头中指定的签名算法(默认情况下为 HMAC SHA256)根据以下公式生成签名。 HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(claims), secret) 在计算出签名哈希后,JWT 头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个 JWT 对象。