HTTP
协议是无状态的,意味着需要验证每一次请求,从而辨别客户端的身份。程序都是通过在服务端存储的登录信息来辨别请求的。
基于 Cookie/Session 的认证方式
Cookie是一种记录客户状态的机制,Cookie
实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response
向客户端浏览器颁发一个Cookie
。客户端浏览器会把Cookie
保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie
一同提交给服务器。服务器检查该Cookie
,以此来辨认用户状态。服务器还可以根据需要修改Cookie
的内容。
Cookie
功能需要浏览器的支持。如果浏览器不支持Cookie
(如大部分手机中的浏览器)或者把Cookie
禁用了,Cookie
功能就会失效。
不同的浏览器采用不同的方式保存Cookie
。IE
浏览器会以文本文件形式保存,一个文本文件保存一个Cookie
。
Cookie
具有不可跨域名性。根据Cookie
规范,只能携带当前域名的cookie。
cookie
是浏览器实现的一种数据存储功能。cookie
可以自行设置保存时间。若没有设置,关闭浏览器,cookie
就自动消失。
Session
是另一种记录客户状态的机制,不同的是Cookie
保存在客户端浏览器中,而Session
保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session
。客户端浏览器再次访问时只需要从该Session
中查找该客户的状态就可以了。
如果说Cookie
机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session
机制就是通过检查服务器上的“客户明细表”来确认客户身份。
Cookie与Session的区别和联系
-
cookie
数据存放在客户的浏览器上,session
数据放在服务器上; -
cookie
不是很安全,别人可以分析存放在本地的COOKIE
并进行COOKIE
欺骗,考虑到安全应当使用session
; -
session
会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能。考虑到减轻服务器性能方面,应当使用COOKIE
; -
单个cookie在客户端的限制是3K,就是说一个站点在客户端存放的COOKIE不能超过3K;
Cookie
和Session
的方案虽然分别属于客户端和服务端,但是服务端的session
的实现对客户端的cookie
有依赖关系的,服务端执行session
机制时候会生成 session
的id值,这个id
值会发送给客户端,客户端每次请求都会把这个id
值放到http
请求的头部发送给服务端,而这个id
值在客户端会保存下来,保存的容器就是cookie
,因此当我们完全禁掉浏览器的cookie
的时候,服务端的session
也会不能正常使用。
服务器使用session
把用户的信息临时保存在了服务器上,用户离开网站后session
会被销毁。这种用户信息存储方式相对cookie
来说更安全。
这种方式一般存在一些问题:
1. Seesion
:每次认证用户发起请求时,服务器需要去创建一个记录来存储信息。当越来越多的用户发请求时,内存的开销也会不断增加。
2. 可扩展性:在服务端的内存中使用 Seesion
存储登录信息,伴随而来的是可扩展性问题。如果web
服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候session
会丢失。
3. CORS
(跨域资源共享):当我们需要让数据跨多台移动设备上使用时,跨域资源的共享会是一个让人头疼的问题。在使用Ajax
抓取另一个域的资源,就可以会出现禁止请求的情况。
4. CSRF
(跨站请求伪造):用户在访问银行网站时,他们很容易受到跨站请求伪造的攻击,并且能够被利用其访问其他的网站。
基于 token 的认证方式
基于 Token 的身份验证的特点是:
1. 无状态、可扩展(跨程序,跨端调用);在客户端存储的Tokens
是无状态的,并且能够被扩展。基于这种无状态和不存储Session
信息,负载均衡器能够将用户信息从一个服务传到其他服务器上。而不用去担心用户是否登录。tokens
自己保存了用户的验证信息。
4. 安全;请求中发送token
而不再是发送cookie
能够防止CSRF
(跨站请求伪造)。即使在客户端使用cookie
存储token
,cookie
也仅仅是一个存储机制而不是用于认证。不将信息存储在Session
中,让我们少了对session
操作。token
是有时效的,一段时间之后用户需要重新验证。
使用基于 Token
的身份验证方法,在服务端不需要存储用户的登录记录。基于Token的身份验证的过程:
1. 用户通过用户名和密码发送请求。
2. 服务器端程序验证。
3. 服务器端程序根据用户id
、用户名、定义好的秘钥、过期时间返回一个带签名的token
给客户端。
4. 客户端储存token,
比如放在 Cookie
里或者 Local Storage
里,并且每次访问API
判断 localStroage
有无 token,
没有则跳转到登录页,有则在请求头里携带 Token
到服务器端,请求获取用户信息,改变登录状态。
5. 服务端验证token,
校验成功则返回请求数据;没有或者 token
过期校验失败则返回错误码 401,
重定向到登录页面。
export interface User {
token: string;
userInfo: UserInfo | any;
companyInfo: CompanyInfo | any;
resources?: string[];
}
save(key: string, value: any, storageType ?: StorageType) {
return this.storageService.put(
{
pool: key,
key: 'chris-app',
storageType: StorageType.localStorage
},
value
);
}
this.storageService.save(CACHE_USER_KEY, user);
HttpInterceptor => headers = headers.set('token', this.authService.getToken());
HttpInterceptor =>
401: '用户登陆状态失效,请重新登陆。'
有效期如何设置?
无论是从安全的角度考虑,还是从吊销的角度考虑,Token
都需要设有效期,处于安全考虑,尽可能短。Token
过期失效,就需要用户重新登录,体验就不太好,才有了Refresh Token的方案。
服务端不需要刷新 Token
的过期时间,一旦 Token
过期,就反馈给前端,前端使用 Refresh Token
申请一个全新Token
继续使用。这种方案中,服务端只需要在客户端请求更新 Token
的时候对 Refresh Token
的有效性进行一次检查,大大减少了更新有效期的操作,也就避免了频繁读写。当然 Refresh Token
也是有效期的,但是这个有效期就可以长点,比如,以天为单位的时间。
使用 Token
和 Refresh Token
的时序图如下:
1)登录
2)业务请求
3)Token
过期,通过 refresh Token 申请刷新 Token
上面的时序图中并未提到 Refresh Token
过期怎么办。Refresh Token
既然已经过期,无可厚非该要求用户重新登录了。