认证与授权之Cookie、Session、Token、JWT

2,036

Spring Security系列文章

  • 认证与授权之Cookie、Session、Token、JWT

认证与授权

认证

进入移动互联网时代,大家每天都在刷手机,常用的软件有微信、支付宝、头条等,下边拿微信来举例子说明认证相关的基本概念,在初次使用微信前需要注册成为微信用户,然后输入账号和密码即可登录微信,输入账号和密码登录微信的操作就是认证(此处说法可能不太严谨,登录操作除了认证可能也会涉及到授权,如果所有资源都对外开放,那么就没有授权一说)。

系统为什么要认证?

认证是为了保护系统的隐私数据与资源,用户的身份合法方可访问该系统的资源

认证(authentication) :用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份信息,身份合法方可继续访问,不合法则拒绝访问。常见的用户身份认证方式有:

  • 用户名密码登录,最为常见
  • 二维码登录
  • 手机短信登录
  • 指纹认证等方式。

为了确认用户的身份,防止请求伪造,在安全要求高的场合,经常会使用组合认证(也叫多因素认证),也就是同时使用多个认证方式对用户的身份进行校验。

授权

还拿微信来举例子,微信登录成功后用户即可使用微信的功能,比如,发红包、发朋友圈、添加好友等,没有绑定银行卡的用户是无法发送红包的,绑定银行卡的用户才可以发红包,发红包功能、发朋友圈功能都是微信的资源即功能资源,用户拥有发红包功能的权限才可以正常使用发送红包功能,拥有发朋友圈功能的权限才可以使用发朋友圈功能,这个根据用户的权限来控制用户使用资源的过程是鉴权,授权和鉴权是两个上下游相匹配的关系,先授权,后鉴权。

为什么要授权?

认证是为了保证用户身份的合法性,授权则是为了更细粒度的对隐私数据进行划分,授权是在认证通过后发生的,控制不同的用户能够访问不同的资源。

授权(authorization): 授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。

在互联网应用开发中,主要通过下面几种方式实现授权:

  • 通过 session 机制,一个访问会话保持着用户的授权信息
  • 通过 cookie 机制,一个网站的 cookie 保持着用户的授权信息
  • 颁发授权令牌(token),一个合法有效的令牌中保持着用户的授权信息

授权实现方式

cookie

HTTP 是一种不保存状态,即无状态(stateless)的协议(对于事务处理没有记忆能力,每次客户端和服务端会话完成时,服务端不会保存任何会话信息)。每个请求都是完全独立的,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次的发送者是不是同一个人。所以服务器与浏览器为了进行会话跟踪(知道是谁在访问我),就必须主动的去维护一个状态,这个状态用于告知服务端前后两个请求是否来自同一浏览器。

那么如何保存用户状态呢?就需要依赖于 session 和 cookie。

cookie 是浏览器存储在本地的数据文件,对于用户而言是具体存在的。一般是从服务端接收到,然后保存在本地,当再次请求对应的网站,会带上该域名下的 cookie。

由于cookie是存在客户端上的,所以浏览器加入了一些限制确保 cookie 不会被恶意使用。cookie 限制大小一般是4K,保证不会占据太多磁盘空间。另外出于对隐私安全的考虑,Cookie 设计为不可跨域名。即 www.google.com颁发的 Cookie 不会被提交到域名www.baidu.com,即使提交过去也不可用。

cookie 的特点:

  • cookie 存储在客户端。cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。
  • **cookie 是不可跨域的:**每个 cookie 都会绑定单一的域名,无法在别的域名下获取使用,一级域名和二级域名之间是允许共享使用的靠的是 domain)

cookie 重要属性如下图所示,如果想要更深入地了解这些属性及使用,推荐阅读这篇文章

cookie 重要属性

cookie 工作流程示意图:

Cookie工作流程图

1、浏览器向服务器发送请求;

2、服务器响应请求,向浏览器设置 cookie;

3、浏览器将 cookie 存在本地,下一次请求带上该 cookie;

4、服务器响应请求。

session

用户打开一个浏览器, 点击多个超链接, 访问服务器一个或者多个 web 资源, 然后关闭浏览器, 整个过程称之为一个会话。

用户认证通过后,服务器根据用户提交的信息进行鉴权,鉴权成功后创建 session,并保存在会话中。会话就是系统为了保持当前用户的登录状态所提供的机制。

因为 cookie 保存在客户端,有被篡改、盗取的风险。而 Session 的出现正是为了解决这个问题。

