前言:
夏洛:大爷,楼上322住的是马冬梅家吧?
大爷:马都什么?
夏洛:马冬梅。
大爷:什么都没啊?
夏洛:马冬梅啊。
大爷:马什么没?
夏洛:行,大爷你先凉快着吧。
基于 http 协议的 web 应用程序是请求——应答模式是无状态的,我们可以这样理解:每次的请求都是独立的,它的执行情况和结果与前面的请求和之后的请求是无直接关系的,它不会受前面的请求应答情况直接影响,也不会直接影响后面的请求应答情况。
为了让Web服务器有记忆,有状态,有以下几种方法。
Cookie
- 浏览器第一次访问服务端时,服务器此时肯定不知道他的身份,所以创建一个独特的身份标识数据,格式为key=value,放入到Set-Cookie字段里,随着响应报文发给浏览器。
- 浏览器看到有Set-Cookie字段以后就知道这是服务器给的身份标识,于是就保存起来,下次请求时会自动将此key=value值放入到Cookie字段中发给服务端。
- 服务端收到请求报文后,发现Cookie字段中有值,就能根据此值识别用户的身份然后提供个性化的服务。
Cookie
就是服务器委托浏览器存储在客户端里的一些数据,而这些数据通常都会记录用户的关键识别信息。所以Cookie
需要用一些其他的手段用来保护,防止外泄或者窃取,这些手段就是Cookie
的属性。
参数名 | 作用 | 后端设置方法 |
---|---|---|
Max-Age | 设置cookie的过期时间,单位为秒 | cookie.setMaxAge(10) |
Domain | 指定了Cookie所属的域名 | cookie.setDomain("") |
Path | 指定了Cookie所属的路径 | cookie.setPath(""); |
HttpOnly | 告诉浏览器此Cookie只能靠浏览器Http协议传输,禁止其他方式访问 | cookie.setHttpOnly(true) |
Secure | 告诉浏览器此Cookie只能在Https安全协议中传输,如果是Http则禁止传输 | cookie.setSecure(true) |
Path
设置为cookie.setPath("/testCookies"),接下来我们访问http://localhost:8005/testCookies,我们可以看到在左边和我们指定的路径是一样的,所以Cookie才在请求头中出现,接下来我们访问http://localhost:8005,我们发现没有Cookie字段了,这就是Path控制的路径。
Domain
设置为cookie.setDomain("localhost"),接下来我们访问http://localhost:8005/testCookies我们发现下图中左边的是有Cookie的字段的,但是我们访问http://172.16.42.81:8005/testCookies,看下图的右边可以看到没有Cookie的字段了。这就是Domain控制的域名发送Cookie。
Session
Cookie是存储在客户端方,Session是存储在服务端方,客户端只存储SessionId
@RequestMapping("/testSession")
@ResponseBody
public String testSession(HttpSession session){
session.setAttribute("testSession","this is my session");
return "testSession";
}
@RequestMapping("/testGetSession")
@ResponseBody
public String testGetSession(HttpSession session){
Object testSession = session.getAttribute("testSession");
return String.valueOf(testSession);
}
我们在请求参数中加上HttpSession session,然后再浏览器中输入http://localhost:8005/testSession进行访问可以看到在服务器的返回头中在Cookie中生成了一个SessionId。然后浏览器记住此SessionId下次访问时可以带着此Id,然后就能根据此Id找到存储在服务端的信息了。
过期时间:
客户端:和Cookie过期一致,如果没设置,默认是关了浏览器就没了,即再打开浏览器的时候初次请求头中是没有SessionId了。
服务端:服务端的过期是真的过期,即服务器端的Session存储的数据结构多久不可用了,默认是30分钟。
Session是存储在Tomcat(以此为例)的容器中,所以如果后端机器是多台的话,因此多个机器间是无法共享Session的,此时可以使用Spring提供的分布式Session的解决方案,是将Session放在了Redis中。
Token
Session是将要验证的信息存储在服务端,并以SessionId和数据进行对应,SessionId由客户端存储,在请求时将SessionId也带过去,因此实现了状态的对应。而Token是在服务端将用户信息经过Base64Url编码过后传给在客户端,每次用户请求的时候都会带上这一段信息,因此服务端拿到此信息进行解密后就知道此用户是谁了,这个方法叫做JWT(Json Web Token)。
Token相比较于Session的优点在于,当后端系统有多台时,由于是客户端访问时直接带着数据,因此无需做共享数据的操作。
Token的优点
- 简洁:可以通过URL,POST参数或者是在HTTP头参数发送,因为数据量小,传输速度也很快
- 自包含:由于串包含了用户所需要的信息,避免了多次查询数据库
- 因为Token是以Json的形式保存在客户端的,所以JWT是跨语言的
- 不需要在服务端保存会话信息,特别适用于分布式微服务
CSRF攻击
可以这么理解:有一个人发给你一个搞(mei)笑(nv)图片链接,你打开这个链接之后,便立刻收到了短信:你的银行里的钱已经转移到这个人的帐户了。
1.登录受信任网站A,并在本地生成Cookie。
2.在不登出A的情况下,访问危险网站B。
CSRF 攻击的对象
在讨论如何抵御 CSRF 之前,先要明确 CSRF 攻击的对象,也就是要保护的对象。从以上的例子可知,CSRF 攻击是黑客借助受害者的 cookie 骗取服务器的信任,但是黑客并不能拿到 cookie,也看不到 cookie 的内容。另外,对于服务器返回的结果,由于浏览器同源策略的限制,黑客也无法进行解析。因此,黑客无法从返回的结果中得到任何东西,他所能做的就是给服务器发送请求,以执行请求中所描述的命令,在服务器端直接改变数据的值,而非窃取服务器中的数据。所以,我们要保护的对象是那些可以直接产生数据改变的服务,而对于读取数据的服务,则不需要进行 CSRF 的保护。比如银行系统中转账的请求会直接改变账户的金额,会遭到 CSRF 攻击,需要保护。而查询余额是对金额的读取操作,不会改变数据,CSRF 攻击无法解析服务器返回的结果,无需保护。
当前防御 CSRF 的几种策略
- 验证 HTTP Referer 字段
服务器验证Referer 字段,如果不是受信网站则拒绝请求。
问题:使用验证 Referer 值的方法,就是把安全性都依赖于第三方(即浏览器)来保障,从理论上来讲,这样并不安全。事实上,对于某些浏览器,比如 IE6 或 FF2,目前已经有一些方法可以篡改 Referer 值。如果 bank.example 网站支持 IE6 浏览器,黑客完全可以把用户浏览器的 Referer 值设为以 bank.example 域名开头的地址,这样就可以通过验证,从而进行 CSRF 攻击。并且用户可以设置不使用Referer。非100%安全。
用户操作限制,比如验证码
严重影响了用户体验,而且还有额外的开发成本Token验证的CSRF防御机制是公认最合适的方案
参考文档
- cookie session token
https://segmentfault.com/a/1190000021185229 - CSRF攻击和跨域
https://segmentfault.com/a/1190000021029334
https://segmentfault.com/a/1190000005125790
https://segmentfault.com/a/1190000006944760
https://segmentfault.com/a/1190000003716037
https://www.ibm.com/developerworks/cn/web/1102_niugang_csrf/
3.web安全周刊
https://segmentfault.com/a/1190000007564080