本地存储
使用HTML5可以在本地存储用户的浏览数据:local Storage、session Storage
1.数据存储于自己的浏览器当中
2.设置、读取十分方便,页面刷新数据不丢失
3.容量较大,sessionStorage约5M,localStorage约20M(可是我测试得到我chrome浏览器localStorage才5M)
4.只能存储字符串,可以将对象JSON.stringify() 编码后存储
cookie是网站为了标示用户身份而储存在用户本地终端上的数据;cookie数据始终在同源的http请求中携带(即使不需要),也会在浏览器和服务器间来回传递。
sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存;
5.此外,User Data 是微软专门为IE在系统中开辟的一块存储空间,单个文件限制大小128kb,一个域名下可保存1M
window.sessionStorage
1.生命周期为关闭浏览器窗口
2.同一个窗口下数据可以共享
3.以键值对的形式存储使用
window.sessionStorage.setItem(key, value)
分别对应键和值,设置(添加或更改)
window.sessionStorage.getItem('key')
获取
window.sessionStorage.removeItem('key')
删除
window.sessionStorage.clear()
删除所有数据(慎用),不在Firefox中实现
4.所有现代浏览器在实现存储写入时都使用了同步阻塞方式,因此数据会被立即提交到存储。(除了老版IE)
window.localStorage
1.生命周期永久生效,除非手动删除,不然关闭页面仍然存在
2.多窗口共享(同一浏览器才能共享)
3.以键值对的形式存储使用
window.localStorage.setItem(key, value)
分别对应键和值,设置(添加或更改)(值是以字符串的形式进行存储的)
window.localStorage.getItem('key')
获取
window.localStorage.removeItem('key')
删除
window.localStorage.clear()
删除所有数据(慎用),不在Firefox中实现
Cookie
Cookie是一种在客户端保持HTTP状态信息的技术:http协议是无状态的,但是在实际工作中,一些万维网网站希望能识别用户(给用户推销产品)=>诞生了cookie小饼干(由服务器生成)
cookie是存储在用户主机的文本文件,存储在浏览器中的纯文本,浏览器的安装目录下会专门有一个 cookie 文件夹来存放各个域下设置的cookie
,记录一段时间内某用户的访问记录
一旦浏览器保存了某个Cookie,那么以后每次访问服务器时,都会在HTTP请求头中将这个Cookie回传给服务器,所以我们前端这里几乎无需什么操作,几乎都是后端那边在操作
cookie数据大小不能超过4k
持久化cookie设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭
但是如果cookie不包含到期日期(Expires/Max-Age),则可视为会话cookie,它在浏览器关闭后就消失了
Web服务器怎么给客户端发Cookie的呢?服务器通过在HTTP响应头中增加Set-Cookie:字段,而浏览器则通过HTTP请求头增加Cookie字段回传
工作中识别用户给用户推销产品,用来保存一些不太敏感的数据(但我很纳闷,其实很多用户用cookie来记住密码,不过我看到一个答案就是:chrome是加密的,只是在密码管理里面明文显示而已。它是使用你Windows的登录密码加密的,还是比较安全的。)
tips:一个浏览器针对一个网站最多存 20 个Cookie,浏览器一般只允许存放 300 个Cookie
移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token
Cookie属性
domain
是域名
path
是路径
两者加起来就构成了 URL,domain
和path
一起来限制 cookie 能被哪些 URL 访问。
某cookie的 domain
为“baidu.com”, path
为“/ ”,若请求的URL(URL 可以是js/html/img/css资源请求,但不包括 XHR 请求)的域名是“baidu.com”或其子域如“api.baidu.com”、“dev.api.baidu.com”,且 URL 的路径是“/ ”或子路径“/home”、“/home/login”,则浏览器会将此 cookie 添加到该请求的 cookie 头部中。
domain
和path
2个选项共同决定了cookie
何时被浏览器自动添加到请求头部中发送出去。如果没有设置这两个选项,则会使用默认值
secure
属性
secure
选项用来设置cookie
只在确保安全的请求中才会发送。当请求是HTTPS
或者其他安全协议时,包含 secure
选项的 cookie
才能被发送至服务器。
设置cookie
服务端设置cookie
cookie和session成对配合 + 生成
传 set-cookie
字段,当你要想设置多个 cookie,需要添加同样多的set-Cookie
字段。
- 服务端可以设置cookie 的所有选项:
expires
、domain
、path
、secure
、HttpOnly
客户端设置cookie
document.cookie = "name=Jonh";
document.cookie = "age=12";
document.cookie = "class=111";
但是红宝书推荐使用 encodeURIComponent
对名称和值进行编码
document.cookie = encodeURIComponent("name") + "=" + encodeURIComponent("Jonh");
encodeURIComponent()
函数通过将一个,两个,三个或四个表示字符的UTF-8编码的转义序列替换某些字符的每个实例来编码 URI (对于由两个“代理”字符组成的字符而言,将仅是四个转义序列) 。
// encodes characters such as ?,=,/,&,:
console.log(`?x=${encodeURIComponent('test?')}`);
// expected output: "?x=test%3F"
console.log(`?x=${encodeURIComponent('шеллы')}`);
// expected output: "?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B"
decodeURIComponent()
方法用于解码由 encodeURIComponent
方法或者其它类似方法编码的部分统一资源标识符(URI)
Session
cookie和session都是用来跟踪浏览器用户身份的会话方式,通常使用cookie方式存储session ID到客户端
如果服务端直接是把用户信息放在cookie,并且每一次从客户端传过来使用的话,如果客户端偷偷更改数据,比如用户名,其实不是乱套了?所以服务端 不能 直接拿cookie来保存用户信息,然后直接来使用(也就是nodejs通过 res.cookie
使用 ),所以存session id最稳妥
现在大多都是Session + Cookie,但是只用session不用cookie,或是只用cookie,不用session在理论上都可以保持会话状态。可是实际中因为多种原因,一般不会单独使用
保持状态:cookie保存在浏览器端,session保存在服务器端
存储的大小:cookie:单个cookie保存的数据不能超过4kb;session大小没有限制。
安全性:cookie:针对cookie所存在的攻击:Cookie欺骗,Cookie截获;session的安全性大于cookie。
应用
cookie判断用户是否登陆过网站,以便下次登录时能够实现自动登录(或者记住密码);保存上次登录的时间等信息。
Session用于保存每个用户的专用信息,变量的值保存在服务器端,服务端会从请求传回来的 Cookie 中获取 SessionID,通过SessionID来区分不同的客户。
(1)网上商城中的购物车
(2)保存用户登录信息
(3)将某些数据放入session中,供同一用户的不同页面使用
(4)防止用户非法登录
cookie - session认证流程
1、用户向服务器发送用户名和密码。
2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
3、服务器向用户返回一个 session_id,写入 Cookie,(有时为了安全,分为多个cookie)传给客户端。
4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。
session缺点: Session保存的东西越多,就越占用服务器内存,对于用户在线人数较多的网站,服务器的内存压力会比较大。
session持久化
由于session是存储在服务器的内存中,如果服务器突然崩了,或者服务器关闭,那岂不是“七天免登录”存储功能失效了?
此时服务端不能单单保存在服务器内存里,还要存到数据库中
session弊端
当前登陆账号成功之后,别人只要拿到你的cookie(可以通过chrome插件一键导出cookie),然后别人就可以直接伪造你的身份直接拿cookie登录,所以在公共电脑,离开之后必须记得点击退出当前帐号,此时服务器就会销毁当前session
Token
上述展示了基于服务器验证的session(sessionID)暴露了一些弊端,当然使用session还有可能引起:跨域共享问题、CSRF攻击问题和可拓展性问题。
基于 Token 的身份验证是无状态的,我们不用将用户信息存在服务器或 Session 中。这种概念解决了在服务端存储信息时的许多问题。没有 session 信息意味着你的程序可以根据需要去增减机器,而不用去担心用户是否登录和已经登录到了哪里。
大多的token验证流程如下:
- 用户通过用户名和密码发送请求。
- 程序验证。
- 程序返回一个签名的 token 给客户端(令牌)。
- 客户端储存 token, 并且每次请求都会附带它。
- 服务端验证 token 并返回数据。(服务器端采用filter过滤器校验。校验成功则返回请求数据,校验失败则返回错误码)
token优势
无状态(实际上是来自于
JWT
),无需存储session信息,负载均衡服务器,能够将用户请求传递到任何一台服务器上可拓展,token保存在客户端本地,使用token可以和其他应用共享权限,比如将博客账号和qq账号进行关联,当通过第三方平台登录qq时,我们可以把博客发到qq平台中
安全性,防止CSRF攻击,请求中发送的是token而不是cookie,浏览器会将接收到的token值存储在Local Storage、sessionStorage或者cookie中
根据浏览器策略,通过domain和path传递cookie是浏览器行为(非人为控制),而token的获取需要通过js脚本控制,然后根据同源策略,b网站也只能拿到b网站的Local Storage、sessionStorage或者cookie中
在信任网站的HTML或js中,会向服务器传递参数token,不是通过Cookie传递的,若恶意网站要伪造用户的请求,也必须伪造这个token,否则用户身份验证不通过。
即使token存储再cookie中,同源策略限制了恶意网站不能拿到信任网站的Cookie内容(token的生成需要提取Cookie里面的内容进行生成),只能使用(只能携带,不能拿出来看!!),所以就算是token是存放在Cookie中的,恶意网站也无法提取出Cookie中的token数据进行伪造。也就无法传递正确的token给服务器,进而无法成功伪装成用户了。
移动端上不支持 cookie,而 token 只要客户端能够进行存储就能够使用,因此 token 在移动端上也具有优势。
不过,token有过期时间,超过事件后要重新获取token
Token 的种类
一般来说 token 主要三种:
- 自定义的 token:开发者根据业务逻辑自定义的 token
- JWT:JSON Web Token,定义在 RFC 7519 中的一种 token 规范
- Oauth2.0:定义在 RFC 6750 中的一种授权规范,但这其实并不是一种 token,只是其中也有用到 token
附上登录认证的进化路程参考网址https://www.cnblogs.com/fengzheng/p/8416393.html
(传统cookie-Session -> 改造版cookie-session -> 基于JWT的Token认证 -> OAuth认证)
拦截器定义携带token
本例使用axios带的请求拦截器,拦截请求,并且附带上token
import MYRequest from './request'
import { BASE_URL } from './request/config'
const myRequest = new MYRequest({
baseURL: BASE_URL,
interceptors: {
requesInterceptor: (config) => {
const token = '123'
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`
}
console.log('请求成功拦截', config)
return config
},
requstInterceptorCatch: (err) => {
console.log('请求失败拦截', err)
},
responseInterceptor: (config) => {
// console.log('响应成功拦截', config)
return config
}
}
})
export default myRequest
内部上有:
this.instance.interceptors.request.use(
this.interceptors?.requesInterceptor,
this.interceptors?.requstInterceptorCatch
)
JWT 的组成和优势
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。中间用 .
分隔,例如:xxxxx.yyyyy.zzzzz
头部(Header)
头部用于描述关于该JWT的最基本的信息
{
"typ": "JWT",
"alg": "HS256"
}
说明了这是一个JWT,并且我们所用的签名算法(后面会提到)是HS256算法
载荷(Payload)
载荷中放置了 token
的一些基本信息,以帮助接受它的服务器来理解这个 token
。同时还可以包含一些自定义的信息,用户信息交换。
Payload 部分也是一个 JSON 对象,JWT 规定了7个官方字段,供选用。
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
{
"iss": "John Wu JWT", //签发者
"iat": 1441593502, //签发时间
"exp": 1441594722, //过期时间
"aud": "www.example.com", //受众
"sub": "jrocket@example.com", //主题
"from_user": "B", //自定义私有字段
"target_user": "A" //自定义私有字段
}
通过Node.js的包base64url来得到一个很长的字符串( console.log(base64url(JSON.stringify(header)))
),如下图
签名(Signature)
为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名
签名时需要用到前面编码过的两个字符串,使用 Header
里面指定的签名算法(默认是 HMAC SHA256)
加密算法对于不同的输入(头部和载荷)产生的输出总是不一样的,如果数据被修改,得到的签名总会是不一样的,而且,如果不知道服务器加密的时候使用密钥,得到的签名也会是不一样的。
如果服务器应用对头部和载荷再次以同样方法签名之后发现,自己计算出来的签名和接受到的签名不一样,那么就说明这个Token的内容被别人动过的,我们应该拒绝这个Token,返回一个HTTP 401 Unauthorized响应。
三者组合就形成了一个完整的JWT
建议
- 在
JWT
中,不应该在载荷里面加入任何敏感的数据(比如密码),怀有恶意的第三方通过Base64解码可以获取到JWT
里的信息 - 虽说
JWT
默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。(此时才可以写入秘密数据) - 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
- JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
使用场景
- 完成加好友的操作,还有诸如下订单的操作
- 设计用户认证和授权系统,甚至实现Web应用的单点登录。
JWT和token的不同点
Token:服务端验证客户端发送过来的 Token 时,还需要查询数据库获取用户信息,然后验证 Token 是否有效。
JWT:将 Token 和 Payload 加密后存储于客户端,服务端只需要使用密钥解密进行校验(校验也是 JWT 自己实现的)即可,不需要查询或者减少查询数据库,因为 JWT 自包含了用户信息和加密的数据。
JWT参考文章:http://blog.leapoahead.com/2015/09/06/understanding-jwt/
阮一峰老师的介绍 http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
JWT详解攻略:https://learnku.com/articles/17883?order_by=vote_count&
JWT实战演练
先进行安装
npm i jsonwebtoken
npm i express-jwt
jsonwebtoken
: 用于生成 Token 。它也有解析 Token 的功能
express-jwt
: 用于解析 Token(比 jsonwebtoken
解决方便) , 它把解析之后的数据,存放到 requset.user
中
jsonwebtoken
提供了sign(payload, secretOrPrivateKey, [options, callback])
的方法。
- sign :对应的其实就是 JWT
签名(Signature)
的动作 - payload:对应的是传入的载荷,可以省略官方提供七个字段,额外添加自定义私有字段,比如用户信息userId等等
- secretOrPrivateKey:自定义的密钥,属于敏感信息
- options:可以配置 header 、荷载、指定算法类型(常用的只有
exp(expiresIn)
有效时间 和algorithm
算法类型这两个字段)