session 存储在服务器端,一般是文件中,也可以存在数据库(比如是使用内存数据库 redis 保存)或缓存中。理论上 session 可以存储任何数据,但并不建议任何数据都保存在 session 中。session 通常用来保存与特定用户信息相关的信息,如:身份信息、登陆状态、用户的个性配置、权限列表、其他的一些通用数据(比如购物车)。

同一个客户端每次和服务端交互时,不需要每次都传回所有的 Cookie 值,而是只要传回一个会话标识(SessionId),这个ID是客户端第一次访问服务器的时候生成的,而且每个客户端是唯一的。这样每个客户端就有一个唯一的ID,客户端只要传回这个ID就行了,这个ID通常是 NAME 为 JSESIONID 的一个Cookie。在Web服务器上,各个会话独立存储保存不同会话的信息。如果客户端禁用了 cookie,还可以通过 url 重写等方法传递 sessionId

session 工作流程示意图:

session 工作流程示意图

1、首次请求时,服务端接收客户端传来的用户名和密码等信息,进行登录认证,服务器根据用户提交的信息进行鉴权,鉴权成功后创建 session 对象,并将 sessionId 塞入 cookie 中,浏览器收到响应信息将 cookie 存入本地;

2、再次请求时,浏览器自动将当前域名下的 cookie 信息发送给服务端,服务端解析 cookie,获取到 sessionId 后再查找对应的 session 对象,如果 session 对象存在说明用户已经登录,进而可以继续操作。

3、当用户退出系统或 session 过期销毁时,客户端的 session_id 也就无效了。

从上面的流程可知,sessionId 是 cookie 和 session 中间的一道桥梁。

总结:

  1. session 由服务端产生;
  2. 以字典的形式存储,session 保存状态信息,sessionid 返回给客户端保存至本地;
  3. 服务端需要一定的空间存储 session,且一般为了提高响应速度,都是存储在内存中;
  4. sessionid 会自动由浏览器带上。

cookie与session区别

Cookie 和 Session 都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。

1、Cookie 一般用来保存用户信息。 比如①我们在 Cookie 中保存已经登录过的用户信息,下次访问网站的时候,页面可以自动帮你把登录的一些基本信息给填了;②一般的网站都会有保持登录的操作,也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);③登录一次网站后访问网站其他页面不需要重新登录。

Session 的主要作用就是通过服务端记录用户的状态。 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。

2、安全性:Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。

Cookie 存储在客户端中,而Session存储在服务器上,相对来说 Session 安全性更高。如果要在 Cookie 中存储一些敏感信息,不要直接写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。

3、存取值的类型不同:Cookie 只支持存字符串数据,想要设置其他类型的数据,需要将其转换成字符串,Session 可以存任意数据类型。

4、有效期不同: Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭(默认情况下)或者 Session 超时都会失效。

5、存储大小不同: 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie,但是当访问量过多,会占用过多的服务器资源。

token

Oauth2.0 引入了 Access Token 和 refresh token 机制,在官方文档中有所介绍,另外有人在某些地方还看到 ID Tokens 这个概念(ID Tokens 是 OpenID Connect (OIDC)中的关键概念。OIDC 是建立在 OAuth 2.0 之上的简单身份层,可提供身份验证和身份断言),并不在本文讨论范围内,关于 Access Token 与 ID Token 的区别,可以参考 Auth0 官方文档是如何定义的,或者阅读这篇文章

OAuth 2.0是一种标准化的授权协议,Auth0是一家销售身份管理平台的公司,该平台具有实现OAuth2 协议(等等)的身份验证和授权服务。

Access Token

token 是访问资源接口(API)时所需要的资源凭证,是服务端生成的一串字符串,以作客户端进行请求的一个令牌。

Access token 是在 Oauth2.0 协议中,客户端访问资源服务器的令牌(其实就是一段全局唯一的随机字符串)。拥有这个令牌代表着得到用户的授权。记录如下信息:哪个用户——在什么时候——授权给哪个客户端——去做什么事情。

token 特点:

  • 服务端无状态化、可扩展性好
  • 支持移动端设备
  • 安全
  • 支持跨程序调用

