前端学习笔记

前端学习笔记

Cookie

HTTP协议的三大特点
  • 请求应答模式:每个HTTP请求都必然对应一个HTTP响应。
  • 无连接:客户端在发送HTTP请求前需要与服务器建立连接,而在收到服务器的HTTP响应后,就会断开连接,即一次连接只支持一次HTTP请求应答。无连接可以理解为HTTP协议不会持久维持连接。
  • 无状态:HTTP协议本身不支持每次HTTP请求应答的状态的保存,只负责状态的传递。无状态可以理解为HTTP协议无法保存状态。
HTTP协议无状态性的优缺点

HTTP是一种简单(请求报文,响应报文结构简单清晰,无状态性),灵活(请求头,响应头容易扩展),对服务器友好(无连接特性,不会持久占用服务器的连接资源)的协议。

无状态性 保证了HTTP协议的简单性。假设我们需要HTTP协议支持HTTP请求应答的状态的保存,那么HTTP协议至少需要考虑如下问题:

  1. 状态的保存(介质选择内存还是硬盘,位置选择服务器还是浏览器)
  2. 状态的管理(增删改查)
  3. 状态的安全性(防盗,防伪,防乱发)

那么HTTP协议的无状态性的缺点就体现出来了:

在业务上关联的两次HTTP请求应答,比如后一次HTTP请求需要前一次HTTP响应的数据。由于HTTP无状态性,前一次的HTTP响应数据无法被保存,所以后一次HTTP请求也无法获得。对应的业务场景就是身份认证。

这就涉及到下面的Cookie了

Cookie技术的具体工作流程

浏览器与服务器约定,如果某次HTTP响应中的状态需要保存,则

  1. 服务器需要手动将状态数据设置为HTTP响应头Set-Cookie的值

  2. 浏览器在收到服务器的HTTP响应后,会自动解析出HTTP响应头Set-Cookie的值,并按需保存在内存或硬盘中。

  3. 浏览器再次请求服务器时,会自动将之前保存的Set-Cookie值从内存或硬盘中取出,并加入到HTTP请求头Cookie中,发送到服务器

  4. 服务器收到浏览器请求后,需要手动解析HTTP请求头Cookie的值,进行状态校验等操作

我们这里将“状态”简称为cookie。

cookie的组成

cookie可以分为两部分内容,一部分是cookie具体内容,一部分是cookie的描述属性

cookie的具体内容就是一个键值对,表现为 cookie_name=cookie_value形式

而cookie的描述属性,主要用于描述cookie的生命周期,作用范围,以及安全限定,包含如下

描述属性作用
expirescookie失效日期,不设置,表示cookie生命周期是本次会话
max-agecookie存活时间,不设置,表示cookie生命周期是本次会话,当同时存在expires和max-age时,以max-age为准
domaincookie作用域,默认为服务器域名
pathcookie作用路径,默认根路径“/”
httponlycookie仅用于http请求,不可通过javascript获取
secure浏览器只有在加密协议 HTTPS 下,才能将这个 Cookie 发送到服务器。
samesite跨站cookie是否发送 strict、lax、 none

Session

Cookie的安全性

cookie由于保存在浏览器端,即用户个人电脑上,所以非常容易受到各种恶意攻击,而导致用户cookie被盗用·,为此浏览器为cookie设置了一些安全属性来限制对于cookie的操作以及cookie的发送。

  • httponly

  • secure

  • samesite

httponly限制了cookie只能用于HTTP请求时携带,而不能使用javascript操作,如document.cookie操作cookie,这样可以有效保护cookie不被XSS的注入js脚本盗取。

secure限制了cookie只能用于HTTPS请求时携带,这样保护了cookie不会在网络中明文传输,即避免了网络传输被截获后,黑客可以直接得到明文cookie。

samesite默认限制了cookie在大部分HTTP请求中只能用于同站发送,比如浏览器网页请求的:

  1. 服务器接口的IP地址和cookie的domain设置的IP地址相同,则cookie可以发送

  2. 服务器接口的域名和cookie的domain设置的域名相同,则cookie可以发送

  3. 服务器接口的域名是cookie的domain设置的域名的子域名,则cookie可以发送

