在游览器刚出现时,使用http协议作为通信协议
在计算机网络江湖中,OSI模型一共有七层
计算机网络江湖
http属于OSI模型中的第七层——应用层
1.0版本的http协议,
是一种短链接无状态的协议(1.0版本之后支持长连接了)
http短链接和长连接那些事:https://blog.51cto.com/muhuizz/1912768
此文非阐述协议本身,而是“协议无状态”和“相关的解决方案”
协议的状态,是指下次传输可以 “记住” 这次传输信息的能力
有人会说:
“嘿,http协议底层采用了tcp协议,它是一种双向通信的协议啊,你怎么能说http协议是无状态的呢?”
tcp协议是个啥:https://www.ruanyifeng.com/blog/2017/06/tcp-protocol.html
其实,
每一次请求之间是没有联系的,独立的,所以服务器不知道请求两次之间是否是同一个用户
举例:
用户登陆网站后,切换到其他页面还需要带着账号密码登录,
因为对于服务器来说,切换页面的时候的用户是哪个用户是不知道的。
在设计http协议之初,考虑到服务器可能面对的不只一个游览器的并发访问,
所以此协议是不携带发送者的状态信息的。(而且游览器厂商为了隐私安全,正在逐步消除UA标头里面的信息)
什么是UA:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/User-Agent
服务器不知客户端是否被关闭了,所以它不可能一直维护者连接状态(维护状态需要占用服务器内存,这是对服务器内存的一种浪费)
“我是杠精,Connection: keep-alive这个http首部头你怎么解释呢,网上说这是支持TCP长连接吗?所以说http是有状态的协议”
首先,是长连接并不意味着是有状态,http中keep-alive存在的必要性就是方便服务器传输文件罢了
举例:
游览器第一次访问服务器,返回一个网页,此时的http的首部是设置了keep-alive,建立的tcp通道得以保留,
第二次服务器访问网页时就能沿用之前建立的tcp通道
服务器可决定多长时间关闭通道(一般也会这么做,维护通道会长期占用服务器的内存)
其次,本质上来说,HTTP是短连接,每次“请求-响应”都是一次TCP连接。
比如用户一次请求就是一次TCP连接,服务器响应结束后断开连接(设置超时时间)
而每次TCP连接是没有关联的,因此HTTP是无状态的
TCP中也有keep-alive,长连接只是保证连接是不会被断开的,保证它的可靠传输,无长短之分
那么?当开发B/S架构的服务,怎能保证端到端通信是有状态的呢?
端到端保存状态
cookie详解 和 session详解
这两篇文章简略的介绍了cookie和session,不懂的读者可以跳过去看
无论是tomcat 、php,传统的实现方案都是在客户端的cookie里面存储sessionID作于服务器对用户会话的唯一标识,然后为对应的session会话申请内存空间
sessionId架构的流程
这样,http请求即使无状态,但框架为每一个用户标识了对应的存储空间,变得有状态了
弊端,状态存储在服务器,对内存来说不太友好
也导致了,单机tomcat不能处理很高的并发量,因为能使用的内存是有上限的
操作系统的内存超过上限便会使用"交换分区",
这是一个本地磁盘IO操作了,非常耗时,极易导致网络出现超时,从而用户拿不到页面。
什么是交换分区:
https://baike.baidu.com/item/Swap%E5%88%86%E5%8C%BA/7613378
但是解决方案还是有的,我们可以用分布式session+多机tomcat来解决,本质就是把(sessionID+session内存里存放的对象持久化)放入数据库(关系型或者非关系型)内
一般是缓存数据库,因为速度比较快
举例:
用户第二次访问页面的时候,首先查看缓存数据库内,"用户请求的cookie内存放的sessionID"是否存在;
如果有,便把数据拿出来反序列化成对象,新申请一个内存空间用于存放对应的对象
弊端,sessionId存放在cookie里面容易导致csrf攻击
csrf攻击,在后续讲解黑客的手段的时候会介绍
在eggjs等后端框架,对session的实现又是另外一种方案,他们直接将session里的对象存放在客户端的cookie内,服务端是不存储状态的
cookie里面存储空间是有限制的,不会存放太多的用户资源,一般存放token
什么是token?
一种令牌机制,如图:
token架构的流程
此图中的校验token有若干实现方案,
1、通过DB(数据库)来进行校验,在生成Token的时候往数据库存一份,校验的时候做查询即可。
好处:对用户的token做到控制过期时间
坏处:增加成本,要使用DB来存储用户的token
2、通过非对称加密进行校验,利用私钥进行签名生成Token(Hash值);公钥验证数据的完整性(也有其他方式,本质上来说是判断这个Token是否合法)
非对称加密流程
好处:不需要保存Token,真正意义上的不用保存状态
坏处:无法合理控制用户Token过期时间
JWT,类似于第二种方案
将使用到的用户数据等信息,通过服务器设置的私钥,生成对应的Hash值,并且放入客户端本地
客户端每次请求都会携带上,并且在后台进行校验观察此值是否正确,只要保证密钥不泄露,Hash就无法伪造。
JWT的两种模式HMAC模式和RSA模式。
图中,JWS是采用HMAC模式实现的JWT,JWE是采用RSA模式实现的JWT
JWS——HMAC模式
HMAC被用来防止被无法获得"私钥"的人操纵。
通常,这意味着如果"私钥"只有服务器知道,就防止客户端操纵;
如果"私钥"是客户端和服务器都知道,就防止传输过程中被操纵。
JWT引入HMAC模式就是让防止,除服务器之外的所有人伪造Token,确保制造Token的人只能有服务器一个人。
在做校验的时候,客户端会提供payload 、 header 、以及一个在本地保存的Token。(也就是第一次登陆之后,服务器生成并客户端保存的Token)
服务器会拿着"payload、header、服务器私钥"重新生成一次Token,并且与客户端发送过来的Token进行比对,看是否为一致的
(在哈希计算中,如果提供的信息没有变化,那么生成的"数据"永远一致)。
简略的谈,A 永远只能生成 B
什么是哈希?:https://baike.baidu.com/item/Hash/390310
JWE——RSA模式
基于RSA的签名用于防止篡改token,并允许其他人在无法篡改数据的情况下验证数据的完整性和来源。
之所以可以这样做,是因为只有数据的"提供者"和"签名者"知道"私钥",而所有验证完整性的服务都知道"公钥"。
什么是RSA?:
https://baike.baidu.com/item/RSA%E7%AE%97%E6%B3%95/263310
所以对于微服务架构,使用RSA相对而言比较安全,除了签发token的服务需要私钥以外,其他服务只需要掌握公钥即可,不会造成私钥的泄露,避免非法者通过其中一个服务的漏洞获取到私钥,从而伪造token
因此,选择什么取决于具体的用例。如果服务器只需要保护令牌不被客户端操纵,那么使用带有服务器端"私钥"的HMAC就足够了。如果它还需要向其他人证明令牌是由特定(可信)服务器创建的,那么应该使用基于RSA的签名。
使用HMAC,每个人都有共享的"私钥",可以验证完整性,但也可以修改数据,这意味着它不能真正用于证明来源超过两方涉及(这也意味着"私钥"需要在这两方之间的传输保护)。使用RSA签名,每个拥有公钥的人都可以验证签名,但只有拥有私钥的人才可以创建签名。
JWT的数据格式
JWT的一个在线解析器,解析了一串JWT生成的参数
① Header(头部):JSON 对象,描述 JWT 的元数据。其中 alg 属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ 属性表示这个令牌(token)的类型(type),统一写为 JWT
② Payload(载荷):JSON 对象,存放实际需要传递的数据,支持自定义字段
③ Signature(签名):这部分就是 JWT 防篡改的精髓,其值是对前两部分 base64UrlEncode 后使用指定算法签名生成,以默认 HS256 为例,指定一个密钥(secret),就会按照如下公式生成:
HMACSHA256(base64UrlEncode(header)+“.”+ base64UrlEncode(payload), secret)
JWT虽好,但也有需要注意的点:
始终执行算法验证签名算法的验证固定在后端,不以 JWT 里的算法为标准。假设每次验证 JWT ,验证算法都靠读取 Header 里面的 alg 属性来判断的话,攻击者只要签发一个 “alg: none” 的 JWT ,就可以绕过验证了。
选择合适的算法具体场景选择合适的算法,例如分布式场景下,建议选择 RS256 。(多服务存放私钥容易泄露,采用RSA模式的更加安全)
HMAC 算法的密钥安全除了需要保证密钥不被泄露之外,密钥的强度也应该重视,防止遭到字典攻击。
避免敏感信息保存在 Payload ,JWT 中JWS 方式下的 JWT 的 Payload 信息是公开的,不能将敏感信息保存在这里,如有需要,请使用 JWE 。
JWT 过期时间建议设置足够短,过期后重新使用 refresh_token 刷新获取新的 token
讲完JWT ,接下来讲讲这种token的实际运用的一个开放协议簇——OIDC
第三方应用授权
它允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表,登陆),而无需将用户名和密码提供给第三方应用
(比如常见的使用qq登陆某某应用,登陆也属于一种资源的使用)
用户通过第三方应用来访问服务
OIDC协议簇百花齐放——SAML、OAuth、OpenID等
详细介绍了OIDC协议簇:https://www.cnblogs.com/CKExp/p/16084545.html
OpenID是一个去中心化网络身份认证系统,第三方OpenID注册中心给用户提供一个统一网络身份,遵守OpenID协议的应用网站引导用户在第三方OpenID认证中心进行认证
用户便可以使用OpenID的对应的网络身份来登陆这些应用网站
任何第三方服务都可以成为OpenID注册中心,或者认证中心
只需要完成OpenID协议上的实现
最开始,大部分公司都是采用OpenID协议来进行第三方应用的授权,直到Twitter、Google的OAuth草案出来
从身份验证来看OAuth和OpenID的差异
由于大公司体量大,影响力广,导致了市面上大部分的公司的服务采用OAuth来进行第三方应用授权,
关于OIDC协议簇,此文着重讲解其中的OAuth协议
在OAuth中一共有四种角色:
以github的OAuth来举例
Resource Owner:资源所有者,拥有资源的人,简单说就是谁在操作浏览器/App/小程序
Resource Server:资源服务器,资源拥有者拥有的资源
Client:第三方应用,从资源所有者处获取授权,然后从资源服务器获取资源
当不考虑OAuth协议,我们常见便是如上三种,想要第三方应用可以使用资源服务器的资源,又得把自身的账号密码交付给第三方应用,带来不安全。如此再加入一个角色,以平衡控制
Authorization Server:授权服务器,提供给资源所有者完成授权并确定允许给第三方应用可以使用的资源范围与操作。由资源服务器方负责实现
如此一来,资源所有者将账号密码提供给授权服务器来完成验证,验证有效由授权服务器来负责提供能够代表身份的标识给第三方应用,这样一来降低了账号密码泄露的可能
OAuth流程可以抽象成五部曲:
OAuth的抽象流程
第三方应用(RP)发送认证请求到认证服务方(OP)
用户在认证页面进行认证与选择授权内容
认证服务方(OP)对认证请求进行验证,发送ID token及Access token给第三方应用(RP)
第三方应用可使用Access token请求用户信息或其他授权内的资源
资源服务对Access token校验并返回用户信息或资源
OAuth的实现上的几种模式
授权码模式、凭证模式、隐藏模式、密码模式
本文只讲最常用的授权码模式(避免拉的过长)
授权码模式的基本流程,红色是重点过程
通过授权端获取授权码,再通过授权码来获取token,最后通过token来去拿资源,这种模式是一种很常见的模式
举个例子:
当前访问的客户端重定向到授权服务器的地址
https://authorization-server.com/auth
?response_type=code
&client_id=29352915982374239857
&redirect_uri=https%3A%2F%2Fexample-app.com%2Fcallback
&scope=create+delete
&state=xcoiv98y2kd22vusuye3kch
参数说明:
response_type=code 必选,向授权服务器标识客户端使用授权码方式
client_id 必选,应用的身份标识Id(应用先得在授权服务器中登记得到一对ClientId和Client Secret)
redirect_uri 可选,授权成功后从授权服务器重定向回client的地址
scope 可选,表示Client申请授权的范围,用一个或多个空格分隔
state 推荐,Client提供的一个字符串,服务器会原样返回给Client,以阻止CSRF攻击
然后,在授权页中同意授权,重定向回Client提供的重定向地址
https://example-app.com/redirect
?code=g0ZGZmNjVmOWIjNTk2NTk4ZTYyZGI3
&state=xcoiv98y2kd22vusuye3kch
参数说明:
state 为Client提供的字符串,授权服务器原样返回,用于Client防止CSRF攻击。
code 授权服务器生成的授权码(临时的)。
重定向回到Client后,Client获取到授权码,向授权服务器发送Post请求,以换取访问令牌。
POST
https://authorization-server.com/auth/token
Content-Type: application/x-www-form-urlencodedAuthorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW(此处值为clientId:clientsecret做Base64,Basic认证格式)
参数:
grant_type=authorization_code
&code=Yzk5ZDczMzRlNDEwY
&redirect_uri=https://example-app.com/cb
参数说明:
grant_type=authorization_code 必选,向授权服务器标识客户端类型为授权码类型。
code 必选,上一步中重定向返回的授权码。
redirect_uri 必选,和第一步中提供的redirect_uri相同。
client_id 必选,客户端Id,第一步中提及的应用标识Id。
client_secret 必选,客户端标识Id对应密钥,用于授权服务器对客户端身份验证,并防范授权码和客户端Id暴露被直接用来换取访问令牌。
授权服务器通过授权码验证客户端身份,以及授权码正确及有效后返回访问令牌,返回的数据一般如下:
{
“access_token”: “MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3”,
“token_type”: “bearer”,
“expires_in”: 3600,
“refresh_token”: “IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk”,
“scope”: “create delete”
}
access_token 、refresh_token 参数说明:
前者是访问资源的时候使用的token, 后者则是前者失效之后,用于获取更新之后的access_token;后者的失效时间远长于前者
关于refresh_token 的存储:
如果采用cookie-session架构(服务器有状态),
可以通过将sessionID标识写入到用户的cookie中,
同时服务器本地session中记录用户的userID和refresh_token,这是可以做到的,由客户端Cookie里保存的sessionID来标识用户是谁。
如果采用JWT架构(服务器无状态),
可能会把refresh_token加密后存入客户端本地,下次请求的时候带上加过密的refresh_token,由服务器的私钥对其密文进行解密,拿到对应的refresh_token(采用JWT的RSA模式)
也可能,在refresh_token生成的时候存入redis,并且写入到用户的cookie中,
鉴权的时候,服务器拿着前台发过来的cookie在redis做查询操作,若查询得到的话则登陆成功(采用传统token模式,对refresh_token的过期控制力强)
需要注意
Access_token和Refresh_token其核心在于,Client端请求资源的访问令牌对外隐藏,只在Client端、Authorization Server和Resource Server间使用。
标识这个用户身份以及如何确保用户下次访问仍是这个用户并不是OAuth2.0所关心的,OAuth2.0更多是偏向于对保护资源的有效访问,核心部分聚焦于此。
其他三种授权码模式请参见:
https://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html
对于端到端保存状态,黑客也有他自己的看法
CSRF攻击是一种黑客,对开发人员设计的保存状态机制的一种利用
那什么是CSRF呢?
举个例子:
陌生人=黑客 、 菜单=请求 、桌号=cookie 、老板=web服务器 、我=用户
想象我去一家餐厅吃饭,陌生人拿了一张带有我桌号的菜单,点餐后给了老板,老板问也不问的就收了菜单并且把这单的帐记在了我的身上,则就是CSRF攻击
CSRF,被人们称为跨站请求伪造攻击——见名知意,肯定是跟请求伪造、跨站是有所关系的
首先先讲讲请求伪造吧,话接上文,指的其实就是陌生人拿了一张有你桌号的菜单点自己想点的餐之后给老板这件事情(黑客用你的cookie的请求发送给web服务器)
跨站?跨越了本该知情的你,把你的桌号的菜单给了老板(送出去的人不同,http协议中的domain字段的参数,会不一样)
csrf的漏洞本质在于web服务器太过于相信cookie,无心关注是谁发送的,因此只能保证这个请求时来自某个用户的,但是不能保证请求本身时用户资源发出的
简略的谈谈,csrf的攻击流程
一次恶意请求的攻击流程
1、用户访问并且登入A网站
2、用户获得cookie并且存储在用户游览器
3、用户未登出A网站的情况下游览B网站,黑客的B网站实现准备了一些js脚本代码,加载之后然后使用了用户的cookie去发送请求,能使用用户的合法资源
具体:B网站会在自身的网站中加入A网站的Domain的Javascript,上图的A/pay?to=B,这里的A是指的A网站Domain,然后以A的身份调用pay这个接口去转账给B
黑客在页面里注入XSS之后,只要访问被注入的XSS页面,黑客利用csrf漏洞可以把自己模拟成为用户
黑客通过让用户加载第三方页面中非法的Javascript的方式,使用本地的游览器缓存(cookie、localStorage等),对服务器发送网络请求来达到对资源的访问、修改、窃取
如何判断是否为csrf攻击呢?三要素
1、有一个可以触发恶意脚本的动作
2、服务器采用单一条件验证网站身份,比如:只验证cookie 或 token
3、没有验证或者验证的参数是可以预测的(比如固定的cookie)
如何这些黑客对状态的利用呢?(防止csrf攻击呢)
只要稳住前面所说的三要素即可,
1、增加敏感动作的验证方式,比如:在转账或者交易的时候,采用人机验证码,强制要求为此操作的行为由"人"来执行的
2、增加魔术变量,也就是让人无法预测的参数,比如:CSRF token(是客户端的每一个请求都要带着这个token)
CSRF token的验证流程,
首先,建立token:在用户打开网页的时候,服务器根据用户的身份添加一个token,并且把token存放在页面中,会与cookie分隔开(生成的逻辑一般是用户名+随机的一个"字符串"或者"时间戳")
传递请求的时候,对于特定的接口,请求如果没有加上这个token,那么请求不会允许
CSRF token在http请求的实现:
通常对于GET请求,token会附在请求的网页之后,这样的url一般会变成
http://url?csrftoken=tokenvalue
而POST请求,一般在form表单的最后加上
如下图
这样就把Token以参数的形式加入请求,每次请求都会带着这个csrftoken
验证csrftoken的环节:
当用户发送有请求的csrftoken给服务器的时候,服务器为了确定token的时效性和正确性,会先解密token,检查当时加密的资料和解密得出的资料是否合法
如果合法,服务器就会认为这次请求是有效的,验证通过
所以对于存在csrf风险的地方需要严加管理,不然你就等着用户找你要被黑客弄走的钱吧
那什么是xss呢?
继续采用刚才讲csrf的小故事:
你是用户,陌生人是黑客,菜单是请求,桌号是cookie,老板是web服务器
你到一家餐厅吃饭,陌生人在你的菜单上面写超级无敌辣,然后你没有注意,直接把菜单给了老板,老板就给你来了一份超级无敌辣的餐点
这就是xss的基本概念
当服务器进行页面渲染的时候(老板开始做菜),使用者没有验证的一些参数(比如博客上面的评论),被被嵌入网页上(菜单),
如果这些参数输入了包含恶意代码(超级无敌辣),其他使用者游览这个页面的时候触发这个JavaScript代码,导致xss风险的诞生
简略的讲一下xss的攻击流程
1、黑客在受害网站注入xss漏洞
2、通过社交手段发送恶意的url给用户(社交软件发送超链接)
3、当用户点击url的时候,会把自己的资料(比如cookie)从受害网站发送给黑客
xss也分为好几派
反射型XSS
最常见的XSS攻击类型,通常是把恶意程序常在网址里面,放在GET请求里面
http://www.example.com/upload.asp?id=
这种手法要成功攻击,需要会使用社交工程钓鱼的技巧,使用户点击超链接攻击才会生效
并且url看起来很诡异,所以黑客可能会使用短链接或者html编码的方式欺骗用户(下文会讲什么是html编码)
DOM型XSS
这种手段跟反射型XSS一样,都需要使用社交工程钓鱼的技巧,要使得用户点击链接才会生效
黑客在dom加载的一些回调事件里面植入恶意脚本
比如:
储存型XSS
这种方法与前两种手法不同,此种方法不需要使用社交工程钓鱼的技巧,也能使用户收到攻击
攻击的方式就是黑客把JavaScript恶意脚本存储在服务器中,从而在渲染页面的时候,将恶意脚本也进行加载
最常见的例子就是把JavaScript注入留言板,当一位用户游览网页的时候,游览器渲染对应的页面,会把留言板中的JavaScript加载导致用户受到攻击
我是壞人!
这行代码被注入留言板的时候,用户游览的页面就会跳出一个为1的alert
如果黑客输入让用户进行传送cookie和其他的恶意行为,网页也会完全照做
值得一提的是,三种XSS攻击中,DOM型XSS和另外两种XSS的区别就是DOM型XSS攻击都是由游览器客户端执行,属于JavaScript的安全漏洞,其他两种XSS攻击是因为服务器产生的安全风险
如何防范XSS攻击呢
1、需要做好任何输入位置的验证与检查,要假设前端的输入都是恶意、不可信的,比如:表单上传、GET请求的参数、表单栏位、留言板等
2、根据语意来进行格式要求:比如年龄的的栏位只接受0-9数字
3、如果是富文本编辑的一些自由格式,应该要经过程序的处理称为纯字符串,可以用第三方库来进行格式化处理
注意,有时自己编写的格式化程序不够全面:
如果非法用户进行以下输入
%3Cscript%3Ealert(1)%3B%3C%2Fscript%3E
那么服务器便会存储对应的代码,并且在前台用户游览的界面里以下方的格式进行加载
黑客会使用这种名字叫做HTML Encoder的方式来进行输入,然后利用web服务器或者游览器解析特殊字符的功能,来达到XSS攻击的目的
4、可以对cookie设置httpOnly的属性,来保证所有的JavaScript代码没有权限读取对应的cookie,可以预防恶意JavaScript
5、确定响应头的content-type格式,是什么文件就使用什么格式,确保内容来源是安全的
如果觉得封面的花很好看的话,可以转发给朋友欣赏哦在游览器刚出现时,使用http协议作为通信协议
在计算机网络江湖中,OSI模型一共有七层
计算机网络江湖
肌肉娃子,公众号:肌肉娃子
《写给她的计算机网络》1 概论
http属于OSI模型中的第七层——应用层
1.0版本的http协议,
是一种短链接无状态的协议(1.0版本之后支持长连接了)
http短链接和长连接那些事:https://blog.51cto.com/muhuizz/1912768
此文非阐述协议本身,而是“协议无状态”和“相关的解决方案”
协议的状态,是指下次传输可以 “记住” 这次传输信息的能力
有人会说:
“嘿,http协议底层采用了tcp协议,它是一种双向通信的协议啊,你怎么能说http协议是无状态的呢?”
tcp协议是个啥:https://www.ruanyifeng.com/blog/2017/06/tcp-protocol.html
其实,
每一次请求之间是没有联系的,独立的,所以服务器不知道请求两次之间是否是同一个用户
举例:
用户登陆网站后,切换到其他页面还需要带着账号密码登录,
因为对于服务器来说,切换页面的时候的用户是哪个用户是不知道的。
在设计http协议之初,考虑到服务器可能面对的不只一个游览器的并发访问,
所以此协议是不携带发送者的状态信息的。(而且游览器厂商为了隐私安全,正在逐步消除UA标头里面的信息)
什么是UA:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/User-Agent
服务器不知客户端是否被关闭了,所以它不可能一直维护者连接状态(维护状态需要占用服务器内存,这是对服务器内存的一种浪费)
“我是杠精,Connection: keep-alive这个http首部头你怎么解释呢,网上说这是支持TCP长连接吗?所以说http是有状态的协议”
首先,是长连接并不意味着是有状态,http中keep-alive存在的必要性就是方便服务器传输文件罢了
举例:
游览器第一次访问服务器,返回一个网页,此时的http的首部是设置了keep-alive,建立的tcp通道得以保留,
第二次服务器访问网页时就能沿用之前建立的tcp通道
服务器可决定多长时间关闭通道(一般也会这么做,维护通道会长期占用服务器的内存)
其次,本质上来说,HTTP是短连接,每次“请求-响应”都是一次TCP连接。
比如用户一次请求就是一次TCP连接,服务器响应结束后断开连接(设置超时时间)
而每次TCP连接是没有关联的,因此HTTP是无状态的
TCP中也有keep-alive,长连接只是保证连接是不会被断开的,保证它的可靠传输,无长短之分
那么?当开发B/S架构的服务,怎能保证端到端通信是有状态的呢?
端到端保存状态
cookie详解 和 session详解
这两篇文章简略的介绍了cookie和session,不懂的读者可以跳过去看
无论是tomcat 、php,传统的实现方案都是在客户端的cookie里面存储sessionID作于服务器对用户会话的唯一标识,然后为对应的session会话申请内存空间
sessionId架构的流程
这样,http请求即使无状态,但框架为每一个用户标识了对应的存储空间,变得有状态了
弊端,状态存储在服务器,对内存来说不太友好
也导致了,单机tomcat不能处理很高的并发量,因为能使用的内存是有上限的
操作系统的内存超过上限便会使用"交换分区",
这是一个本地磁盘IO操作了,非常耗时,极易导致网络出现超时,从而用户拿不到页面。
什么是交换分区:
https://baike.baidu.com/item/Swap%E5%88%86%E5%8C%BA/7613378
但是解决方案还是有的,我们可以用分布式session+多机tomcat来解决,本质就是把(sessionID+session内存里存放的对象持久化)放入数据库(关系型或者非关系型)内
一般是缓存数据库,因为速度比较快
举例:
用户第二次访问页面的时候,首先查看缓存数据库内,"用户请求的cookie内存放的sessionID"是否存在;
如果有,便把数据拿出来反序列化成对象,新申请一个内存空间用于存放对应的对象
弊端,sessionId存放在cookie里面容易导致csrf攻击
csrf攻击,在后续讲解黑客的手段的时候会介绍
在eggjs等后端框架,对session的实现又是另外一种方案,他们直接将session里的对象存放在客户端的cookie内,服务端是不存储状态的
cookie里面存储空间是有限制的,不会存放太多的用户资源,一般存放token
什么是token?
一种令牌机制,如图:
token架构的流程
此图中的校验token有若干实现方案,
1、通过DB(数据库)来进行校验,在生成Token的时候往数据库存一份,校验的时候做查询即可。
好处:对用户的token做到控制过期时间
坏处:增加成本,要使用DB来存储用户的token
2、通过非对称加密进行校验,利用私钥进行签名生成Token(Hash值);公钥验证数据的完整性(也有其他方式,本质上来说是判断这个Token是否合法)
非对称加密流程
好处:不需要保存Token,真正意义上的不用保存状态
坏处:无法合理控制用户Token过期时间
JWT,类似于第二种方案
将使用到的用户数据等信息,通过服务器设置的私钥,生成对应的Hash值,并且放入客户端本地
客户端每次请求都会携带上,并且在后台进行校验观察此值是否正确,只要保证密钥不泄露,Hash就无法伪造。
JWT的两种模式HMAC模式和RSA模式。
图中,JWS是采用HMAC模式实现的JWT,JWE是采用RSA模式实现的JWT
JWS——HMAC模式
HMAC被用来防止被无法获得"私钥"的人操纵。
通常,这意味着如果"私钥"只有服务器知道,就防止客户端操纵;
如果"私钥"是客户端和服务器都知道,就防止传输过程中被操纵。
JWT引入HMAC模式就是让防止,除服务器之外的所有人伪造Token,确保制造Token的人只能有服务器一个人。
在做校验的时候,客户端会提供payload 、 header 、以及一个在本地保存的Token。(也就是第一次登陆之后,服务器生成并客户端保存的Token)
服务器会拿着"payload、header、服务器私钥"重新生成一次Token,并且与客户端发送过来的Token进行比对,看是否为一致的
(在哈希计算中,如果提供的信息没有变化,那么生成的"数据"永远一致)。
简略的谈,A 永远只能生成 B
什么是哈希?:https://baike.baidu.com/item/Hash/390310
JWE——RSA模式
基于RSA的签名用于防止篡改token,并允许其他人在无法篡改数据的情况下验证数据的完整性和来源。
之所以可以这样做,是因为只有数据的"提供者"和"签名者"知道"私钥",而所有验证完整性的服务都知道"公钥"。
什么是RSA?:
https://baike.baidu.com/item/RSA%E7%AE%97%E6%B3%95/263310
所以对于微服务架构,使用RSA相对而言比较安全,除了签发token的服务需要私钥以外,其他服务只需要掌握公钥即可,不会造成私钥的泄露,避免非法者通过其中一个服务的漏洞获取到私钥,从而伪造token
因此,选择什么取决于具体的用例。如果服务器只需要保护令牌不被客户端操纵,那么使用带有服务器端"私钥"的HMAC就足够了。如果它还需要向其他人证明令牌是由特定(可信)服务器创建的,那么应该使用基于RSA的签名。
使用HMAC,每个人都有共享的"私钥",可以验证完整性,但也可以修改数据,这意味着它不能真正用于证明来源超过两方涉及(这也意味着"私钥"需要在这两方之间的传输保护)。使用RSA签名,每个拥有公钥的人都可以验证签名,但只有拥有私钥的人才可以创建签名。
JWT的数据格式
JWT的一个在线解析器,解析了一串JWT生成的参数
① Header(头部):JSON 对象,描述 JWT 的元数据。其中 alg 属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ 属性表示这个令牌(token)的类型(type),统一写为 JWT
② Payload(载荷):JSON 对象,存放实际需要传递的数据,支持自定义字段
③ Signature(签名):这部分就是 JWT 防篡改的精髓,其值是对前两部分 base64UrlEncode 后使用指定算法签名生成,以默认 HS256 为例,指定一个密钥(secret),就会按照如下公式生成:
HMACSHA256(base64UrlEncode(header)+“.”+ base64UrlEncode(payload), secret)
JWT虽好,但也有需要注意的点:
始终执行算法验证签名算法的验证固定在后端,不以 JWT 里的算法为标准。假设每次验证 JWT ,验证算法都靠读取 Header 里面的 alg 属性来判断的话,攻击者只要签发一个 “alg: none” 的 JWT ,就可以绕过验证了。
选择合适的算法具体场景选择合适的算法,例如分布式场景下,建议选择 RS256 。(多服务存放私钥容易泄露,采用RSA模式的更加安全)
HMAC 算法的密钥安全除了需要保证密钥不被泄露之外,密钥的强度也应该重视,防止遭到字典攻击。
避免敏感信息保存在 Payload ,JWT 中JWS 方式下的 JWT 的 Payload 信息是公开的,不能将敏感信息保存在这里,如有需要,请使用 JWE 。
JWT 过期时间建议设置足够短,过期后重新使用 refresh_token 刷新获取新的 token
讲完JWT ,接下来讲讲这种token的实际运用的一个开放协议簇——OIDC
第三方应用授权
它允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表,登陆),而无需将用户名和密码提供给第三方应用
(比如常见的使用qq登陆某某应用,登陆也属于一种资源的使用)
用户通过第三方应用来访问服务
OIDC协议簇百花齐放——SAML、OAuth、OpenID等
详细介绍了OIDC协议簇:https://www.cnblogs.com/CKExp/p/16084545.html
OpenID是一个去中心化网络身份认证系统,第三方OpenID注册中心给用户提供一个统一网络身份,遵守OpenID协议的应用网站引导用户在第三方OpenID认证中心进行认证
用户便可以使用OpenID的对应的网络身份来登陆这些应用网站
任何第三方服务都可以成为OpenID注册中心,或者认证中心
只需要完成OpenID协议上的实现
最开始,大部分公司都是采用OpenID协议来进行第三方应用的授权,直到Twitter、Google的OAuth草案出来
从身份验证来看OAuth和OpenID的差异
由于大公司体量大,影响力广,导致了市面上大部分的公司的服务采用OAuth来进行第三方应用授权,
关于OIDC协议簇,此文着重讲解其中的OAuth协议
在OAuth中一共有四种角色:
以github的OAuth来举例
Resource Owner:资源所有者,拥有资源的人,简单说就是谁在操作浏览器/App/小程序
Resource Server:资源服务器,资源拥有者拥有的资源
Client:第三方应用,从资源所有者处获取授权,然后从资源服务器获取资源
当不考虑OAuth协议,我们常见便是如上三种,想要第三方应用可以使用资源服务器的资源,又得把自身的账号密码交付给第三方应用,带来不安全。如此再加入一个角色,以平衡控制
Authorization Server:授权服务器,提供给资源所有者完成授权并确定允许给第三方应用可以使用的资源范围与操作。由资源服务器方负责实现
如此一来,资源所有者将账号密码提供给授权服务器来完成验证,验证有效由授权服务器来负责提供能够代表身份的标识给第三方应用,这样一来降低了账号密码泄露的可能
OAuth流程可以抽象成五部曲:
OAuth的抽象流程
第三方应用(RP)发送认证请求到认证服务方(OP)
用户在认证页面进行认证与选择授权内容
认证服务方(OP)对认证请求进行验证,发送ID token及Access token给第三方应用(RP)
第三方应用可使用Access token请求用户信息或其他授权内的资源
资源服务对Access token校验并返回用户信息或资源
OAuth的实现上的几种模式
授权码模式、凭证模式、隐藏模式、密码模式
本文只讲最常用的授权码模式(避免拉的过长)
授权码模式的基本流程,红色是重点过程
通过授权端获取授权码,再通过授权码来获取token,最后通过token来去拿资源,这种模式是一种很常见的模式
举个例子:
当前访问的客户端重定向到授权服务器的地址
https://authorization-server.com/auth
?response_type=code
&client_id=29352915982374239857
&redirect_uri=https%3A%2F%2Fexample-app.com%2Fcallback
&scope=create+delete
&state=xcoiv98y2kd22vusuye3kch
参数说明:
response_type=code 必选,向授权服务器标识客户端使用授权码方式
client_id 必选,应用的身份标识Id(应用先得在授权服务器中登记得到一对ClientId和Client Secret)
redirect_uri 可选,授权成功后从授权服务器重定向回client的地址
scope 可选,表示Client申请授权的范围,用一个或多个空格分隔
state 推荐,Client提供的一个字符串,服务器会原样返回给Client,以阻止CSRF攻击
然后,在授权页中同意授权,重定向回Client提供的重定向地址
https://example-app.com/redirect
?code=g0ZGZmNjVmOWIjNTk2NTk4ZTYyZGI3
&state=xcoiv98y2kd22vusuye3kch
参数说明:
state 为Client提供的字符串,授权服务器原样返回,用于Client防止CSRF攻击。
code 授权服务器生成的授权码(临时的)。
重定向回到Client后,Client获取到授权码,向授权服务器发送Post请求,以换取访问令牌。
POST
https://authorization-server.com/auth/token
Content-Type: application/x-www-form-urlencodedAuthorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW(此处值为clientId:clientsecret做Base64,Basic认证格式)
参数:
grant_type=authorization_code
&code=Yzk5ZDczMzRlNDEwY
&redirect_uri=https://example-app.com/cb
参数说明:
grant_type=authorization_code 必选,向授权服务器标识客户端类型为授权码类型。
code 必选,上一步中重定向返回的授权码。
redirect_uri 必选,和第一步中提供的redirect_uri相同。
client_id 必选,客户端Id,第一步中提及的应用标识Id。
client_secret 必选,客户端标识Id对应密钥,用于授权服务器对客户端身份验证,并防范授权码和客户端Id暴露被直接用来换取访问令牌。
授权服务器通过授权码验证客户端身份,以及授权码正确及有效后返回访问令牌,返回的数据一般如下:
{
“access_token”: “MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3”,
“token_type”: “bearer”,
“expires_in”: 3600,
“refresh_token”: “IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk”,
“scope”: “create delete”
}
access_token 、refresh_token 参数说明:
前者是访问资源的时候使用的token, 后者则是前者失效之后,用于获取更新之后的access_token;后者的失效时间远长于前者
关于refresh_token 的存储:
如果采用cookie-session架构(服务器有状态),
可以通过将sessionID标识写入到用户的cookie中,
同时服务器本地session中记录用户的userID和refresh_token,这是可以做到的,由客户端Cookie里保存的sessionID来标识用户是谁。
如果采用JWT架构(服务器无状态),
可能会把refresh_token加密后存入客户端本地,下次请求的时候带上加过密的refresh_token,由服务器的私钥对其密文进行解密,拿到对应的refresh_token(采用JWT的RSA模式)
也可能,在refresh_token生成的时候存入redis,并且写入到用户的cookie中,
鉴权的时候,服务器拿着前台发过来的cookie在redis做查询操作,若查询得到的话则登陆成功(采用传统token模式,对refresh_token的过期控制力强)
需要注意
Access_token和Refresh_token其核心在于,Client端请求资源的访问令牌对外隐藏,只在Client端、Authorization Server和Resource Server间使用。
标识这个用户身份以及如何确保用户下次访问仍是这个用户并不是OAuth2.0所关心的,OAuth2.0更多是偏向于对保护资源的有效访问,核心部分聚焦于此。
其他三种授权码模式请参见:
https://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html
对于端到端保存状态,黑客也有他自己的看法
CSRF攻击是一种黑客,对开发人员设计的保存状态机制的一种利用
那什么是CSRF呢?
举个例子:
陌生人=黑客 、 菜单=请求 、桌号=cookie 、老板=web服务器 、我=用户
想象我去一家餐厅吃饭,陌生人拿了一张带有我桌号的菜单,点餐后给了老板,老板问也不问的就收了菜单并且把这单的帐记在了我的身上,则就是CSRF攻击
CSRF,被人们称为跨站请求伪造攻击——见名知意,肯定是跟请求伪造、跨站是有所关系的
首先先讲讲请求伪造吧,话接上文,指的其实就是陌生人拿了一张有你桌号的菜单点自己想点的餐之后给老板这件事情(黑客用你的cookie的请求发送给web服务器)
跨站?跨越了本该知情的你,把你的桌号的菜单给了老板(送出去的人不同,http协议中的domain字段的参数,会不一样)
csrf的漏洞本质在于web服务器太过于相信cookie,无心关注是谁发送的,因此只能保证这个请求时来自某个用户的,但是不能保证请求本身时用户资源发出的
简略的谈谈,csrf的攻击流程
一次恶意请求的攻击流程
1、用户访问并且登入A网站
2、用户获得cookie并且存储在用户游览器
3、用户未登出A网站的情况下游览B网站,黑客的B网站实现准备了一些js脚本代码,加载之后然后使用了用户的cookie去发送请求,能使用用户的合法资源
具体:B网站会在自身的网站中加入A网站的Domain的Javascript,上图的A/pay?to=B,这里的A是指的A网站Domain,然后以A的身份调用pay这个接口去转账给B
黑客在页面里注入XSS之后,只要访问被注入的XSS页面,黑客利用csrf漏洞可以把自己模拟成为用户
黑客通过让用户加载第三方页面中非法的Javascript的方式,使用本地的游览器缓存(cookie、localStorage等),对服务器发送网络请求来达到对资源的访问、修改、窃取
如何判断是否为csrf攻击呢?三要素
1、有一个可以触发恶意脚本的动作
2、服务器采用单一条件验证网站身份,比如:只验证cookie 或 token
3、没有验证或者验证的参数是可以预测的(比如固定的cookie)
如何这些黑客对状态的利用呢?(防止csrf攻击呢)
只要稳住前面所说的三要素即可,
1、增加敏感动作的验证方式,比如:在转账或者交易的时候,采用人机验证码,强制要求为此操作的行为由"人"来执行的
2、增加魔术变量,也就是让人无法预测的参数,比如:CSRF token(是客户端的每一个请求都要带着这个token)
CSRF token的验证流程,
首先,建立token:在用户打开网页的时候,服务器根据用户的身份添加一个token,并且把token存放在页面中,会与cookie分隔开(生成的逻辑一般是用户名+随机的一个"字符串"或者"时间戳")
传递请求的时候,对于特定的接口,请求如果没有加上这个token,那么请求不会允许
CSRF token在http请求的实现:
通常对于GET请求,token会附在请求的网页之后,这样的url一般会变成
http://url?csrftoken=tokenvalue
而POST请求,一般在form表单的最后加上
如下图
这样就把Token以参数的形式加入请求,每次请求都会带着这个csrftoken
验证csrftoken的环节:
当用户发送有请求的csrftoken给服务器的时候,服务器为了确定token的时效性和正确性,会先解密token,检查当时加密的资料和解密得出的资料是否合法
如果合法,服务器就会认为这次请求是有效的,验证通过
所以对于存在csrf风险的地方需要严加管理,不然你就等着用户找你要被黑客弄走的钱吧
那什么是xss呢?
继续采用刚才讲csrf的小故事:
你是用户,陌生人是黑客,菜单是请求,桌号是cookie,老板是web服务器
你到一家餐厅吃饭,陌生人在你的菜单上面写超级无敌辣,然后你没有注意,直接把菜单给了老板,老板就给你来了一份超级无敌辣的餐点
这就是xss的基本概念
当服务器进行页面渲染的时候(老板开始做菜),使用者没有验证的一些参数(比如博客上面的评论),被被嵌入网页上(菜单),
如果这些参数输入了包含恶意代码(超级无敌辣),其他使用者游览这个页面的时候触发这个JavaScript代码,导致xss风险的诞生
简略的讲一下xss的攻击流程
1、黑客在受害网站注入xss漏洞
2、通过社交手段发送恶意的url给用户(社交软件发送超链接)
3、当用户点击url的时候,会把自己的资料(比如cookie)从受害网站发送给黑客
xss也分为好几派
反射型XSS
最常见的XSS攻击类型,通常是把恶意程序常在网址里面,放在GET请求里面
http://www.example.com/upload.asp?id=
这种手法要成功攻击,需要会使用社交工程钓鱼的技巧,使用户点击超链接攻击才会生效
并且url看起来很诡异,所以黑客可能会使用短链接或者html编码的方式欺骗用户(下文会讲什么是html编码)
DOM型XSS
这种手段跟反射型XSS一样,都需要使用社交工程钓鱼的技巧,要使得用户点击链接才会生效
黑客在dom加载的一些回调事件里面植入恶意脚本
比如:
储存型XSS
这种方法与前两种手法不同,此种方法不需要使用社交工程钓鱼的技巧,也能使用户收到攻击
攻击的方式就是黑客把JavaScript恶意脚本存储在服务器中,从而在渲染页面的时候,将恶意脚本也进行加载
最常见的例子就是把JavaScript注入留言板,当一位用户游览网页的时候,游览器渲染对应的页面,会把留言板中的JavaScript加载导致用户受到攻击
我是壞人!
这行代码被注入留言板的时候,用户游览的页面就会跳出一个为1的alert
如果黑客输入让用户进行传送cookie和其他的恶意行为,网页也会完全照做
值得一提的是,三种XSS攻击中,DOM型XSS和另外两种XSS的区别就是DOM型XSS攻击都是由游览器客户端执行,属于JavaScript的安全漏洞,其他两种XSS攻击是因为服务器产生的安全风险
如何防范XSS攻击呢
1、需要做好任何输入位置的验证与检查,要假设前端的输入都是恶意、不可信的,比如:表单上传、GET请求的参数、表单栏位、留言板等
2、根据语意来进行格式要求:比如年龄的的栏位只接受0-9数字
3、如果是富文本编辑的一些自由格式,应该要经过程序的处理称为纯字符串,可以用第三方库来进行格式化处理
注意,有时自己编写的格式化程序不够全面:
如果非法用户进行以下输入
%3Cscript%3Ealert(1)%3B%3C%2Fscript%3E
那么服务器便会存储对应的代码,并且在前台用户游览的界面里以下方的格式进行加载
黑客会使用这种名字叫做HTML Encoder的方式来进行输入,然后利用web服务器或者游览器解析特殊字符的功能,来达到XSS攻击的目的
4、可以对cookie设置httpOnly的属性,来保证所有的JavaScript代码没有权限读取对应的cookie,可以预防恶意JavaScript
5、确定响应头的content-type格式,是什么文件就使用什么格式,确保内容来源是安全的
如果觉得封面的花很好看的话,可以转发给朋友欣赏哦