1. 基本术语
1.1. 什么是认证(Authentication)
通俗地讲就是验证当前用户的身份,证明你是你自己
互联网中的认证:
- 用户名密码登录
- 邮箱发送登录链接
- 手机号接收验证码
1.2. 什么是授权(Authorization)
用户授予第三方应用访问该用户某些资源的权限
- 你在安装手机应用的时候,APP 会询问是否允许授予权限(访问相册、地理位置等权限)
- 你在访问微信小程序时,当登录时,小程序会询问是否允许授予权限(获取昵称、头像、地区、性别等个人信息)
- 实现授权的方式有:Cookie、Session、token、OAuth
1.3. 什么是凭证(Credentials)
实现认证和授权的前提是需要一种媒介(证书) 来标记访问者的身份
- 通过身份证,我们可以办理手机卡/银行卡/个人贷款/交通出行等等,这就是认证的凭证
- 在互联网应用中,一般网站会有两种模式,游客模式和登录模式。游客模式下,可以正常浏览网站上面的文章,一旦想要点赞/收藏/分享文章,就需要登录或者注册账号。当用户登录成功后,服务器会给该用户使用的浏览器颁发一个令牌(token),这个令牌用来表明你的身份,每次浏览器发送请求时会带上这个令牌,就可以使用游客模式下无法使用的功能。
1.4. 什么是单点登录(Single Sign-On,简称SSO)
单点登录,是指在多个关联的应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统
用户在首次登录时进行身份验证,并在成功后获得一个令牌(通常是加密的)。该令牌然后可以被传递给其他关联的应用程序,以证明用户的身份。其他应用程序会接受并验证令牌,从而允许用户访问应用程序。
1.5. 什么是源、站
一个URL的基本组成部分
- 协议(Protocol):协议指定了访问 Web 资源所使用的通信协议,例如 HTTP 或 HTTPS。协议部分以冒号和两个斜杠开头,如
http://
或https://
- 域名(Domain Name):域名是用于标识一个网站或服务器的名称。它可以是一个主机名(如 “example.com”)或者是一个 IP 地址(如 “192.0.2.1”)。域名部分跟在协议之后,并以斜杠或端口号(如果有的话)结尾。
- 端口号(Port Number):端口号是用于标识网络通信中的特定应用程序或服务的数字。它通常是可选的,如果未指定,默认使用协议的默认端口号(例如 HTTP 默认端口号为 80,HTTPS 默认端口号为 443)。
- 路径(Path):路径表示 Web 服务器上特定资源的位置。它指定了服务器上资源的相对路径或绝对路径。路径部分跟在域名之后,以斜杠开头。
- 查询参数(Query Parameters):查询参数用于向服务器传递附加的数据或参数。它们是由一个问号
?
开头,然后是一系列的键值对key=value
,多个键值对之间使用&
分隔。 - 锚点(Anchor):锚点用于指定页面内的特定位置或锚点。它以井号
#
开头,后面是指向页面内的特定元素的标识符
https://www.example.com:8080/path/to/resource?param1=value1¶m2=value2#section1
什么是源
源 = 协议 + 域名 + 端口
URLhttps://www.example.com:443/foo
,它的源是https://www.example.com:443
什么是站
站 = 域名的限定子集:部分域名+顶级域名
URLhttps://www.example.com:443/foo
,它的站是www.example.com
同源、跨源
协议、域名、端口相同为同源,其他为跨源
同站、跨站
属于子域名下的网站是同站,其他为跨站
1.6. 什么是localStorage
localStorage
是一种在Web浏览器中存储数据的客户端存储机制
2. Cookie
背景:HTTP 是无状态的协议
Cookie是一种在用户计算机上存储数据的技术,可以用于保存用户身份验证信息。Web应用可以将用户身份验证信息保存在Cookie中,然后在用户下次访问时从Cookie中读取这些信息。随着Web应用的不断发展,Cookie技术也得到了不断升级,例如HttpOnly、Secure、SameSite等。
Cookie是服务器发送到用户浏览器并保存在用户本地的一小块数据,它会在浏览器下次向同一服务器(相同域名)再发起请求时被携带并发送到服务器上
Cookie是不可跨域的,每个 Cookie 都会绑定单一的域名,无法在别的域名下获取使用,一级域名和二级域名之间是允许共享使用的(靠的是 domain)
Cookie 重要的属性
属性 | 说明 |
---|---|
name=value | 键值对,设置 Cookie 的名称及相对应的值,都必须是字符串类型 - 如果值为 Unicode 字符,则需要为字符编码(如utf-8)。 - 如果值为二进制数据,则需要使用 BASE64 编码。 |
domain | 指定 Cookie 所属域名,默认是当前域名 |
path | 指定 Cookie 在哪个路径(路由)下生效,默认是 ‘/’。 如果设置为 /abc ,则只有 /abc 下的路由可以访问到该 Cookie |
maxAge | Cookie 失效的时间,单位秒。如果为整数,则该 Cookie 在 maxAge 秒后失效。如果为负数,该 Cookie 为临时 Cookie ,关闭浏览器即失效,浏览器也不会以任何形式保存该 Cookie 。如果为 0,表示删除该 Cookie 。默认为 -1。 - 比 expires 好用。 |
expires | HTTP响应头中的 “Expires” 字段,用于指定客户端缓存的过期时间,在设置的某个时间点后该 Cookie 就会失效。 一般浏览器的 Cookie 都是默认储存的,当关闭浏览器结束这个会话的时候,这个 Cookie 也就会被删除 |
secure | 该 Cookie 是否仅被使用安全协议传输。安全协议有 HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false。 当 secure 值为 true 时,Cookie 在 HTTP 中是无效,在 HTTPS 中才有效。 |
httpOnly | 如果给某个 Cookie 设置了 httpOnly 属性,则无法通过 JS 脚本 读取到该 Cookie 的信息,但还是能通过 Application 中手动修改 Cookie,所以只是在一定程度上可以防止 XSS 攻击,不是绝对的安全 |
使用 cookie 时需要考虑的问题
- 因为存储在客户端,容易被客户端篡改,使用前需要验证合法性
- 不要存储敏感数据,比如用户密码,账户余额
- 使用 httpOnly 在一定程度上提高安全性
- 尽量减少 cookie 的体积,能存储的数据量不能超过 4kb
- 设置正确的 domain 和 path,减少数据传输
- cookie 无法跨域
- 一个浏览器针对一个网站最多存 20 个Cookie,浏览器一般只允许存放 300 个Cookie
- 移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token
3. Session
Session是一种在服务器端存储数据的技术,用于保存用户身份验证信息。Web应用可以在用户登录时为其创建一个Session,当用户访问其他页面时,服务器根据由Cookie携带的Session_id
从对应的Session中读取用户身份验证信息。Session技术的优点是可以避免用户身份验证信息被窃取,但是它需要占用服务器端的资源。
Session 是另一种记录服务器和客户端会话状态的机制
Session 是基于 cookie 实现的,Session 存储在服务器端,SessionId 会被存储到客户端的cookie 中
Session 认证流程
- 用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session
- 服务器的响应中将此 Session 的唯一标识信息 SessionID 返回给浏览器
- 浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名
- 当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作
使用 session 时需要考虑的问题
- 将 session 存储在服务器里面,当用户同时在线量比较多时,这些 session 会占据较多的内存,需要在服务端定期的去清理过期的 session
- 当网站采用集群部署的时候,会遇到多台 web 服务器之间如何做 session 共享的问题。因为 session 是由单个服务器创建的,但是处理用户请求的服务器不一定是那个创建 session 的服务器,那么该服务器就无法拿到之前已经放入到 session 中的登录凭证之类的信息了。
- 当多个应用要共享 session 时,除了以上问题,还会遇到跨域问题,因为不同的应用可能部署的主机不一样,需要在各个应用做好 cookie 跨域的处理。
- sessionId 是存储在 cookie 中的,假如浏览器禁止 cookie 或不支持 cookie 怎么办? 一般会把 sessionId 跟在 url 参数后面即重写 url,所以 session 不一定非得需要靠 cookie 实现
- 移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token
4. Session和Cookie的对比
产生
Session和Cookie都是由服务器生成的,都是用来存储特定的值(键值对应)
SessionID
SessionID是服务器用来识别、操作存储Session值的对象的。在服务器端,Session的存储方式有文件方式、数据库方式,SessionID就是用来识别这个文件的(文件名相关)、识别数据库的某一条记录。SessionID并不是Session值。
存取值的类型
Cookie 只支持存字符串数据,想要设置其他类型的数据,需要将其转换成字符串,Session 可以存任意数据类型。
存储大小
单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie,但是当访问量过多,会占用过多的服务器资源。
生命周期
客户端(浏览器)在发送请求的时候,会自动将存活、可用的Cookie封装在请求头中和请求一起发送
Cookie的生命周期由过期时间和持久性两个因素决定。过期时间是指Cookie在用户计算机上保留的时间,持久性是指Cookie是否在浏览器关闭后仍然保留。如果没有指定过期时间和持久性,则Cookie的生命周期默认为会话级别,即在用户关闭浏览器时自动删除。
Session的生命周期由服务器端的会话管理器控制,一般情况下,Session会在用户登录时创建,并在用户注销或超时后销毁。
作用域
Cookie和Session都是有其作用域的
Cookie的作用域由域名、路径和协议三个因素决定。只有在Cookie的作用域内才能被浏览器发送到服务器。
Session的作用域仅限于服务器端,客户端无法直接访问Session。Web应用可以通过在Cookie中保存Session ID的方式来跨请求管理Session。
安全性
Cookie是存储在客户端的,是可见的,是可以改变的。
Session是存储在服务器端的,是不可见的,是不可改变的。
5. Token
Token是一种短暂的、可撤销的凭证,用于验证用户身份。Web应用可以在用户登录时为其创建一个Token,并在每次请求中将Token传递给服务器,服务器端可以通过验证Token的合法性来验证用户身份。Token技术的优点是可以避免服务器端存储用户身份验证信息,从而减轻服务器端的负担。
可以把Token 放在Cookie里面自动发送,但是这样不能跨域。更好的做法是放在HTTP请求的头信息Authorization字段里面,单独发送
Token 一旦生成无法让其失效,必须等到其过期才行
简单 token 的组成: uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)
token 的身份验证流程
- 客户端输入用户名和密码并请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个 token 并把这个 token 发送给客户端
- 客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage 里
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 token
- 服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据
使用 token 时需要考虑的问题
- 如果你认为用数据库来存储 token 会导致查询时间太长,可以选择放在内存当中。比如 redis 很适合你对 token 查询的需求。
- token 完全由应用管理,所以它可以避开同源策略
- token 可以避免 CSRF 攻击(因为不需要 cookie 了)
- 移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token
6. Token 和 Session 的对比
Session 只提供一种简单的认证,即只要有此 SessionID ,即认为有此 User 的全部权利
Token 提供的是 认证 和 授权 ,认证是针对用户,授权是针对 应用 。其目的是让某 应用 有权利访问某用户的信息
如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token 。如果永远只是自己的网站,自己的 App,用什么就无所谓了。
7. Token 缺点
Token 太长了
Token 是 header, payload 编码后的样式,所以一般要比 SessionId 长很多,很有可能超出 Cookie 的大小限制(Cookie 一般有大小限制的,如 4kb)
不安全
Token 是存在浏览器的,它太长放在 Cookie 里可能导致 Cookie 超限,那就只好放在 local storage 里,这样会造成安全隐患,因为 local storage 这类的本地存储是可以被 JS 直接读取的,另外由上文也提到,Token 一旦生成无法让其失效,必须等到其过期才行,这样的话如果服务端检测到了一个安全威胁,也无法使相关的Token 失效。
所以 token 更适合一次性的命令认证,设置一个比较短的有效期
8. CSRF 攻击
攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过(Cookie 里带来 SessionId 等身份认证的信息),所以被访问的网站会认为是真正的用户操作而去运行
比如用户登录了某银行网站(假设为 http://www.examplebank.com/,并且转账地址为 http://www.examplebank.com/withdraw?amount=1000&transferTo=PayeeName),登录后 Cookie 里会包含登录用户的 Sessionid,攻击者可以在另一个网站上放置如下代码
<img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">
那么如果正常的用户误点了上面这张图片,由于相同域名的请求会自动带上 Cookie,而 Cookie 里带有正常登录用户的 Sessionid,类似上面这样的转账操作在 server 就会成功,会造成极大的安全风险
CSRF 攻击的根本原因在于对于同样域名的每个请求来说,它的 Cookie 都会被自动带上,这个是浏览器的机制决定的
9. JWT
JSON Web Token(简称 JWT)是目前最流行的跨域认证解决方案。是一种认证授权机制
JWT 一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上。
可以使用 HMAC 算法或者是 RSA 的公/私秘钥对 JWT 进行签名。因为数字签名的存在,这些传递的信息是可信的
JWT 认证流程
- 用户输入用户名/密码登录,服务端认证成功后,会返回给客户端一个 JWT
- 客户端将 token 保存到本地(通常使用 localstorage,也可以使用 cookie)
- 当用户希望访问一个受保护的路由或者资源的时候,需要请求头的 Authorization 字段中使用Bearer 模式添加 JWT,其内容看起来是下面这样
Authorization: Bearer <token>
- 服务端的保护路由将会检查请求头 Authorization 中的 JWT 信息,如果合法,则允许用户的行为
- 因为 JWT 是自包含的(内部包含了一些会话信息),因此减少了需要查询数据库的需要
- 因为 JWT 并不使用 Cookie 的,所以你可以使用任何域名提供你的 API 服务而不需要担心跨域资源共享问题(CORS)
- 因为用户的状态不再存储在服务端的内存中,所以这是一种无状态的认证机制
JWT 的使用方式
- 方式一:放在 HTTP 请求头信息的 Authorization 字段里,使用 Bearer 模式添加 JWT
- 方式二:跨域的时候,可以把 JWT 放在 POST 请求的数据体里
- 方式三:通过 URL 传输
http://www.example.com/user?token=xxx
使用 JWT 时需要考虑的问题
- 因为 JWT 并不依赖 Cookie 的,所以你可以使用任何域名提供你的 API 服务而不需要担心跨域资源共享问题(CORS)
- JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
- JWT 不加密的情况下,不能将秘密数据写入 JWT。
- JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
- JWT 最大的优势是服务器不再需要存储 Session,使得服务器认证鉴权业务可以方便扩展。但这也是 JWT 最大的缺点:由于服务器不需要存储 Session 状态,因此使用过程中无法废弃某个 Token 或者更改 Token 的权限。也就是说一旦 JWT 签发了,到期之前就会始终有效,除非服务器部署额外的逻辑。
- JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
- JWT 适合一次性的命令认证,颁发一个有效期极短的 JWT,即使暴露了危险也很小,由于每次操作都会生成新的 JWT,因此也没必要保存 JWT,真正实现无状态。
- 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
10. Token 和 JWT 的区别
相同:
- 都是访问资源的令牌
- 都可以记录用户的信息
- 都是使服务端无状态化
- 都是只有验证成功后,客户端才能访问服务端上受保护的资源
区别:
- Token:服务端验证客户端发送过来的 Token 时,还需要查询数据库获取用户信息,然后验证 Token 是否有效。
- JWT: 将 Token 和 Payload 加密后存储于客户端,服务端只需要使用密钥解密进行校验(校验也是 JWT 自己实现的)即可,不需要查询或者减少查询数据库,因为 JWT 自包含了用户信息和加密的数据。