只有少部分情况时,比如a标签超链接跳转GET请求,form表单GET请求,这种导航跳转式的GET请求才可以跨站发送cookie。

session

Session身份认证技术脱胎于Cookie,它主要是解决Cookie上述两个安全隐患,所以Session工作流程如下:

  1. 浏览器发送登录请求到服务器,服务器基于登录请求中的用户名和密码进行身份认证,认证成功后,将用户名和密码保存在服务器本地内存或硬盘上,并为其创造一个随机唯一串作为查询索引,我们将随机唯一串称为sessionid。

  2. 服务器将sessionid作为响应头中Set-Cookie的值交给浏览器保存

  3. 浏览器再次请求服务器,自动携带保存的cookie(即sessionid)到请求头Cookie中

  4. 服务器受到请求,解析出请求头Cookie值(即sessionid),并基于sessionid到本地内存或硬盘上查询对应的用户名和密码进行身份认证,认证成功,则继续业务处理。

这里我们将保存在服务器端的用户登录状态数据称为session。

可以分析出,浏览器端不再保存实际的登录状态数据,只是保存了一个无业务意义的随机串sessionid,所以Session技术的安全性要比Cookie强。

sessionid的作用是,作为凭据交给浏览器端保存,解决了浏览器端cookie被破解后暴露出明文用户名密码的安全问题。sessionid还有一个作用就是,在服务器端,和session组成键值对,也可以理解为查找session的索引值。

session的实现

Session技术实际上就是将登录状态数据从浏览器端转移到服务器端保存,即需要在服务器端考虑登录状态数据session的管理,具体管理行为可以参照浏览器端cookie的管理:

  • cookie的设置:浏览器自动将服务器HTTP响应set-cookie的值保存到内存或硬盘中,当cookie有效期为会话时,它将被缓存在内存中,当cookie有效期为指定时间时,它将被持久化在硬盘中。

  • cookie的获取:浏览器发送HTTP请求给服务器时,自动添加对应服务器域名domain和资源路径path下的,expires/max-age处于有效的,httpOnly,secure,samesite符合要求的cookie

  • cookie的清除:当cookie有效期到达后,浏览器自动清除它。

所以服务器端也需要对session考虑以上管理行为

  • session保存在哪?

  • session如何获取?

  • session何时清除?

服务器端既可以将session缓存在内存中,也可以将session持久化在硬盘中。但是缓存在内存中的session的生命周期不一定是会话级别的,持久化在硬盘中的session也可能是会话级别的。

为什么会这样呢?我们需要先了解会话生命周期的概念:

cookie的会话生命周期指的是:从浏览器请求服务器得到cookie开始,到浏览器关闭为止。

会话级别的cookie一定是保存在浏览器内存中的,而会话结束,意味着cookie要失效清除,同时会话结束,也意味着浏览器关闭,浏览器关闭会释放自身内存,此时内存中的cookie也会被清除,这就是会话级别cookie保存在浏览器内存中的原因。

类比可得,保存在服务器上的session会话生命周期指的是:从服务器接收浏览器请求生成session开始,到服务器关闭为止。

而服务器不会轻易关闭,这会导致session的会话生命周期很长,影响安全性。同时服务器也不会为了控制会话时间跨度而关闭自身。session理论上不存在会话生命周期。

但是一般我们可以设定固定时间为一个会话周期,比如20分钟为一次会话周期,20分钟到后,服务器强制清除对应session。

所以服务器端将session生命周期和其保存位置无关。

服务器端在保存session时,会生成一个sessionid,该sessionid是一个随机唯一的字符串,作用是作为在服务器端查询session的索引,和在浏览器端作为免登录的凭证。

所以服务器端获取session靠的是sessionid。