token 工作流程示意图:

  +--------+                                           +---------------+
  |        |--(A)------- Authorization Grant --------->|               |
  |        |                                           |               |
  |        |<-(B)----------- Access Token -------------|               |
  |        |               & Refresh Token             |               |
  |        |                                           | Authorization |
  | Client |                            +----------+   |     Server    |
  |        |--(C)---- Access Token ---->|          |   |               |
  |        |                            |          |   |               |
  |        |<-(D)- Protected Resource --| Resource |   | 							 |
  | 			 |                            |  Server  |   |     			     |
  |        |                            +----------+   |               |
  +--------+              										         +---------------+

上图中 Authorization Server 翻译为授权服务,负责 Token 的签发。Resource Server 翻译为资源服务,也就是被授权访问的资源,比如 API 接口。在分布式应用中,他们应该分属不同的服务。 值得注意的是,资源服务器不签发 Token,但是可以具备独立验证 Access Token 的能力。

详细步骤如下所示:

  • (A) 客户端向授权服务器请求Access Token(整个认证授权的流程,可以是多次请求完成该步骤)
  • (B) 授权服务器验证客户端身份无误,且请求的资源是合理的,则颁发Access Token 和 Refresh Token,可以同时返回Access Token的过期时间等附加属性。
  • (C) 带着Access Token请求资源
  • (D) 资源服务器验证Access Token有效则返回请求的内容。

使用须知:

  • 服务器在首次处理用户名和密码时会生成 access token,考虑到安全性,它有一定的有效期,过期后需要依赖 refresh token 来刷新 access token
  • 每次请求都需要携带 access token,需要把 access token 放到 HTTP 的 Header 里
  • token 完全由应用管理,所以它可以避开同源策略

那么问题来了:

资源服务如何脱离授权服务验证Access Token?

以 JTW 为例。如果 Access Token 是 JWT 形式签发,资源服务可以使用验证签名的方式判断是否合法,只需要把签名密钥在资源服务同步一份即可。也有使用非对称加密的,授权服务使用私钥签发,资源服务使用公钥验证。由于JWT允许携带一些信息,用户,权限,有效期等,因此资源服务判断JWT合法之后可以继续根据携带信息来判断是否可访问资源。仅此而已,这样的好处是可以快速验证有效性,坏处是Access Token一旦签发,将很难收回,只能通过过期来失效。

Refresh Token

Oauth2.0 引入了 refresh token 机制。当鉴权服务器发送 access token 给使用方时,同时也发送一个 refresh token。 这个 refresh token 的有效期很长,作用是可以用来刷新 access token。

如果没有 refresh token,也可以刷新 access token,但每次刷新都要用户输入登录用户名与密码,会很麻烦。有了 refresh token,可以减少这个麻烦,客户端直接用 refresh token 去更新 access token,无需用户进行额外的操作。

refresh token 工作流程示意图:

  +--------+                                           +---------------+
  |        |--(A)------- Authorization Grant --------->|               |
  |        |                                           |               |
  |        |<-(B)----------- Access Token -------------|               |
  |        |               & Refresh Token             |               |
  |        |                                           |               |
  |        |                            +----------+   |               |
  |        |--(C)---- Access Token ---->|          |   |               |
  |        |                            |          |   |               |
  |        |<-(D)- Protected Resource --| Resource |   | Authorization |
  | Client |                            |  Server  |   |     Server    |
  |        |--(E)---- Access Token ---->|          |   |               |
  |        |                            |          |   |               |
  |        |<-(F)- Invalid Token Error -|          |   |               |
  |        |                            +----------+   |               |
  |        |                                           |               |
  |        |--(G)----------- Refresh Token ----------->|               |
  |        |                                           |               |
  |        |<-(H)----------- Access Token -------------|               |
  +--------+           & Optional Refresh Token        +---------------+
               Figure 2: Refreshing an Expired Access Token

相较于 Access Token 的流程多了下面这些步骤:

  • (E) 注意: 上面的(C)(D)步骤可以反复进行,直到Access Token过期。 如果客户端在请求之前就能判断Access Token已过期或临近过期(下发过期时间),就可以直接跳到步骤(G)。否则,就会再请求一次,也就产生了本步骤。
  • (F) 当Access Token无效的时候,资源服务器会拒绝响应资源并返回Token无效的错误。
  • (G) 客户端重新向授权服务器请求Access Token,但是这次只需带着Refresh Token即可,而不需要用户再执行认证和授权的流程。这样就可以做到用户无感。
  • (H) 授权服务器验证Refresh Token,如果有效,则签发新的Access Token(或者同时下发一个新的Refresh Token)。

前面说 access token 可能会泄漏,于是设置较短的有效期,可是现在又同时给一个 refresh token,那 refresh token 是怎么保证安全的呢? 

