前言:在 Web 应用中,用户的认证和鉴权是非常重要的一环。HTTP 是一个无状态的协议,一次请求结束后,下次在发送服务器就不知道这个请求是谁发来的了(同一个 IP 不代表同一个用户),这样就无法确定你的本次请求和上次请求是不是你发送的。如果要进行类似论坛登陆相关的操作,就实现不了。

Http会话保持是Web开发中常用的技术,用来跟踪用户的整个会话。以前Web网站早期常用的会话保持技术是Session和Cookie机制,现在的Web网站和移动端可以通用Token机制。

一、基于 Session 和 Cookie的会话管理机制 

在 Web 应用发展的初期,大部分采用基于 Session 和 Cookie 的会话管理方式,逻辑如下。

当用户第一次通过浏览器使用用户名和密码访问服务器时,服务器会验证用户数据,验证成功后在服务器端写入session数据,向客户端浏览器返回sessionid,浏览器将sessionid保存在cookie中,当用户再次访问服务器时,浏览器会携带sessionid,服务器会拿着sessionid从数据库获取session数据,然后进行用户信息查询,查询到,就会将查询到的用户信息返回,从而实现状态保持。

· 浏览器使用用户名密码进行认证

· 服务端生成并存储 Session,将 SessionID 通过 Cookie 返回给浏览器

·浏览器访问需要认证的接口时在 Cookie 中携带 SessionID

· 服务端通过 SessionID 查找 Session 并进行鉴权,返回给客户端浏览器需要的数据

图片

默认的,当我们关闭浏览器的时候,cookie会被删除。但可以通过修改cookie 的expire time使cookie在一定时间内有效; Java程序中session都有一个默认的过期时间,其中tomcat中的默认时间为30分钟,根据需要我们可以去手动设置session的过期时间。

基于 Session 的方式存在多种弊端:

  • 服务端需要存储 Session,并且由于 Session 需要经常快速查找,通常存储在内存或内存数据库中,同时在线用户较多时需要占用大量的服务器资源。

  • 当需要扩展时,创建 Session 的服务器可能不是验证 Session 的服务器,所以还需要将所有 Session 单独存储并共享。

  • 由于客户端使用 Cookie 存储 SessionID,在Cookie跨域场景下需要进行兼容性处理,同时这种方式也难以防范 CSRF 攻击。

  • Session保持会话依赖于Cookie,而移动端App没有Cookie,这种机制不通用

1、服务器压力增大

通常session是存储在内存中的,每个用户通过认证之后都会将session数据保存在服务器的内存中,而当用户量增大时,服务器的压力增大。

2、扩展性不强,Session共享麻烦

如果将来搭建了多个服务器,虽然每个服务器都执行的是同样的业务逻辑,但是session数据是保存在内存中的(不是共享的),用户第一次访问的是服务器1,当用户再次请求时可能访问的是另外一台服务器2,服务器2获取不到session信息,就判定用户没有登陆。

3、CSRF跨站伪造请求攻击

session是基于cookie进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

4、Cookie的不可跨域名性

Cookie具有不可跨域名性。根据Cookie规范,浏览器访问Google只会携带GoogleCookie,而不会携带BaiduCookie。浏览器判断一个网站是否能操作另一个网站Cookie的依据是域名。

5、不适应于移动端App

Session保持会话依赖于Cookie,而移动端App没有Cookie,这种机制不通用

二、基于 Token 的会话管理机制 

鉴于基于 Session 的会话管理方式存在上述多个缺点,无状态的基于 Token 的会话管理方式诞生了,所谓无状态,就是服务端不再存储信息,甚至是不再存储 Session,逻辑如下。

· 客户端使用用户名密码进行认证

· 服务端验证用户名密码,通过后生成 Token 返回给客户端,用js把它存储起来,比如放在 Cookie 里或者 Local Storage 里

· 客户端保存 Token,访问需要认证的接口时在 URL 参数或 HTTP Header 中加入 Token

· 服务端通过解码 Token 进行鉴权,返回给客户端需要的数据(没有或者 Token 过期,返回401状态码;如果验证成功,就向前端返回请求的数据)

· 客户端得到 401 状态码,重定向到登录页面;得到json数据就进行UI展示

图片

基于 Token 的会话管理方式有效解决了基于 Session 的会话管理方式带来的弊端:

· 服务端不需要存储和用户鉴权有关的信息,鉴权信息会被加密到 Token 中,服务端只需要读取 Token 中包含的鉴权信息即可

· 避免了共享 Session 导致的不易扩展问题

· 不需要依赖 Cookie,有效避免 Cookie 带来的 CSRF 攻击问题

· 使用 CORS 可以快速解决跨域问题

· 更适用于移动端App

Token和Session的区别?

token和session其实都是为了身份验证,session一般翻译为会话,而token更多的时候是翻译为令牌;
session服务器会保存一份,可能保存到内存,缓存,数据库;同样,session和token都是有过期时间一说,都需要去管理过期时间;
其实token与session的问题是一种时间与空间的博弈问题,session是空间换时间,而token是时间换空间。两者的选择要看具体情况而定。

虽然确实都是“客户端记录,每次访问携带”,但 token 很容易设计为自包含的比如常见的JWT(一种基于 token 的认证方案),也就是说,后端不需要记录什么东西,每次一个无状态请求,每次解密验证,每次当场得出合法 /非法的结论。这一切判断依据,除了固化在 CS 两端的一些逻辑之外,整个信息是自包含的。这才是真正的无状态。 
而 sessionid ,一般都是一段随机字符串,需要到后端去检索 id 的有效性。万一服务器重启导致内存里的 session 没了呢?万一 redis 服务器挂了呢? 