无论是cookie还是session,理论上都应该在其失效时清除,而不同的是清除手段。对于cookie而言,如果存储在内存中,则可以通过关闭浏览器释放内存来间接清除,如果存储在硬盘中,则需要浏览器内部代码逻辑清除。对于session,存储位置与生命周期无关,所以都只能通过代码清除,清除时,需要检查对应的session的有效期。

session和cookie对比

概念

session和cookie都是用户登录状态数据

工作流程

cookie由服务器生成,通过响应头Set-Cookie传递给浏览器,cookie由浏览器保存,每次请求服务器时,浏览器都会通过请求头Cookie将对应的cookie传递给服务器,服务器基于请求头Cookie信息进行身份认证。

session由服务器生成,并且保存在服务器,服务器在生成session的同时会生成与之一一对应的sessionid,sessionid有两个作用,一是在服务器端作为查找session的索引,二是作为登录凭据通过响应头Set-Cookie传递给浏览器,并保存在浏览器端。浏览器每次请求服务器都会通过请求头Cookie将sesionid传递给服务器,服务器通过sessionid查询到服务器端保存的session,并基于session进行身份认证。

安全性

在身份认证实践中,session比cookie更加安全,因为session机制会将用户登录状态数据保存在服务器端,浏览器端只有一个无意义字串sessionid。而cookie机制将用户登录状态数据保存在浏览器端。黑客盗取浏览器保存的cookie,要比盗取服务器保存的session,容易的多。

同时,cookie机制中,服务器无法验证cookie有效期,cookie有效期完全由浏览器管理,同时浏览器具备多种入口篡改cookie有效期,这可能导致cookie有效期被故意篡改导致长期有效。

而session机制中,服务器端可以在生成session时,保持session的有效期和作为cookie的sessionid的有效期一致,并且以服务器端保存的session的有效期为准,这保障了即使cookie:sessionid在浏览器端被篡改的长期有效,但是服务器端session的有效期却无法被篡改。

管理难度

cookie保存在浏览器端,浏览器已经有了一套成熟的管理机制:

保存:自动将服务器HTTP响应头中Set-Cookie信息保存在浏览器内存或硬盘中

获取:自动将保存在浏览器内存或硬盘中的cookie取出作为HTTP请求头Cookie发生给服务器

清除:自动清除内存或硬盘中失效的cookie

而session保存在服务器端,服务器本身没有内置管理session的机制,需要第三方工具或者人为开发管理代码

性能方面

另外cookie是将每个用户的登录状态数据分散保存在用户个人电脑上,对于服务器来说内存友好,性能友好。

session是将每个用户的登录状态数据集中保存在服务器内存或服务器连接的数据库中,对于服务器来说内存不友好,性能也不友好。

跨站请求与cookie禁用时的session
大部分跨站请求时,浏览器是禁止HTTP请求携带目的站点cookie的。

而session是基于cookie工作的,所以发生跨站请求时,session也是无法工作的。

另外浏览器提供了cookie禁用设置渠道,所以一旦用户禁用了cookie,那么session也是无法工作的。

Token

Session认证机制存在的问题
  1. Session是基于Cookie工作的,所以当浏览器禁用cookie,或者发生跨站请求时,Session就无法工作了。

  2. Session如果存储在服务器内存中,则会占用大量服务器内存,并且当项目是分布式部署到多个服务器时,多个服务器内存中session的互相共享与同步成为一个问题。

  3. Session如果存储的数据库中,则可以解决分布式部署多个服务器间session共享和同步问题,但是如果数据库挂了,则所有分布式服务器的session认证都会失败,所以数据库也要集群部署,这意味着一份session需要被复制到多个数据库中。

  4. Session认证机制,本质是基于用户名和密码的认证机制,服务器需要取出session中保存的用户名和密码,去和数据库中的用户名和密码进行对比,对比一致才能认证成功,而这意味着每次session认证都需要进行一次数据库查询。