RFC6749第10节中有说明:授权服务器可以向 Web 应用程序客户端和本机应用程序客户端发出刷新令牌。刷新令牌必须在传输和存储过程中保密,并且仅在授权服务器和向其颁发刷新令牌的客户端之间共享。授权服务器必须维护 Refresh Token 和发出它的客户端之间的绑定

每当可以验证客户端身份时,授权服务器必须验证 Refresh Token 和客户端身份之间的绑定。当客户端身份验证不可行时,授权服务器应该部署其他方法来检测 Refresh Token 滥用。

退一步讲,如果攻击者模拟了客户端可以执行刷新请求,那么就要看谁先刷。由于授权服务可以设置Refresh Token一次有效,因此不管哪个先刷新,另一个人刷新就会报错。如果用户先刷新,攻击者以Access Token和Refresh Token的双重失效结束游戏。如果攻击者先刷新了,合法用户就会收到报错信息,授权服务会引导用户从上图的步骤(A)重新开始认证,从而把有效的Refresh Token拿回到合法用户这里。

小结

Access Token 应该维持在较短有效期,过长不安全,过短也会影响用户体验,因为频繁去刷新带来没有必要的网络请求。Access Token 有效期要短于 Refresh Token 的有效期。

Refresh Token 的有效期就是允许用户在多久时间内不用重新登录的时间,可以很长,视业务而定。我们在使用某些 APP 的时候,即使一个月没有打开过也是登录状态的,这就是 Refresh Token 决定的。授权服务在接到 Refresh Token 的时候还要进一步做客户端的验证,尽可能排除盗用的情况。

所有 token 应该保管在 private 的地方,也就是只能客户端自己使用,所有 token 都应该在TLS信道下发送(比如HTTPS)。

token和session区别

token 和 session 功能相似,那为什么会有 token 呢?普通 cookie 和 session 机制的问题:

  1. 服务器需要记录每个用户的状态信息,内存开销大;
  2. 多机 session 问题,扩展性差;
  3. CORS(跨域资源共享):当我们需要让数据跨多台移动设备上使用时,跨域资源共享难。
  4. CSRF(跨站请求伪造):用户在访问银行网站时,它们很容易受到跨站请求伪造的攻击,并且能够被利用其访问其他的网站。

token 相较于 session 的优势:

1、支持跨域访问。Cookie 是不允许垮域访问的。

2、无状态(也称:服务端可扩展行)。

Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息
只需要在客户端的cookie或本地介质存储状态信息.
基于这种无状态和不存储Session信息,负载负载均衡器能够将用户信息从一个服务传到其他服务器上。

3、更适用于移动应用。

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

4、安全性

请求中发送token而不再是发送cookie能够防止CSRF(跨站请求伪造)。
举例:小明登录了某网上银行,不小心点进一个钓鱼链接,该链接会拿着 session Id 来发出请求,攻击者就可以通过让用户误点攻击链接,达到攻击效果。但是我们使用 token 的话就不会存在这个问题,即使有个你点击了非法链接发送了请求到服务端,这个非法请求是不会携带 token 的,所以这个请求将是非法的。

JWT

JWT 即:Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准(RFC 7519)。是目前最流行的跨域认证解决方案。

JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息。

JWT 的结构可以是 JWS 或 JWE,我们常见的 JWT 格式都属于是 JWS。

jwt的组成

  • JWT token的格式:header.payload.signature

  • header中用于存放签名的生成算法

    {
      "alg": "RS256",
      "typ": "JWT"
    }
    
  • payload中用于存放用户名、token的生成时间和过期时间

    {"sub":"admin","created":1489079981393,"exp":1489684781}
    
  • signature 为以 header 和 payload 生成的签名,一旦 header 和 payload 被篡改,验证将失败

    //secret为加密算法的密钥
    String signature = HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
    

示例:

Header:
{
  "alg": "HS256",
  "typ": "JWT"
}
Claims:
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}
Signature:
base64UrlEncode(Header) + "." + base64UrlEncode(Claims)

jwt 实例

这是一个 JWT 的字符串

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

可以在该网站上获得解析结果:jwt.io/

jwt解析结果

参考文献

Access Token 与 Refresh Token

Access Token & Refresh Token 详解以及使用原则

认证与授权:一、傻傻分不清之 Cookie、Session、Token、JWT

认证、授权、会话(cookie、session、token)JWT

session和token的关系和区别?