Cookie
Cookie最主要的作用就是用于会话管理,当用户登录一个网站时,服务器会生成一个包含会话ID的Cookie并发送给浏览器,浏览器将这个Cookie保存在本地。此后,每次用户发送请求时,浏览器都会自动将这个Cookie发送给服务器,服务器通过会话ID识别用户身份,从而保持用户的登录状态。Cookie除了用于会话管理,还可以在指客户端持久存储,一般使用kv形式保存。
cookie属性
可以看一下浏览器持久化存储的cookie内容:
Name/value | 键值对,cookie的内容。 |
Domain | Domain决定Cookie在哪个域是有效的,也就是决定在向该域发送请求时是否携带此Cookie,Domain的设置是对子域生效的,如Doamin设置为 .a.com,则b.a.com和c.a.com均可使用该Cookie,但如果设置为b.a.com,则c.a.com不可使用该Cookie。Domain参数必须以点(“.”)开始。 |
Path | Cookie的有效路径,和Domain类似,也对子路径生效。 |
Expires/Max-Age | 用于指定Cookie的过期时间。 |
HttpOnly | 此属性指定Cookie只能通过HTTP协议访问。 |
Secure | Cookie的安全属性,若设置为true,则浏览器只会在HTTPS和SSL等安全协议中传输此Cookie,不会在HTTP协议中传输此Cookie。 |
SameSite | 该属性用于限制第三方Cookie,防止跨站请求伪造(CSRF)攻击。
浏览器一般会默认限制cookie不支持跨域,如果跨域需要配置合理的CORS策略,即配置白名单。 |
Session
当客户端第一次访问服务器的时候,此时客户端的请求中不携带任何标识给服务器,此时服务器会新建session标识,当服务器进行响应的时候,服务器会将session标识放到http响应头的Set-Cookie中,以key-value的形式返回给客户端,例:
SESSIONID=xxx;
客户在下次访问服务端的时候,将sessionid带上,服务端根据sessionid从本地内存获取客户会话信息。
由于会有越来越多的用户访问服务器,因此服务端的Session也会越来越多。为防止内存溢出,服务器会把长时间(默认30分钟)没有活跃的Session从内存删除。如果超过了超时时间没访问过服务器,Session就自动失效了,需要重新登录。
分布式session
SESSIONID仅存储在单机服务器会有单点问题,一般解决Session在多个服务器之间的共享问题有几种方法:
1、Sesson Replication
也就是每台Web服务器都保存所有用户的Session数据,网络开销大,性能低。
2、Sesson Sticky
基于Nginx的sticky模块,即让前置负载均衡器根据每次请求的ip/cookie来进行请求的转发,保证一个会话中的每次请求都能落到同一台服务器上面。
缺陷:如果某台服务器宕机,那么它上面存储的Session数据就丢失了,用户就需要重新进行登陆。
负载均衡器变为一个有状态的节点,因为他需要保存Session到具体服务器的映射,和之前无状态的节点相比,内存消耗会更大,容灾方面会更麻烦。
3、Sesson数据集群存储
集中存到外部缓存如Redis中,一般大型系统都会单独部署redis缓存集群。
Session和cookie一样都有可能遭到跨站请求伪造(CSRF)攻击,一般可以使用token解决,对于敏感操作可以双重验证,如用户登陆和资金转账使用独立密码。
Token
Token是身份验证和授权令牌,具有无状态性、跨域支持、安全性等优点,但需注意安全传输、失效管理等问题。Token安全策略包括使用HTTPS、加密存储、设置过期时间等。
常用的jwt token分为三部分:
1、header:指定签名算法和令牌类型。
2、payload:也是一个JSON对象,用于存放需要传递的数据,例如用户的信息uid、权限等,一般还会携带时间戳,用于防止重放攻击。
JWT推荐了payload中的7个可选官方字段:iss (签发人), exp (过期时间), aud (接收方)等,另外也可以自己协商私有字段。
3、Signature:签名,server根据header获取签名算法,再用公钥根据此签名算法对head+payload 生成签名,这样一个token就生成了。
客户端使用token执行会话流程
1、用户使用用户名和密码进行登录,系统验证用户身份成功后,生成一个Token并返回给客户端。
2、客户端将Token保存在本地,通常是存储在Cookie或LocalStorage中,在每次发送请求时在请求头带上token。
3、服务端验证token,通过后执行业务操作。
4、如果token非法,返回401给客户端,客户端需要执行登录操作。
服务端如何验证token
这里就要考虑token如何生成。Token既可以使用对称加密也可以使用非对称加密(JWT)生成。对称加密中Token 中通常只有 userId 和 timestamp,客户端无法解密token也无法往里面添加内容。非对称加密中客户端可以使用公钥解密token获取用户信息,也可以使用公钥打包添加额外信息。
为了安全起见,防止token被攻击者盗用,token 的有效期不会设置的太长,这样就会由于 token过期导致用户需要重新登录从而生成新的token。服务端验证token过期会返回401,前端重定向到登录页面。如何才能做到不需要用户去频繁的登录呢,有下面两种形式。
1、单token方案
2、双token方案,Refresh Token机制
Oauth2认证规范中把之前的那个Token称为Access Token,业务方用这个 Access Token进行认证鉴权。而Refresh Token是专门用来在Access Token过期后重新获取新的Access Token的Token。
Refresh Token的过期时间一般设置长一点
比如一天,Access Token的过期时间设置短一点比如30分钟,这样可以缩短Access Token的过期时间保证安全,同时又不会因为频繁过期重新要求用户登录。
Access Token每次访问都要携带,因此更容易被盗取;Refresh Token被盗的风险远小于Access Token。
Token失效与管理
Token是由客户端保存的,一旦服务器生成,它就一直有效,直到过期。换句话说,服务器无法主动让一个Token失效,除非将Token放入“黑名单”中,这样每次验证Token时都需要先检查这个黑名单。如果Token在黑名单里,就会被认为无效。但问题是,黑名单必须保存在服务器端,这意味着如果要支持服务器随时“踢掉”用户,就相当于将服务变成了“有状态”的管理,类似于Session模式。
所以,一般情况下,当客户端登出时,最简单的方法是直接删除本地存储的Token,用户下次登录时,服务器会重新生成一个新的Token。
对于大规模用户的系统,通常会部署独立的认证系统,并使用如Redis集群这种方式来存储Session,这样就能更方便地管理Token的过期和刷新问题。
通常,Token是存放在HTTP请求头中的Authorization字段,而不是放在Cookie里。这样做的主要原因是为了解决跨域请求时无法共享Cookie的问题。对于跨域问题,通常需要在服务器端配置允许来自所有域的请求,这可以通过设置Access-Control-Allow-Origin: *来实现。
基于token可以实现SSO单点登录,只需要各个服务器都使用同一个密钥对token进行加密解密即可。