问题1:sessionid可以选择保存在浏览器的webStorage中,具体操作方案是,服务器将sessionid放在自定义响应头(非Set-Cookie)中,或者放在响应体中,浏览器收到响应后,从自定义响应头或响应体中取出sessionid,保存到浏览器webStorage中。当浏览器再次请求服务器时,需要手动从浏览器webStorage中取出sessionid加入自定义请求头(非Cookie)或请求体中,服务器收到请求后,从自定义请求头或请求体中解析出sessionid。

对于问题3:目前已有成熟的session数据库集群管理方案,但是任需要服务端提供大量硬盘进行session存储

对于问题4,主要需要优化的是:为了频繁重复的验证用户名和密码而去查询数据库,但是这是Session认证机制的安全性保障,无法优化。

可以分析出:Session认证机制会消耗服务端大量硬盘资源,以及会进行频繁重复的数据库查询。

这是Session认证机制的缺点。

Token令牌认证的特点

令牌中包含用户名,“见令牌如主帅亲临”

令牌中不包含密码,“令牌不是主帅本人”

所以服务器拿到令牌无法解析出密码,也就无法基于用户名和密码进行身份认证。

令牌认证,其实就是基于令牌本身进行认证。也就是令牌既要支持表明身份,也要支持防伪和自验证。

Token身份认证的优缺点

优点

服务器无需保存用户登录状态,节省了服务器内存和硬盘资源,以及节省了服务端对于登录状态的管理
服务器端无需基于用户名密码验证,省去了频繁重复的查询数据库的动作
缺点

服务器一旦签发token,则在token有效期时间内,token始终有效,服务器端无法强制让token失效。

这将导致一个问题:即使用户已经退出登录,理论上应该让token失效,但是实际上,服务器端无法让还在有效期内的token失效,token的失效只能等它有效期过了。

原因是:token本质是一段支持自验证的字符串,而自验证的规则已经固化了,服务器端只能按照固化的验证规则对token进行验证,所以一个未失效的token总是可以通过固定规则的验证。

前面代码案例中,用户退出登录的实现都是清除了浏览器端保存的token信息,本质上并不是让token失效,如果用户将token刻意复制下来,然后再导入浏览器中,依旧可以实现免登录。

Token强制失效的方案

1、在服务器端建立黑名单,保存强制失效的token,这样服务器就可以通过查询黑名单来禁止强制失效token正常工作了。

该方案违背了token的初衷,token初衷是实现服务器端无状态保存,现在虽然不保存登录状态,但是却要保存黑名单token,依旧会给集群服务器带来管理压力。

2、减少token有效期的长度,降为20分钟到30分钟,这样可以减少token强制失效后的存活时间,降低影响。

该方案可以就是五十步笑百步的典型,依旧存在很大安全风险。

目前来看,没有太好的让Token强制失效的方案。

Token续签问题

前面让token强制失效,考虑的是token有效时长未用完,用户就退出登录,造成有效token可能被黑客盗用的风险。

但是还有一个实际生活中常见的问题:token有效时长用完了,用户还在继续正常访问网站,这时候会造成一个尴尬的问题,用户并没有退出登录,但是却要重新登录。

所以token续签成为一个需要思考的问题。

Token续签实现方案

服务器给用户发放两个token,分别是access_token和refresh_token。

access_token就是验证用户身份信息的token,它的有效期设置为短时间的,比如20分钟。

refresh_token用于更新access_token,即当access_token失效后,服务器可以验证refresh_token来后生成一个新的access_token。所以refresh_token的有效期一般设置为长时间跨度的,比如半个月。

需要注意的是refresh_token的secret和access_token的secret不能是同一个,否则refresh_token就可以被用来登录了。

跨域xhr/fetch

浏览器的同源策略

同源策略是浏览器特有的一种安全机制,它主要用于限制不同的源之间的数据交互。浏览器没有限制非同源请求的发送,浏览器也没有限制服务器的响应动作,浏览器只是限制了网页接收非同源的响应。

即:服务器的响应被浏览器收到了,但是浏览器没有将非同源响应交给发起的网页。

如何定义同源

同源:即 protocol(协议)、host(主机)、port(端口)相同

我们通过下面的例子来深入理解下同源