方案 A :我发给你一张身份证,但只是一张写着身份证号码的纸片。你每次来办事,我去后台查一下你的 id 是不是有效。 
方案 B :我发给你一张加密的身份证,以后你只要出示这张卡片,我就知道你一定是自己人。 
就这么个差别。

三、Token认证机制相对于Session和Cookie机制的好处

1. 支持跨域访问: Cookie是不允许跨域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输。

2.无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.

3.更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可.

4.去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可.

5.更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等)时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。

6. CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。

7.性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算 的Token验证和解析要费时得多.

8.不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要为登录页面做特殊处理.

9.基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft).

四、 JWT 介绍 

JWT 是 JSON Web Token 的缩写,JWT 本身没有定义任何技术实现,它只是定义了一种基于 Token 的会话管理的规则,涵盖 Token 需要包含的标准内容和 Token 的生成过程。

一个 JWT Token 长这样。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDQ1MTE3NDMsImp0aSI6IjYxYmVmNjkyLTE4M2ItNGYxYy1hZjE1LWUwMDM0MTczNzkxOSJ9.CZzB2-JI1oPRFxNMaoFz9-9cKGTYVXkOC2INMoEYNNA

仔细辨别会发现它由 A.B.C 三部分组成,这三部分依次是头部(Header)、负载(Payload)、签名(Signature),头部和负载以 JSON 形式存在,这就是 JWT 中的 JSON,三部分的内容都分别单独经过了 Base64URL编码,以 . 拼接成一个 JWT Token。

JWT 的 Header 中存储了所使用的加密算法和 Token 类型。

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

Payload 是负载,JWT 规范规定了一些字段,并推荐使用,开发者也可以自己指定字段和内容,例如下面的内容。

{  username: 'yage',  email: 'sa@simpleapples.com',  role: 'user',  exp: 1544602234}

需要注意的是,Payload的内容只经过了 Base64 编码,对客户端来说当于明文存储,所以不要放置敏感信息。

Signature 部分用来验证 JWT Token 是否被篡改,所以这部分会使用一个 Secret 将前两部分加密,逻辑如下。

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

- JWT 优势 & 问题 - 

JWT 拥有基于 Token 的会话管理方式所拥有的一切优势,不依赖 Cookie,使得其可以防止 CSRF 攻击,也能在禁用 Cookie 的浏览器环境中正常运行。

而 JWT 的最大优势是服务端不再需要存储 Session,使得服务端认证鉴权业务可以方便扩展,避免存储 Session 所需要引入的 Redis 等组件,降低了系统架构复杂度。但这也是 JWT 最大的劣势,由于有效期存储在 Token 中,JWT Token 一旦签发,就会在有效期内一直可用,无法在服务端废止,当用户进行登出操作,只能依赖客户端删除掉本地存储的 JWT Token,如果需要禁用用户,单纯使用 JWT 就无法做到了。

- 基于 JWT 的实践 -

既然 JWT 依然存在诸多问题,甚至无法满足一些业务上的需求,但是我们依然可以基于 JWT 在实践中进行一些改进,来形成一个折中的方案,毕竟,在用户会话管理场景下,没有银弹。

前面讲的 Token,都是 Access Token,也就是访问资源接口时所需要的 Token,还有另外一种 Token,Refresh Token,通常情况下,Refresh Token 的有效期会比较长,而 Access Token 的有效期比较短,当 Access Token 由于过期而失效时,使用 Refresh Token 就可以获取到新的 Access Token,如果 Refresh Token 也失效了,用户就只能重新登录了。

在 JWT 的实践中,引入 Refresh Token,将会话管理流程改进如下。

· 客户端使用用户名密码进行认证

· 服务端生成有效时间较短的 Access Token(例如 10 分钟),和有效时间较长的 Refresh Token(例如 7 天)

· 客户端访问需要认证的接口时,携带 Access Token

· 如果 Access Token 没有过期,服务端鉴权后返回给客户端需要的数据

· 如果携带 Access Token 访问需要认证的接口时鉴权失败(例如返回 401 错误),则客户端使用 Refresh Token 向刷新接口申请新的 Access Token

· 如果 Refresh Token 没有过期,服务端向客户端下发新的 Access Token

· 客户端使用新的 Access Token 访问需要认证的接口

图片

将生成的 Refresh Token 以及过期时间存储在服务端的数据库中,由于 Refresh Token 不会在客户端请求业务接口时验证,只有在申请新的 Access Token 时才会验证,所以将 Refresh Token 存储在数据库中,不会对业务接口的响应时间造成影响,也不需要像 Session 一样一直保持在内存中以应对大量的请求。

上述的架构,提供了服务端禁用用户 Token 的方式,当用户需要登出或禁用用户时,只需要将服务端的 Refresh Token 禁用或删除,用户就会在 Access Token 过期后,由于无法获取到新的 Access Token 而再也无法访问需要认证的接口。这样的方式虽然会有一定的窗口期(取决于 Access Token 的失效时间),但是结合用户登出时客户端删除 Access Token 的操作,基本上可以适应常规情况下对用户认证鉴权的精度要求。

- 总结 -

JWT 的使用,提高了开发者开发用户认证鉴权功能的效率,降低了系统架构复杂度,避免了大量的数据库和缓存查询,降低了业务接口的响应延迟。然而 JWT 的这些优点也增加了 Token 管理上的难度,通过引入 Refresh Token,既能继续使用 JWT 所带来的优势,又能使得 Token 管理的精度符合业务的需求。

 

参考链接:

WEB后台--基于Token的WEB后台登录认证机制

基于 JWT + Refresh Token 的用户认证实践

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