什么是 Token ?

摘要:Token,我们通常称它为“令牌”,是服务端生成的一串字符串,作为验证用户身份的凭证,主要有以下几个作用:身份认证、信息交换、防止表单重复提交等。

Token

Token,我们通常称它为“令牌”,是服务端生成的一串字符串,作为验证用户身份的凭证,主要有以下几个作用:

  • 身份认证
  • 信息交换
  • 防止表单重复提交

工作流程

织梦tag标签添加自定义seo标题、关键词、描述、缩略图

  • 客户端第一次请求,提交用户名密码等信息进行登录认证;
  • 服务端鉴权成功后生成 Token,将 Token 和用户信息,存储到数据库或 Redis 中;
  • 同时将 Token 返回给客户端,客户端将其存储在 localStorage、sessionStorage 或 Cookie 中;
  • 客户端之后的每一次请求,都要在请求中携带这个 Token(可以放在请求头、请求体、URL 或 Cookie 中);
  • 服务端接收到 Token,从数据库或 Redis 中,查找与之对应的 Token 和用户信息,验证该 Token 是否一致,是否过期;
  • 如未查找到 Token,或 Token 不一致,或用户信息已过期,则展示错误信息,并返回登陆页面;
  • 如果 Token 一致且用户信息未过期,则服务端放行,返回请求数据。

注意:上面文字所描述的流程为传统 Token 身份认证流程,不是我们常说的使用 JWT 标准的 Token 身份认证流程。(JWT 详见下文)

生成方式

Token 的生成方式有以下几种:

  • 使用 uuid 生成 Token;
  • 使用 JWT 标准生成 Token;
  • 其它。

UUID 是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。

通常作为对象或表的唯一标识,根据机器码和时间戳(从 1970 年 1 月 1 日开始到现在)生成 32 位的字符串,格式为(8-8-4-8-4)。

存储方式