浏览器网页 http://106.52.60.253/index.html 请求以下源时,是否会发生CORS error,即判断二者是否同源?

网址说明
https://106.52.60.253/user不同源,协议不同
http://106.52.234.235/user不同源,主机IP不同
http://106.52.60.253:8080/user不同源,端口不同,http协议默认端口80,可以省略不写

另外,我们还需要注意 IP地址和其对应域名之间是否算同源?

什么是跨域

跨域其实就是同源的反义词,跨域可以理解为非同源。

随着前后端分离的发展,前端独立部署,访问非同源的后端服务器,已经是一种普遍现象。

但是由于浏览器同源策略的影响,跨域请求被限制了,所以我们需要一种可以规避同源策略,实现跨域请求的解决方案。

JSONP跨域

script标签的特点

在HTML中,有一些标签也可以发起HTTP请求,比如script标签,link标签,img标签,form标签,且被允许跨域。

  • link,img标签都是单纯的引入资源文件

  • form标签用于收集用户输入并发送,但是发送成功会跳转到新网页,并将服务器响应作为新网页的内容

  • script标签可以引入外部js文件,并执行引入的js文件的代码

其中,script标签由于其可以执行引入的js文件的代码,再加上其跨域特性,让script标签可以用来做一些超出其设计初衷的事。

script标签会发起HTTP GET去请求服务器上的js文件,所以script标签可以用于实现HTTP GET跨域请求。

JSONP实现原理

那么如何使用script标签实现普适的跨域请求呢?

什么叫普适的跨域请求,举个例子,我们可以利用script标签请求上例中http://106.52.60.253/user接口,并获得接口响应。

这个过程,有两个点,一是发送HTTP GET请求http://106.52.60.253/user,这点不难,直接将http://106.52.60.253/user作为script标签src值即可

还有一点,如何获取http://106.52.60.253/user返回值?

我们知道常规的http://106.52.60.253/user接口返回的就是一个文本内容,这个文本内容可能是html字符串,也可能是json字符串,或者其他,但是script标签期望引入的文件内容是js代码,这样就可以执行了。

所以,我们可以将实际文本内容的HTTP响应,封装进js函数入参中,这样script标签引入后,就会自动执行js函数,这样我们就可以收到入参了。

JSONP的缺点

JSONP的缺点也很明显,只能实现HTTP GET请求的跨域,对于其他请求方式无能为力,因为script标签只能发起HTTP GET请求。

另外JSONP要求前后端协作,前端和后端代码开发起来都很别扭,不符合规范的代码要求。

同时由于JSONP实现了跨域,所以很容易被恶意网站利用,因为任何网站都可以通过script标签引入可以跨域的目的源接口,有很大的安全问题,除非后端对请求源做限制。
JSONP的安全问题防护策略

服务器端根据请求头中的Referer来判断请求源,并限制可以跨域的Referer源,设置可信源,如果不在可信源的Referer则响应401,无权操作。

但是其实对于服务器来说,来自前端的请求信息都不可信,无论是origin请求头,还是referer请求头,它们都可能被篡改,所以我们不应该让服务器来做非同源校验。而真正适合非同源校验的还得是浏览器,因为第一手请求是浏览器发出去的,buripsuite这些拦截工具都是吃的浏览器的剩饭。所以对于浏览器来说,请求头中referer,origin都是可信的。

CORS响应头跨域

前面分析了,JSONP跨域并不安全,即使在服务器做了请求源限制,意义也不大,因为服务器目前只能靠请求头中的referer或origin来判断请求源,但是请求头中的referer或origin很容易被篡改。

浏览器就是请求的制造者,浏览器可以拿到第一手的请求源信息,且不存在被篡改的风险,所以跨域请求的源限定适合交给浏览器来做,而服务器只需要告诉浏览器允许哪些源的跨域请求即可。
Access-Control-Allow-Origin

所以,为了实现安全的跨域资源共享,浏览器和服务器之间约定,当服务器接口需要支持跨域请求时,服务器可以在接口的HTTP响应头中包含Access-Control-Allow-Origin字段,该字段的值就是允许跨域请求的源。

