最后更新于
2020-04-12
最近在复习相关内容,打算从头的整理一下 Cookie
、Session
、Token
与 JWT
相关内容,彻底弄清它们的含义以及它们之间的区别,主要内容包括以下这些
- 认证(
Authentication
) - 授权(
Authorization
) - 凭证(
Credentials
) - 什么是
Cookie
- 什么是
Session
- 什么是
Token
- 什么是
JWT
Cookie
和Session
的区别Token
和Session
的区别Token
和JWT
的区别- 常见的加密算法
- 常见问题
下面就让我们一个一个来进行了解,先从几个基本概念开始看起
认证(Authentication)
关于认证,通俗地讲就是验证当前用户的身份,证明你是你自己(比如你每天上下班打卡,当你的指纹和系统里录入的指纹相匹配时,就打卡成功),而互联网中的认证则包括
- 用户名密码登录
- 邮箱发送登录链接
- 手机号接收验证码
- 只要你能收到邮箱/验证码,就默认你是账号的主人
授权(Authorization)
关于授权,简单来说就是用户授予第三方应用访问该用户某些资源的权限,比如你在安装手机应用的时候,App
会询问是否允许授予权限(访问相册、地理位置等权限),又或者你在访问微信小程序时,在登录的时候,小程序会询问是否允许授予权限(获取昵称、头像、地区、性别等个人信息等),实现授权的方式一般有 Cookie、Session、Token、OAuth
凭证(Credentials)
实现认证和授权的前提是需要一种媒介(证书) 来标记访问者的身份,在现实生活中,每个人都会有一张专属的居民身份证,是用于证明持有人身份的一种法定证件,通过身份证,我们可以办理手机卡/银行卡/个人贷款/交通出行等等,这就是认证的凭证
而在互联网应用中,一般网站会有两种模式,游客模式和登录模式,在游客模式下,你可以正常浏览网站上面的文章,一旦想要实现某些交互操作等(比如评论),就需要登录或者注册账号,当用户登录成功后,服务器会给该用户使用的浏览器颁发一个令牌(Token
),这个令牌用来表明你的身份,每次浏览器发送请求时会带上这个令牌,就可以使用游客模式下无法使用的功能
什么是 Cookie
在看完了几个基本概念以后,我们就正式的来了解一下什么是 Cookie
,众所周知,HTTP
是一个无状态协议,所以客户端每次发出请求时,下一次请求无法得知上一次请求所包含的状态数据,那么我们如何能把一个用户的状态和数据关联起来呢?
比如在某个页面中,你进行了登录操作,而当你跳转到商品页时,服务端如何知道你是已经登录的状态呢?所以在这种情况下就产生了 Cookie
这门技术来解决这个问题,Cookie
是 HTTP
协议的一部分,它的处理分为如下几步
- 客户端发送
HTTP
请求到服务器 - 当服务器收到
HTTP
请求时,在响应头里面添加一个Set-Cookie
字段 - 浏览器收到响应后保存下
Cookie
- 之后对该服务器每一次请求中都通过
Cookie
字段将Cookie
信息发送给服务器
这里有几个需要注意的地方,首先 Cookie
是存储在客户端的,其次 Cookie
是不可跨域的,每个 Cookie
都会绑定单一的域名,无法在别的域名下获取使用,但是一级域名和二级域名之间是允许共享使用的(依靠的是 domain
)
其次 Cookie
主要用于以下三个方面
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
- 个性化设置(如用户自定义设置、主题等)
- 浏览器行为跟踪(如跟踪分析用户行为等)
下面我们来看看一些 Cookie
当中比较重要的参数
Name/Value
键值对,设置 Cookie
的名称及相对应的值,都必须是字符串类型(如果值为 Unicode
字符,需要为字符编码,如果值为二进制数据,则需要使用 BASE64
编码),所以我们在用 JavaScript
操作 Cookie
的时候需要注意对 Value
进行编码处理
Expires
Expires
用于设置 Cookie
的过期时间,比如
1 | Set-Cookie: id=b7fGcj3fWa; Expires=Wed, 21 Oct 2017 07:28:00 GMT; |
当 Expires
属性缺省时,表示是会话性 Cookie
,当为会话性 Cookie
的时候,值保存在客户端内存中,并在用户关闭浏览器时失效,需要注意的是,有些浏览器提供了会话恢复功能,这种情况下即使关闭了浏览器,会话期 Cookie
也会被保留下来,就好像浏览器从来没有关闭一样
与会话性 Cookie
相对的是持久性 Cookie
,持久性 Cookie
会保存在用户的硬盘中,直至过期或者清除 Cookie
,这里值得注意的是,设定的日期和时间只与客户端相关,而不是服务端
Max-Age
Max-Age
用于设置在 Cookie
失效之前需要经过的秒数,比如
1 | Set-Cookie: id=b7fGcj3fWa; Max-Age=604800 |
Max-Age
可以为正数、负数、甚至是 0
,具体区别如下
- 如果
max-Age
属性为正数时,浏览器会将其持久化,即写到对应的Cookie
文件中 - 当
max-Age
属性为负数,则表示该Cookie
只是一个会话性Cookie
,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie
- 当
max-Age
为0
时,则会立即删除这个Cookie
假如 Expires
和 Max-Age
都存在,则 Max-Age
优先级更高
Domain
Domain
指定了 Cookie
可以送达的主机名,假如没有指定,那么默认值为当前文档访问地址中的主机部分(但是不包含子域名),比如某个站点首页设置的 Domain
是 .test.com
,这样无论是 a.test.com
还是 b.test.com
都可以使用 Cookie
但是这里需要注意的是,不能跨域设置 Cookie
,比如 a
域名下的页面把 Domain
设置成 b
是无效的
1 | Set-Cookie: qwerty=219ffwef9w0f; Domain=b.com; Path=/; Expires=Wed, 30 Aug 2017 00:00:00 GMT |
Path
Path
指定了一个 URL
路径,这个路径必须出现在要请求的资源的路径中才可以发送 Cookie
首部,比如设置 Path=/docs
,/docs/Web/
下的资源会带 Cookie
首部,/test
则不会携带 Cookie
首部
Domain
和 Path
标识共同定义了 Cookie
的作用域,即 Cookie
应该发送给哪些 URL
Secure
标记为 Secure
的 Cookie
只应通过被 HTTPS
协议加密过的请求发送给服务端,默认为 false
,当值为 true
时,Cookie
在 HTTP
中是无效,在 HTTPS
中才有效
使用 HTTPS
安全协议,可以保护 Cookie
在浏览器和 Web
服务器间的传输过程中不被窃取和篡改
HTTPOnly
如果给某个 Cookie
设置了 httpOnly
属性,则无法通过 JavaScript
脚本读取到该 Cookie
的信息,但还是能通过 Application
中手动修改 Cookie
,所以只是在一定程度上可以防止 XSS
攻击,不是绝对的安全
SameSite
这里我们重点来看看这个属性,因为在二月份发布的 Chrome 80
的版本中已经默认屏蔽掉了第三方的 Cookie
,这样一来就导致了许多问题,我们先来看看这个属性的作用,其实简单来说就是『SameSite
属性可以让 Cookie
在跨站请求时不会被发送,从而可以阻止跨站请求伪造攻击(CSRF
)』
SameSite
可以有下面三种值
Strict
,仅允许一方请求携带Cookie
,即浏览器将只发送相同站点请求的Cookie
,即当前网页URL
与请求目标URL
完全一致Lax
,允许部分第三方请求携带Cookie
None
,无论是否跨站都会发送Cookie
之前默认是 None
的,而 Chrome 80
后默认是 Lax
跨域和跨站
首先要理解的一点就是『跨站』和『跨域』是不同的,同站(same-site
)与跨站(cross-site
)和第一方(first-party
)与第三方(third-party
)是等价的,但是与浏览器同源策略(SOP
)中的同源(same-origin
)与跨域(cross-origin
)是完全不同的概念
同源策略作为浏览器的安全基石,其『同源』判断是比较严格的,相对而言,Cookie
中的『同站』判断就比较宽松,只要两个 URL
的 eTLD + 1
相同即可,不需要考虑协议和端口,其中 eTLD
表示有效顶级域名,注册于 Mozilla
维护的公共后缀列表(Public Suffix List
)中,例如 .com
、.co.uk
、.github.io
等,其中的 eTLD + 1
则表示,有效顶级域名加上二级域名,例如 test.com
等
举几个例子来说的话就是 www.taobao.com
和 www.baidu.com
是跨站,www.a.taobao.com
和 www.b.taobao.com
是同站,a.github.io
和 b.github.io
是跨站(注意是跨站)
改变
接下来我们来看下从 None
改成 Lax
到底影响了哪些地方的 Cookies
的发送?见下表
请求类型 | 实例 | 以前 | Strict | Lax | None |
---|---|---|---|---|---|
链接 | <a href=""></a> |
发送 Cookie |
不发送 | 发送 Cookie |
发送 Cookie |
预加载 | <link rel="prerender" href="" /> |
发送 Cookie |
不发送 | 发送 Cookie |
发送 Cookie |
GET 表单 |
<form method="GET" action=""> |
发送 Cookie |
不发送 | 发送 Cookie |
发送 Cookie |
POST 表单 |
<form method="POST" action=""> |
发送 Cookie |
不发送 | 不发送 | 发送 Cookie |
iframe |
<iframe src=""></iframe> |
发送 Cookie |
不发送 | 不发送 | 发送 Cookie |
Ajax |
$.get() |
发送 Cookie |
不发送 | 不发送 | 发送 Cookie |
Image |
<img src="" /> |
发送 Cookie |
不发送 | 不发送 | 发送 Cookie |
从上图可以看出,对大部分 Web
应用而言,POST
表单,iframe
,Ajax
,Image
这四种情况从以前的跨站会发送三方 Cookie
,变成了不发送,主要原因如下
iframe
嵌入的Web
应用有很多是跨站的,都会受到影响Ajax
可能会影响部分前端取值的行为和结果Image
一般都存放在CDN
上,大部分情况不需要Cookie
,故影响有限,但如果引用了需要鉴权的图片,可能会受到影响
问题
我们再看看会出现什么的问题?举几个例子
- 通过接口获取的登录信息,由于
Cookie
丢失,用户无法登录,页面还会误判断成是由于用户开启了浏览器的禁止第三方Cookie
功能导致而给与错误的提示 - 一些站点上使用
iframe
嵌入的部分,没有了Cookie
,都会受到影响 - 一些埋点系统会把用户
id
信息埋到Cookie
中,用于日志上报,这种系统一般走的都是单独的域名,与业务域名分开,所以也会受到影响 - 一些用于防止恶意请求的系统,对判断为恶意请求的访问会弹出验证码让用户进行安全验证,通过安全验证后会在请求所在域设置一个
Cookie
,请求中带上这个Cookie
之后,短时间内不再弹安全验证码,在Chrome 80
以上如果因为Samesite
的原因请求没办法带上这个Cookie
,则会出现一直弹出验证码进行安全验证 - 某些请求了跨域的接口,因为没有
Cookie
,接口不会返回数据
解决方法
解决方案就是设置 SameSite
为 none
,以 Adobe
网站为例 www.adobe.com/sea/,查看请求可以看到 SameSite
是为 none
的,如下图
这里也有两点我们需要注意的地方
HTTP
接口不支持SameSite=none
如果想加 SameSite=none
属性,那么该 Cookie
就必须同时加上 Secure
属性,表示只有在 HTTPS
协议下该 Cookie
才会被发送
- 需要
UA
检测,部分浏览器不能加SameSite=none
iOS 12
的 Safari
以及老版本的一些 Chrome
会把 SameSite=none
识别成 SameSite=Strict
,所以服务端必须在下发 Set-Cookie
响应头时进行 User-Agent
检测,对这些浏览器不下发 SameSite=none
属性
实例
我们下面来看一个实际的使用示例,即 express
中的 Cookie
是如何使用的,这里需要注意的是 express
在 4.x
版本之后,Session
的管理和 Cookies
等许多模块都不再直接包含在 express
中,而是需要单独添加相应模块,在 express
在 4.x
版本中操作 Cookie
可以使用 cookie-parser 模块
1 | var express = require('express') |
什么是 Session
Cookie
虽然很方便,但是使用 Cookie
有一个很大的弊端,那就是 Cookie
中的所有数据在客户端就可以被修改,数据非常容易被伪造,那么一些重要的数据就不能存放在 Cookie
中了,而且如果 Cookie
中数据字段太多会影响传输效率,为了解决这些问题,就产生了 Session
,Session
是另一种记录服务器和客户端会话状态的机制,Session
是基于 Cookie
实现的,Session
中的数据是保留在服务器端的,流程可以如下图所示
Session
的运作通过一个 SessionId
来进行,SessionId
通常是存放在客户端的 Cookie
中(比如在 express
中,默认是 connect.sid
这个字段),当请求到来时,服务端检查 Cookie
中保存的 SessionId
并通过这个 SessionId
与服务器端的 SessionData
关联起来,进行数据的保存和修改
这意思就是说,当你浏览一个网页时,服务端随机产生一个字符串,然后存在你 Cookie
中的 connect.sid
字段中,当你下次访问时,Cookie
会带有这个字符串,然后浏览器就知道你是上次访问过的某某某,然后从服务器的存储中取出上次记录在你身上的数据,由于字符串是随机产生的,而且位数足够多,所以也不担心有人能够伪造
Session
可以存放在内存、Cookie
本身、Redis
等缓存,又或者可以放置于数据库中,如果是线上环境,缓存的方案比较常见,如果是存在数据库的话,查询效率相比前三者都太低,不太推荐,而 Cookie-Session
有安全性问题,下面我们就来看看几种存储 Session
方式之间的区别以及一些实际使用的示例
在内存中存储 Session
在 express
当中操作 Session
要用到 express-session 这个模块,主要的方法就是 session(options)
,其中 options
中包含可选参数,主要有下面这些
name
,保存Session
的字段名称,默认为connect.sid
store
,Session
的存储方式,默认存放在内存中,也可以使用redis
,mongodb
等,express
生态中都有相应模块的支持secret
,通过设置的secret
字符串,来计算Hash
值并放在Cookie
中,使产生的signedCookie
防篡改Cookie
,设置存放SessionId
的Cookie
的相关选项,默认为(default: { path: '/', HTTPOnly: true, secure: false, maxAge: null }
)genid
,产生一个新的SessionId
时,所使用的函数, 默认使用uid2
这个npm
包rolling
,每个请求都重新设置一个Cookie
,默认为false
resave
,即使Session
没有被修改,也保存Session
值,默认为true
express-session
默认使用内存来存 Session
,对于开发调试来说很方便
1 | var express = require('express') |
在 Redis 中存储 Session
Session
存放在内存中不方便进程间共享,因此可以使用 Redis
等缓存来存储 Session
,假设你的机器是四核的,你使用了四个进程在跑同一个服务,当用户访问进程一时,他被设置了一些数据当做 Session
存在内存中,而下一次访问时,他被负载均衡到了进程二,则此时进程二的内存中没有他的信息,认为他是个新用户,这就会导致用户在服务中的状态不一致
所以在这种情况下我们可以考虑使用 Redis
作为缓存,可以使用 connect-redis 模块来得到 Redis
连接实例,然后在 Session
中设置存储方式为该实例
1 | var express = require('express') |
我们可以运行 redis-cli
查看结果,如图可以看到 Redis
中缓存结果
各种存储的利弊
上面我们说到,Session
的 store
有四个常用选项
- 内存,其实在开发环境存内存就可以,一般的小程序为了省事,如果不涉及状态共享的问题,用内存
Session
也没问题,但内存Session
除了省事之外,没有别的好处 Cookie
,Cookie-Session
我们下面会提到,先说说利弊,用Cookie-Session
的话,是不用担心状态共享问题的,因为Session
的data
不是由服务器来保存,而是保存在用户浏览器端,每次用户访问时,都会主动带上他自己的信息,它的弊端是增大了数据量传输,利端是方便- 缓存,缓存方式是最常为常用用的方式,不仅速度快,而且又能共享状态,相比
Cookie-Session
来说,当SessionData
比较大的时候,可以节省网络传输,也是推荐使用的方式 - 数据库,关于数据库
Session
除非你很熟悉这一块,知道自己要什么,否则还是使用缓存吧
signedCookie
Cookie
虽然很方便,但是使用 Cookie
有一个很大的弊端,即 Cookie
中的所有数据在客户端就可以被修改,数据非常容易被伪造,比如我们现在有一个网站,使用 Cookie
来记录登录的用户凭证,相应的 Cookie
长这样, dotcom_user=zhangsan
,它说明现在的用户是 zhangsan
这个用户,如果我在浏览器中装个插件,把它改成 dotcom_user=lisi
,服务器一读取,就会误认为我是 lisi
,然后我就可以进行 lisi
才能进行的操作了
现在我们有一些数据,不想存在 Session
中,想存在 Cookie
中,怎么保证不被篡改呢?答案很简单,签个名,假设我们的服务器有个秘密字符串,是 this_is_my_secret
,然后我们就可以为用户 Cookie
的 dotcom_user
字段设置了个值 zhangsan
,Cookie
本应是
1 | { 'dotcom_user': 'zhangsan' } |
而如果我们签个名,比如把 dotcom_user
的值跟我们的秘密字符串做个 SHA1
1 | sha1('this_is_my_secret' + 'zhangsan') === '59ea8588929a887ff79757a5f3fe8ae57f5df104' |
然后把 Cookie
变成这样
1 | { |
这样一来,用户就没法伪造信息了,一旦它更改了 Cookie
中的信息,则服务器会发现 Hash
校验的不一致,毕竟他不懂我们的秘密字符串是什么,而暴力破解哈希值的成本太高
Cookie-Session
Cookie-Session
的实现跟 signedCookies
差不多,不过 Cookie-Session
建议不要轻易使用,有受到回放攻击的危险,回放攻击指的是,比如一个用户,它现在有 100
积分,积分存在 Session
中,Session
保存在 Cookie
中,他先复制下现在的这段 Cookie
,然后去发个帖子,扣掉了 20
积分,于是他就只有 80
积分了,而他现在可以将之前复制下的那段 Cookie
再粘贴回去浏览器中,于是服务器在一些场景下会认为他又有了 100
积分
如果避免这种攻击呢?这就需要引入一个第三方的手段来验证 Cookie-Session
,而验证所需的信息,一定不能存在 Cookie
中,这么一来,避免了这种攻击后,使用 Cookie-Session
的好处就荡然无存了,如果为了避免攻击而引入了缓存使用的话,那不如把 Cookie-Session
也一起放进缓存中
什么是 Token
Acesss Token
简单来说就是访问资源接口(API
)时所需要的资源凭证,Token
是由 uid
(用户唯一的身份标识)、time
(当前时间的时间戳)、sign
(签名,Token
的前几位以哈希算法压缩成的一定长度的十六进制字符串)等几部分组成,它的特点是服务端无状态化、可扩展性好,支持移动端设备,相对而言比较安全,又支持跨程序调用,Token
的身份验证流程可以如下图所示
它的流程大致是下面这样的
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个
Token
并把这个Token
发送给客户端 - 客户端收到
Token
以后,会把它存储起来,比如放在Cookie
里或者localStorage
里 - 客户端每次向服务端请求资源的时候需要带着服务端签发的
Token
- 服务端收到请求,然后去验证客户端请求里面带着的
Token
,如果验证成功,就向客户端返回请求的数据
下面是一些使用 Token
的注意事项
- 每一次请求都需要携带
Token
,需要把Token
放到HTTP
的Header
里 - 基于
Token
的用户认证是一种服务端无状态的认证方式,服务端不用存放Token
数据,用解析Token
的计算时间换取Session
的存储空间,从而减轻服务器的压力,减少频繁的查询数据库 Token
完全由应用管理,所以它可以避开同源策略
Token 和 Session 的区别
Session
是一种记录服务器和客户端会话状态的机制,使服务端有状态化,可以记录会话信息,而Token
是令牌,访问资源接口(API
)时所需要的资源凭证,Token
使服务端无状态化,不会存储会话信息Session
和Token
并不矛盾,作为身份认证Token
安全性比Session
好,因为每一个请求都有签名还能防止监听以及重放攻击,而Session
就必须依赖链路层来保障通讯安全了,如果你需要实现有状态的会话,仍然可以增加Session
来在服务器端保存一些状态- 所谓
Session
认证只是简单的把User
信息存储到Session
里,因为SessionId
的不可预测性,暂且认为是安全的,而Token
,如果指的是OAuth Token
或类似的机制的话,提供的是认证和授权,认证是针对用户,授权是针对App
,其目的是让某App
有权利访问某用户的信息,这里的Token
是唯一的,不可以转移到其它App
上,也不可以转到其它用户上,Session
只提供一种简单的认证,即只要有此SessionId
,即认为有此User
的全部权利,是需要严格保密的,这个数据应该只保存在站方,不应该共享给其它网站或者第三方App
,所以简单来说,如果你的用户数据可能需要和第三方共享,或者允许第三方调用API
接口,用Token
,如果永远只是自己的网站,自己的App
,用什么就无所谓了
什么是 JWT
JSON Web Token
(简称 JWT
)是一种认证授权机制,也是目前最流行的跨域认证解决方案,JWT
是为了在网络应用环境间传递声明而执行的一种基于 JSON
的开放标准(RFC 7519
),JWT
的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,比如用在用户登录上,可以使用 HMAC
算法或者是 RSA
的公/私秘钥对 JWT
进行签名,因为数字签名的存在,这些传递的信息是可信的
我们这里只是简单介绍,更为关于
JWT
的内容可以参考 JSON Web Token 入门教程,JWT
的原理是下面这样的
- 用户输入用户名/密码登录,服务端认证成功后,会返回给客户端一个
JWT
- 客户端将
Token
保存到本地(通常使用localStorage
,也可以使用Cookie
) - 当用户希望访问一个受保护的路由或者资源的时候,需要请求头的
Authorization
字段中使用Bearer
模式添加JWT
,其内容看起来是下面这样的
1 | Authorization: Bearer <token> |
一些注意事项
- 服务端的保护路由将会检查请求头
Authorization
中的JWT
信息,如果合法,则允许用户的行为 - 因为
JWT
是自包含的(内部包含了一些会话信息),因此减少了需要查询数据库的需要 - 因为
JWT
并不使用Cookie
的,所以你可以使用任何域名提供你的API
服务而不需要担心跨域资源共享问题(CORS
) - 因为用户的状态不再存储在服务端的内存中,所以这是一种无状态的认证机制
Token 和 JWT 的区别
相同点如下
- 都是访问资源的令牌
- 都可以记录用户的信息
- 都是使服务端无状态化
- 都是只有验证成功后,客户端才能访问服务端上受保护的资源
区别如下
Token
,服务端验证客户端发送过来的Token
时,还需要查询数据库获取用户信息,然后验证Token
是否有效JWT
,将Token
和Payload
加密后存储于客户端,服务端只需要使用密钥解密进行校验(校验也是JWT
自己实现的)即可,不需要查询或者减少查询数据库,因为JWT
自包含了用户信息和加密的数据
常见的加密算法
我们在之前的存储 Session
部分提到了一种加密方式 SHA1
其实是一种哈希算法,下面我们就来看看一些常见的加密算法,见下图
哈希算法(Hash Algorithm
)又称散列算法、散列函数、哈希函数,是一种从任何一种数据中创建小的数字指纹的方法,哈希算法将数据重新打乱混合,重新创建一个哈希值,哈希算法主要用来保障数据真实性(即完整性),即发信人将原始消息和哈希值一起发送,收信人通过相同的哈希函数来校验原始数据是否真实,哈希算法通常有以下几个特点
- 正像快速,原始数据可以快速计算出哈希值
- 逆向困难,通过哈希值基本不可能推导出原始数据
- 输入敏感,原始数据只要有一点变动,得到的哈希值差别很大
- 冲突避免,很难找到不同的原始数据得到相同的哈希值
但是也有一些需要注意的地方
- 上面提到的一些算法不能保证数据被恶意篡改,原始数据和哈希值都可能被恶意篡改,要保证不被篡改可以使用
RSA
公钥私钥方案,再配合哈希值 - 哈希算法主要用来防止计算机传输过程中的错误
常见问题
下面我们来看一些比较常见的问题
使用 Cookie 时需要考虑的问题
- 因为存储在客户端,容易被客户端篡改,使用前需要验证合法性
- 不要存储敏感数据,比如用户密码,账户余额
- 使用
httpOnly
在一定程度上提高安全性 - 尽量减少
Cookie
的体积,能存储的数据量不能超过4kb
- 设置正确的
domain
和path
,减少数据传输 Cookie
无法跨域- 一个浏览器针对一个网站最多存
20
个Cookie
,浏览器一般只允许存放300
个Cookie
使用 Session 时需要考虑的问题
- 将
Session
存储在服务器里面,当用户同时在线量比较多时,这些Session
会占据较多的内存,需要在服务端定期的去清理过期的Session
- 当网站采用集群部署的时候,会遇到多台
Web
服务器之间如何做Session
共享的问题,因为Session
是由单个服务器创建的,但是处理用户请求的服务器不一定是那个创建Session
的服务器,那么该服务器就无法拿到之前已经放入到Session
中的登录凭证之类的信息了 - 当多个应用要共享
Session
时,除了以上问题,还会遇到跨域问题,因为不同的应用可能部署的主机不一样,需要在各个应用做好Cookie
跨域的处理 SessionId
是存储在Cookie
中的,假如浏览器禁止Cookie
或不支持Cookie
怎么办? 一般会把SessionId
跟在URL
参数后面即重写URL
,所以Session
不一定非得需要靠Cookie
实现- 移动端对
Cookie
的支持不是很好,而Session
需要基于Cookie
实现,所以移动端常用的是Token
使用 Token 时需要考虑的问题
- 如果你认为用数据库来存储
Token
会导致查询时间太长,可以选择放在内存当中,比如Redis
很适合你对Token
查询的需求 Token
完全由应用管理,所以它可以避开同源策略Token
可以避免CSRF
攻击(因为不需要Cookie
了)- 移动端对
Cookie
的支持不是很好,而Session
需要基于Cookie
实现,所以移动端常用的是Token
使用 JWT 时需要考虑的问题
- 因为
JWT
并不依赖Cookie
,所以可以使用任何域名提供你的API
服务而不需要担心跨域资源共享问题(CORS
) JWT
默认是不加密,但也是可以加密的,生成原始Token
以后,可以用密钥再加密一次JWT
不仅可以用于认证,也可以用于交换信息,有效使用JWT
,可以降低服务器查询数据库的次数JWT
最大的优势是服务器不再需要存储Session
,使得服务器认证鉴权业务可以方便扩展,但这也是JWT
最大的缺点,由于服务器不需要存储Session
状态,因此使用过程中无法废弃某个Token
或者更改Token
的权限,也就是说一旦JWT
签发了,到期之前就会始终有效,除非服务器部署额外的逻辑JWT
本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限,为了减少盗用,JWT
的有效期应该设置得比较短,对于一些比较重要的权限,使用时应该再次对用户进行认证JWT
适合一次性的命令认证,颁发一个有效期极短的JWT
,即使暴露了危险也很小,由于每次操作都会生成新的JWT
,因此也没必要保存JWT
,真正实现无状态- 为了减少盗用,
JWT
不应该使用HTTP
协议明码传输,要使用HTTPS
协议传输
使用加密算法时需要考虑的问题
- 永远使用哈希算法来处理密码,不要使用
Base64
或其他编码方式来存储密码,这和以明文存储密码是一样的 - 绝不要使用弱哈希或已被破解的哈希算法,像
MD5
或SHA1
,只使用强密码哈希算法 - 绝不要以明文形式显示或发送密码,即使是对密码的所有者也应该这样,如果你需要忘记密码的功能,可以随机生成一个新的一次性的(这点很重要)密码,然后把这个密码发送给用户
只要关闭浏览器 Session 就消失了吗
不会,因为对 Session
来说,除非程序通知服务器删除一个 Session
,否则服务器会一直保留,程序一般都是在用户做退出操作的时候发个指令去删除 Session
,然而浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分 Session
机制都使用会话 Cookie
来保存 SessionId
而关闭浏览器后这个 SessionId
就消失了,再次连接服务器时也就无法找到原来的 Session
,如果服务器设置的 Cookie
被保存在硬盘上,或者使用某种手段改写浏览器发出的 HTTP
请求头,把原来的 SessionId
发送给服务器,则再次打开浏览器仍然能够打开原来的 Session
,恰恰是由于关闭浏览器不会导致 Session
被删除,迫使服务器为 Session
设置了一个失效时间,当距离客户端上一次使用 Session
的时间超过这个失效时间时,服务器就认为客户端已经停止了活动,才会把 Session
删除以节省存储空间