Token 的存储方式同样也有以下几种:

  • 存储在数据库
  • Token 信息存储在数据库(关系型数据库),方便管理与多服务器支持,但每次验证用户状态都要走数据库,增加了数据库的负担,不过在用户量不是特别庞大的情况下没问题。

    毕竟大部分用户请求都要经过数据库,顺便验证一下问题不大。

  • 存储在 Redis
  • Redis(非关系型数据库)应该算比较常用的方案,方便管理与多服务器支持,由于 Redis 跑在内存中,验证效率快上很多,但相应地会增加服务器内存开销。如果 Redis 服务器宕机,可能会造成数据丢失。

    当然也可以存储在 Memcache 中,但 Redis 支持的数据类型多。

  • 存储在 Session 和隐藏域
  • 这种 Token 是用来防止表单重复提交和 CSRF 攻击的,会在当前用户的 Session(会话)中存一份, Form 表单的隐藏域中也存一份,用来验证 Token 是否一致。

  • 使用 JSON Web Token(JWT)
  • 使用 JWT 把 Token 信息存储到客户端,服务端只负责签发和验证,从而实现了服务端无状态,方便扩展。

    请求方式

    使用 Token 的请求方式有四种,客户端可以选择一种来实现,但是不能同时使用多种。

  • 请求头
  • 放在 Header 的 Authorization 中,并使用 Bearer 开头:

    GET /resource HTTP/1.1
    Host: www.example.com
    Authorization: Bearer <Token>
  • 请求体
  • 放在 Body 的 access_token 参数中,并且满足以下条件:

    • HTTP 请求头的 Content-Type 设置成 application/x-www-form-urlencoded;
    • Body 参数是 single-part;
    • HTTP 请求方法应该是推荐可以携带 Body 参数的方法,比如 POST,不推荐 GET。
    POST /resource HTTP/1.1
    Host: www.example.com
    Content-Type: application/x-www-form-urlencoded
    
    access_token=<Token>
  • URL
  • 放在 URL 中的 access_token 参数中:

    GET /resource?access_token=<Token>
    Host: www.example.com

    或通过 URL 传输:

    http://www.example.com/user?access_token=<Token>
  • Cookie
  • 放在 Cookie 中,通过请求头传输,不推荐。

    防止表单重复提交

    防止表单重复提交一般采用前后端都限制的方式。

    • 客户端请求表单页,服务端返回时,会先生成一个 subToken,存储在当前用户的 Session(会话)中;
    • 同时把该 subToen 传送给客户端,客户端存储在 Form 表单的隐藏域中;
    • 客户端第一次提交表单,服务端拦截器 Interceptor 拦截该请求,判断 subToken 是否一致;
    • subToken 一致,拦截器 Interceptor 删除存储在 Session 的 subToken,提交表单成功;
    • 当客户端再次提交表单时,由于 Session 中的 subToken 为空,不通过请求,从而实现了防止表单重复提交。

    这种 Token 是用来“防重放”的,它用过一次就失效了。更多说明请浏览 防止表单重复提交的 4 种方法

    总结

    • Token 按状态划分,可分为传统 Token(有状态,数据存储在服务端)和无状态 Token(使用 JWT 标准,数据存储在客户端);
    • Token 按功能划分,主要可分为 Access Token(访问令牌)和 Refresh Token(刷新令牌),此外还有 CSRF Token(防止 CSRF 攻击),subToken(子令牌)等。

    我们常说的 Token,实际上指的是,使用 JWT 标准的访问令牌。

    JSON Web Token

    JSON Web Token(缩写 JWT)是标准化的 Token,是目前最流行的跨域认证解决方案。

    JWT 是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于在各方之间作为 JSON 对象,安全地传输信息。由于此信息是经过数字签名的,因此可以被验证和信任。

    工作流程

    织梦tag标签添加自定义seo标题、关键词、描述、缩略图

    • 客户端第一次请求,提交用户名密码等信息进行登录认证;
    • 服务端鉴权成功后,将登陆凭证等相关用户信息做数字签名,加密(编码)之后得到字符串作为 Token;
    • 服务端不存储该 Token,将其返回给客户端,客户端存储在 localStorage、sessionStorage 或 Cookie 中;
    • 客户端之后的每一次请求,都要在请求中携带这个 Token(可以放在请求头、请求体、URL 或 Cookie 中);
    • 服务端接收到 Token,做解密(解码)和签名认证,查看签名是否一致,用户信息是否过期;
    • 如果签名不一致(数据遭到篡改),或用户信息已过期,则展示错误信息,并返回登陆页面;
    • 如果签名一致且用户信息未过期,则服务端放行,返回请求数据。

    数据签名

    如果我们把所有状态信息都附加在 Token 上,服务端就可以不存储状态信息。只要服务端能确认是自己签发的 Token,而且其信息未被改动过,那就可以认为 Token 有效——“签名”可以作此保证。

    平时常说的签名都存在一方签发,另一方验证的情况,所以要使用非对称加密算法。

    但是在这里,签发和验证都是同一方,所以对称加密算法就能达到要求,而对称算法比非对称算法要快得多(可达数十倍差距)。

    对称加密算法除了加密,还带有还原加密内容的功能,而这一功能在对 Token 签名时并无必要。

    既然不需要解密,摘要(散列)算法就会更快。可以指定密钥(secret)的散列算法,自然是 HMAC。

    上面说了这么多,还需要自己去实现吗?不用!JWT 已经定义了详细的规范,而且有各种语言的若干实现。

    规范

    JWT 规范共有三部分组成:头部(Header)、有效载荷(Payload)和签名(Signature)。

    织梦tag标签添加自定义seo标题、关键词、描述、缩略图

    在 https://jwt.io/ 网站上可以解析一个已知的 JWT:

    织梦tag标签添加自定义seo标题、关键词、描述、缩略图

    • 头部承载两部分信息,令牌类型(这里是 jwt)和签名算法(默认使用 HMAC SHA256),然后用 Base64URL 进行编码;
    • 有效载荷用来存放用户信息,如用户 ID、权限、过期时间等,不过不要存储敏感信息(如:密码),然后用 Base64URL 进行编码;
    • 签名这部分才是 JWT 的精华之处,把头部和数据两段字符串进行哈希加密,哈希加密是需要密码的,这个密码不能透露给用户,也就是加密后的内容是不可逆的。

    更多相关内容请浏览 JSON Web Token 入门教程,这里只做简单概述。

    特点

    • 简洁(Compact):可以通过 URL、POST 参数或 HTTP heander 发送,因为数据量小,传输速度也快;
    • 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库,节省了服务器开销;
    • 标准:因为 Token 是以 JSON 加密的形式传输数据,所以 JWT 是跨语言的,原则上任何 Web 形式都支持;
    • 无状态:不需要在服务器保存 Session 信息,特别适用于分布式微服务。

    总结

    这种基于 Token 的认证方式,相比传统的 Session 认证方式,更节约服务器资源,并且对移动端和分布式更加友好。其优点如下:

    • Token 完全由应用管理,所以它可以避开同源策略,支持跨域访问;
    • Token 可以是无状态的,可以在多个服务间共享,对单点登陆更友好;
    • Token 可以避免 CSRF 攻击。

    无状态

    无状态 Token,即服务端不存储会话信息,由客户端存储,服务端只负责签发和验证。

    我们知道 Session 会话信息是存储在服务端,当我们想要增加服务器来解决负载问题时,Session 里的关键性信息会限制我们的扩展。

    而 Token 在服务端可以是无状态的,不存储会话信息,所以不存在这些限制,服务端易维护可扩展性(可伸缩性)高,适用于分布式/微服务。

    减轻服务端压力

    服务端不存储会话信息,只负责签发和验证,用解析 Token 的计算时间,减轻了服务端压力(存储压力、数据库、内存开销),减少频繁的查询数据库。

    其实解析 JWT Token 也是需要消耗服务器 CPU 的。

    支持多平台跨域

    跨域资源共享(Cross-Origin Resource Sharing,CORS),是一种允许当前域(domain)的资源被其他域的脚本请求访问的机制,通常由于同源策略,浏览器会禁止这种跨域请求。

    Token 由于不依赖 Cookie( Cookie 遵循同源策略)进行传递,所以可以实现多平台跨域访问(跨域资源共享、跨程序调用)。

    只要用户有了一个验证通过的 Token,数据和资源就能够在任何有相同校验规则的服务器(域)上请求到。

    单点登陆友好

    单点登陆简称为 SSO(Single Sign On),是指在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统,是目前比较流行的业务整合方案。

    当 Token 无状态且支持跨域,单点登录就变得容易了。客户端拿到一个有效的 Token,它就可以在任何同一体系的服务上认证通过——只要它们使用同样的密钥和算法来认证 Token 的有效性。

    织梦tag标签添加自定义seo标题、关键词、描述、缩略图

    第三方授权登录

    当用户想让第三方应用程序,访问它们的数据,我们可以使用 oAuth2.0 建立自己的授权页面,提供可选的权限给第三方应用程序。

    例如,用户登录有道云笔记,如果不想单独注册该网站账号,可以选择用 QQ 账号进行授权登录。

    织梦tag标签添加自定义seo标题、关键词、描述、缩略图

    这种登录方式的实现,使用的是 oAuth2.0 的授权登录方式。

    oAuth2.0 是单点登录的实现方式之一,oAuth2.0 规定授权流程,Token 为其中一环的一个信息载体,具体的一种实现方式由 JWT 规定。

    Token 更多的应用场景是第三方授权登录,可以有效减少对第三方站点账号系统的查询。

    支持移动端

    移动端对 Cookie 的支持不是很好,而 Session 需要基于 Cookie 实现。

    如果使用 Token 的话,由于它可以通过请求头或请求体进行传递,不依赖 Cookie,也就不存在此问题,换句话说 Token 天生支持移动平台,可扩展性好。

    适用前后端分离

    Token 可以用于前后端分离,其实对后端来说,没有移动端不移动端的概念,反正你们都是调用接口,我管你是 PC、H5 还是 APP。

    避免 CSRF 攻击

    CSRF(Cross-site request forgery,跨站请求伪造),是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。

    简单地说,是攻击者通过一些技术手段,欺骗用户的浏览器去访问一个自己曾经认证过的网站,并运行一些操作(如发邮件,发消息,请求授权,财产操作等)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。

    织梦tag标签添加自定义seo标题、关键词、描述、缩略图

    CSRF 攻击之所以能够成功,是因为攻击者可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 Cookie 中,因此攻击者可以在不知道这些验证信息的情况下,直接利用用户自己的 Cookie 来通过安全验证。

    • 要抵御 CSRF,关键在于在请求中放入攻击者所不能伪造的信息,并且该信息不存储在 Cookie 之中。
    • 使用 Token 可以避免 CSRF 攻击,因为它可以存储在 localStorage,通过请求头或请求体进行传递,不依赖 Cookie。
    • 但 localStorage 也会被 JS 读取,所以不管是 Cookie 还是 Token,从存储角度来看其实都不安全,都有暴露的风险。

    我们所说的安全更多的是强调传输中的安全,可以用 HTTPS 协议来传输, 这样的话请求头都能被加密,也就保证了传输中的安全。

    Session vs. Token

    其实我们把 Cookie 和 Token 比较本身并不合理,它们的维度不同,一个是存储方式,一个是验证方式,正确的比较应该是 Session 和 Token。

    SessionToken
    使用场景浏览器(SessionID)。浏览器、H5、APP、小程序、自定义(只要能发送 HTTP 请求的都可以)。
    存储方式会话信息存储在服务端(内存、文件、数据库),SessionID 存储在客户端 Cookie。会话信息存储在客户端,也可以存储在服务端(传统 Token)。
    传递方式Cookie(标准 Header)、URL。Cookie、HTTP Header、自定义 Header、URL。
    过期时间默认 20 分钟,或指定时间。指定时间。
    扩展性Cookie 是本地存储,不是很安全,别人可以分析存放在本地的 Cookie 并进行欺骗。
    特点Cookie 是客户端存储会话信息的一种机制,用来记录用户的一些信息,也是实现 Session 的一种方式。Session 是在服务端存储的一个数据结构,用来跟踪用户的状态,这个数据可以保存在内存、文件、数据库中
    总结1. Cookie 遵循同源策略,不能跨域访问,除非特别部署。
    2. SessionID 是客户端的唯一标识,存储在 Cookie 中,它是维持一个会话的核心。
    3. Cookie 和 Session 都是为了在无状态的 HTTP 协议之上维护会话状态,使得服务端可以知道当前是和哪个客户在“打交道”。

    Session 和 Token 本质上都是对用户身份的认证机制,只是他们实现的校验机制不一样。

    • 一个保存在 server,通过在 Redis 等中间件获取来校验,
    • 一个保存在 client,通过签名校验的方式来校验),

    多数场景上使用 Session 会更合理,但如果在单点登录,一次性命令认证上使用 Token 会更合适,最好在不同的业务场景中合理选型,才能达到事半功倍的效果。

    版权声明:本文为博主原创文章,未经博主允许不得转载。http://www.dedenotes.com/html/token.html
    (1)
    打赏 微信扫一扫 微信 支付宝 QQ 扫码打赏

    meta

    Dedenotes 赞(3)

    meta 是 html 语言 head 区的一个辅助性标签,位于文档的头部,不包含任何内容,标签的属性定义了与文档相关联的名称/值对。meta 标签可提供相关页面的元信息(meta-information),比如针对搜索引擎和更新频度的描述和关键词。

    防止表单重复提交的 4 种方法

    Dedenotes 赞(3)

    平时开发的项目中可能会出现下面这些情况:由于用户误操作,多次点击表单提交按钮;由于网速等原因造成页面卡顿,用户重复刷新提交页面;黑客或恶意用户使用 Postman 等工具重复恶意提交表单(攻击网站)。

    HTTP消息结构 HTTP请求报文和响应报文的格式

    Dedenotes 赞(3)

    HTTP 协议(HyperText Transfer Protocol,超文本传输协议)是因特网上应用最为广泛的一种网络传输协议,基于 TCP/IP 通信协议来传递数据(HTML 文件, 图片文件, 查询结果等),所有的 WWW(World Wide Web)文件都必须遵守这个标准。