浏览器会根据HTTP响应头中的Access-Control-Allow-Origin获取到允许发起跨域请求的源,并对比实际发起请求的源,若相同则浏览器会将响应正常返回给发起者,若不同,则报错CORS error。
Access-Control-Allow-Origin请求头的值有两种情况:

  • Access-Control-Allow-Origin: *
  • Access-Control-Allow-Origin:

当值为*时,表示允许所有源进行跨域请求。当值为特定源时,表示只允许该源进行跨域请求。

但是CORS跨域是有要求的,并不是说非同源服务器接口响应头添加了Access-Control-Allow-Origin就可以了。

跨域请求的分类

浏览器将请求分为了两类, 简单请求 和 非简单请求。

简单请求需要满足如下条件:

  • 请求方式只能是 get、post、head之一

  • 请求头只能包含如下 accept、accept-lanuage、content-lanuage、content-type

  • content-type值只能是 application/x-www-form-urlencoded、multIpart/form-data、text/plain 之一

不满足以上条件的请求就是非简单请求。

所以跨域请求也被分为了两类,简单跨域请求 和 非简单跨域请求。
简单跨域请求

浏览器遇到简单跨域请求时,会直接发送,并且会在请求头中添加一个Origin字段,该字段用于告知服务器当前请求来自于哪个站点,Origin值包含了发起请求的源的协议、主机、端口。

服务器可以根据请求中的Origin头过滤掉一些不可信源的请求,以减轻服务器的资源消耗。而对于可信源的请求,则走正常的服务器业务流程,并在响应头中添加Access-Control-Allow-Origin:可信源,用于告知浏览器该源可以进行跨域请求。

非简单跨域请求

浏览器在遇到非简单请求时(如PUT,DELETE请求,或者content-type:application/json的请求),不会直接发送,而是会先发送一个预检请求。

所谓预检请求preflight,即在发送正式请求之前,先发送一次HTTP OPTIONS请求到服务器,用于预先检测服务器是否支持即将发送的正式请求。
预检请求会在请求头中加入

  • Origin:请求源的协议、主机、端口

  • Access-Control-Request-Method:当前请求方法

  • Access-Control-Request-Header:简单头部以外的请求头

服务器可以根据以上请求头做相应过滤,以减轻服务器压力。对于符合要求的请求源,请求方法,请求头,服务器需要响应如下头部,预检请求会将如下信息告知浏览器允许该跨域请求的得到响应。

  • Access-Control-Allow-Origin:可信源
  • Access-Control-Allow-Methods:可用请求方法
  • Access-Control-Allow-Headers:可用请求头

所以即使服务器设置了Access-Control-Allow-Origin,且请求源符合就是其设置的可信源,也不能保证成功的CORS,对于非简单请求而言,还要服务器设置Access-Control-Allow-Headers和Access-Control-Allow-Methods。

对比JSONP跨域

CORS响应头跨域更加安全,且支持所有HTTP METHOD,另外只需要服务器在响应中增加几个头字段即可,前端不需要任何改动。

代理服务器跨域

同源策略是浏览器内置的安全策略,所有从浏览器发出的请求都要符合同源策略,如果需要通过浏览器发送跨域请求,则需要借助JSONP,CORS等跨域技术,而JSONP,CORS实际上也是基于浏览器的,它们并不是绕过浏览器同源策略,而是走浏览器同源策略开的后门。

那么有没有办法绕过浏览器的同源策略呢?

有,那就是不通过浏览器发出跨域请求。

那么如何实现呢?大致流程如下:

网页 → 同源服务器 → 非同源服务器

即由网页同源的代理服务器作为中间件,来转发网页的请求到非同源服务器,这样就绕过了浏览器发送跨域请求。

此时,我们需要注意的是,如果目的服务器自己做了请求源过滤,则我们需要做一些欺骗收到,即设置虚假的referer或origin头来迷惑目的服务器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值