前端登录方案
背景:http协议的无状态特点,使得我们需要发送很多用于身份验证的网络请求。所以我们可以利用cookie或者token将客户端的身份信息保存起来,减少身份验证的网络请求次数,提高网络请求的效率。
1、Cookie+Session
session是服务器记录用户身份状态的一种机制。每个session对象由唯一的session Id标识,存放对应用户的信息,如账号密码等。
cookie是服务器发传给客户端的一个数据块,里面可以存放用户的身份信息或者用户的其他信息等。
具体实现登录过程
当用户输入账号密码后,客户端向服务器发起登录请求时,服务器会开辟一块内存空间,创建一个session对象,生成唯一标识session id,把用户的信息存到当前session对象中,并在响应头部加上set-cookie字段,将当前用户的session id传给客户端。客户端收到响应报文后,将session id存储到浏览器本地缓存的cookie中。之后当客户端发送网络请求的时候浏览器会自动在请求头部中添加相应的cookie值,服务端根据cookie值验证用户信息,实现用户鉴权
cookie除了身份信息还能存什么?
1、当前用户设置的语言、主题颜色,大小等个性化设置信息;
2、用户最近浏览记录,方便app对用户进行大数据推送;
3、用户浏览的实际情况,有利于后台分析用户数据等..
cookie的常见属性
name=value,设置表示当前cookie的键值对
domain,设置可以接受当前cookie的域名,默认为发送cookie的服务器的域名
size,设置cookie的大小,最好小于4kb
path,设置路径,配合domain设置cookie接受的范围
expires,max-age:设置cookie的有效期。max-age的值为时间戳,表示还有几秒过期,而expires的值为绝对时间,可能会因为时区问题造成误差,所以max-age优先级高
secure:规定当前cookie只能在https协议中使用,因为cookie的内容是透明可见的,被窃取之后直接就可以被盗用了。所以需要https提供安全保障
httponly:规定cookie只能用于http协议,严格限制cookie不能别js脚本获取到,防止XSS攻击
samesite:可以规定第三方网站是否可以发送携带cookie的网络请求,防止CXRF攻击。
strict:严格限制cookie的发送对象,不允许cookie随跨站请求发送。
none:不限制cookie,也就是说cookie可以随任何跨站请求发送。必须在设置了secure的情况才生效,不然双重debuff(http+csrf)就太危险了。
lax:介于两者之间,cookie可以随某些跨站请求发送。
组合拳
一般用法就是cookie+secure+httpOnly+sameSite:none
缺点
1、服务器需要维护很多的session id,服务器压力大
2、服务器如果是一个集群,那么所有的服务器都需要有一份session id的副本,也是很浪费资源
3、CSRF攻击风险大
4、cookie有跨域问题,出于安全考虑,浏览器会自动截获携带cookie的跨域请求报文,防止cookie泄露。
cookie怎么解决跨域问题
1、CORS,设置access-control-allow-origin和access-control-allow-credentials等头部信息,设置允许访问资源的白名单,并允许发送cookie。这样就可以发送携带cookie的跨域请求
2、jsonp,利用script等标签不受同源策略的影响,我们在script的src属性也就是在请求地址中添加一个回调函数并发送给服务器,服务器识别提取回调函数并将所需的数据作为函数参数,最后返回当前回调函数,客户端调用回调函数就可以实现跨域请求资源
3、反向代理,配置反向代理服务器,利用服务器与服务器之间无跨域问题,客户端只需要和代理服务器打交道即可,这个过程中隐藏了目标服务器,代理服务器转发客户端的请求以及返回目标服务器的数据,使得客户端可以访问跨域资源
2、token
token是服务器通过特定算法生成的一段随机字符串,作为客户端身份验证的令牌。客户端通过发送携带token令牌的网络请求实现用户鉴权。
具体实现登录过程
客户端输入账号密码后,调用登陆接口,服务端收到请求后就会生成一个token值返回给客户端。客户端可以把token保存在localstrage或者cookie中。当客户端访问需要身份验证的页面时,在请求头部设置Authorization字段为token值,服务器收到请求后根据token值验证客户端身份,实现用户鉴权。
token生成方式
json web token,JWT就是一种常见的生成token令牌的方式。
token数据结构
头部header:设置加密算法和令牌类型
{
"alg": "HS256",
"typ": "JWT"
}
负载payload:设置存储的具体内容,以键值对的形式存储。key值有自定义声明和标准声明
{
"username": "ymitc", //用户名
"id": "123456", //账号
"iat": 1516239022 //令牌签发时间
}
签名signature:使用加密算法加密负载内容,服务器就是解析签名对客户端进行身份验证的
//默认算法
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)//secret是我们自定义的对称密钥
jwt生成的token格式如下:采用Base64编码和点号分割的形式。这就意味着我们的头部和负载一旦泄露能被解析出来,所以不要在头部和负载放一些敏感信息。强调一下,token的作用作身份识别而已,理解成就是一张校园卡,然后你会在你的校园卡上写你的银行卡密码吗?
<Header>.<Payload>.<Signature>
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
常见加密算法
1、HMAC:基于哈希值利用对称密钥进行加密解密token的签名,简单高效
2、RSA:使用公钥密钥进行加密解密,更安全但费时间
3、ECDSA:引入椭圆曲线公钥,类似于RSA
token的优点
1、使用session id鉴权服务器压力比较大,而使用token不会对服务器造成负担。token值是基于用户的账号密码等身份信息加密的,所以服务器只需要解析客户端发送token就可以验证客户端的身份,有点时间换空间内味了。
2、token没有跨域问题,因为token只是一个普通请求头部字段的值而已,并且token本身就是加密过的,容错性高。
3、token存储地方比较灵活不受限制
token的缺点
一旦服务器发出token了,只要在有效期内,token就一直有效,服务器回收就比较麻烦,比如如何实现后台强制用户下线,一个想法就是数据库维护一个jwt表,黑名单白名单都行,可以对发出去的token进行增删操作。那不就成session了???
因此我们可以设置token的有效期,但如果token过期了,用户需要重新登录发起请求验证身份这样用户体验不好。服务器设置发送两个token,access token用于身份验证(有效期短),refresh token(有效期长)的作用就是当token失效时,客户端可以发送携带refresh token的请求向服务端请求一个新的access token,实现无感刷新,提高用户体验
无感刷新
实现过程:
用户调用登录接口,客户端向服务端发起身份验证请求,服务端返回两个token,access-token是客户端访问服务端的凭证,有效期比较短(考虑到安全性)。而refresh-token是客户端向服务端获取access token的凭证,有效期会比较长。
当客户端发出请求时,服务器返回401状态码,说明当前token值过期了或者无效。实际场景中我们可以通过设置axios的响应拦截器,在响应报文到达浏览器之前截获并做出分析,若状态码为401时,客户端携带refresh-token向服务器发起请求,服务器返回一个新的access-token以及对应的refresh-token,这样就能保证每个token只使用一次。然后在拦截器里再次发送客户端的请求,只是这次携带的是有效的token。
这里有一个细节,当我们携带过期token的请求很多时,因为是异步请求,所以我们引入Promise对象,来实现异步。用数组收集所有的回调,也就是所有的网络请求,待新token返回了我们再遍历执行所有回调函数。可以理解为发布订阅者模式
具体实现:segmentfault.com/a/119000001…
token存localStorage还是cookie好
1、存loaclStorage或者sessionStorage
我们会在请求头部的Authorization设置token值,但是由于js可以访问localStorage和sessionstorage,容易收到xss攻击,另外还需要应用程序保证token能在https下传输
2、存cookie
我们会在响应头部设置set-cookie:token,httponly,secure; secure设置当前为https协议以及设置httponly防止js获取cookie值,避免xss攻击。但因为cookie会被浏览器自动添加到请求头,容易受到csrf攻击(可以在服务器端检查 Refer 和 Origin)
3、SSO单点登录
核心:一次登录,全线通用。比如我们在百度首页登录了账号后,我们进入百度网盘就不需要再登录帐号了。
客户端与服务器
具体过程
1、客户端请求访问系统A,判断有无认证中心SSO凭证,若无则需要用户登录,登录成功返回ticket以及SSO凭证
2、客户端携带ticket请求访问系统A,系统A带着ticket去SSO验证ticket,如果ticket有效,则请求成功,系统A返回数据
3、客户端请求访问系统B,此时有SSO凭证,所以不需要用户再次登录,认证中心返回ticket
4、客户端携带ticket请求访问B,系统B带着ticket去SSO验证ticket,如果ticket有效,则请求成功,系统B返回数据
浏览器与服务器
因为浏览器受同源策略影响,所以当浏览器把SSO.com的ticket发送给A.com时会出现跨域问题。解决方法就是系统各自的ticket自己保存,浏览器利用认证中心发放的code值换取ticket
具体过程:
1、客户端请求访问系统A,判断有无认证中心SSO凭证,若无则需要用户登录,登录成功返回带code的URL(定向到系统A)以及SSO凭证
2、客户端将code作为参数传入系统A的回调函数,并执行回调,整个过程其实就是利用code换取ticket值
3、客户端携带ticket请求访问A,系统A带着ticket去SSO验证ticket,如果ticket有效,则请求成功,系统A返回数据
4、客户端请求访问系统B,此时有SSO凭证,所以不需要用户再次登录,认证中心返回带code的URL(定向到系统B)以及SSO凭证
5、客户端拿到code后和系统A换取ticket值
6、客户端携带ticket请求访问B,系统B带着ticket去SSO验证ticket,如果ticket有效,则请求成功,系统B返回数据