1. Cookie 和 Session
HTTP 协议是一种无状态协议,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录;Session 和 Cookie 的主要目的就是为了弥补 HTTP 的无状态特性。
1.1 Cookie是什么
Cookie,它是客户端浏览器用来保存服务端数据的一种机制。
当通过浏览器进行网页访问的时候,服务器可以把某一些状态数据以 key-value的方式写入到 Cookie 里面存储到客户端浏览器。
然后客户端下一次再访问服务器的时候,就可以携带这些状态数据发送到服务器端,服务端可以根据 Cookie 里面携带的内容来识别使用者。
Cookie 主要用于下面三个目的:
- 会话管理:登陆、购物车、游戏得分或者服务器应该记住的其他内容
- 个性化:用户偏好、主题或者其他设置
- 追踪:记录和分析用户行为
主要过程可以简单用下图表示:
大致分为以下几步:
- 客户端发送请求到服务端(比如登录请求)。
- 服务端收到请求后生成一个 session 会话。
- 服务端响应客户端,并在响应头中设置 Set-Cookie。Set-Cookie 里面包含了 sessionId,它的格式如下:Set-Cookie: value[; expires=date][; domain=domain][; path=path][; secure]。其中 sessionId 就是用来标识客户端的。
- 客户端收到该请求后,如果服务器给了 Set-Cookie,那么下次浏览器就会在请求头中自动携带 cookie。
- 客户端发送其它请求,自动携带了 cookie,cookie 中携带有用户信息等。
- 服务端接收到请求,验证 cookie 信息,比如通过 sessionId 来判断是否存在会话,存在则正常响应。
1.2 Session是什么
Session 表示一个会话,它是属于服务器端的容器对象。
默认情况下,针对每一个浏览器的请求,Servlet 容器都会分配一个 Session。
Session 本质上是一个 ConcurrentHashMap,可以存储当前会话产生的一些状态数据。
session 由服务端创建,当一个请求发送到服务端时,服务器会检索该请求里面有没有包含 sessionId 标识,如果包含了 sessionId,则代表服务端已经和客户端创建过 session,然后就通过这个 sessionId 去查找真正的 session,如果没找到,则为客户端创建一个新的 session,并生成一个新的 sessionId 与 session 对应,然后在响应的时候将 sessionId 给客户端,通常是存储在 cookie 中。如果在请求中找到了真正的 session,验证通过,正常处理该请求。
session机制采用的是在服务器端保持 HTTP 状态信息的方案。为了加速session的读取和存储,web服务器中会开辟一块内存用来保存服务器端所有的session,每个session都会有一个唯一标识sessionid,根据客户端传过来的sessionid(cookie中),找到对应的服务器端的session。为了防止服务器端的session过多导致内存溢出,web服务器默认会给每个session设置一个有效期, (30分钟)若有效期内客户端没有访问过该session,服务器就认为该客户端已离线并删除该session。
整个请求过程图,如下所示:
1.2.1 Session 如何判断是否是同一会话?
服务器第一次接收到请求时,开辟了一块 Session 空间(创建了Session对象),同时生成一个 sessionId ,并通过响应头的 Set-Cookie:JSESSIONID=XXXXXXX 命令,向客户端发送要求设置 Cookie 的响应; 客户端收到响应后,在本机客户端设置了一个 JSESSIONID=XXXXXXX的 Cookie 信息,该 Cookie 的过期时间为浏览器会话结束;
接下来客户端每次向同一个网站发送请求时,请求头都会带上该 Cookie信息(包含 sessionId ), 然后,服务器通过读取请求头中的 Cookie 信息,获取名称为 JSESSIONID 的值,得到此次请求的 sessionId。
1.3 Cookie与Session的在请求中的工作流程
(1)客户端第一次访问服务端的时候,服务端会针对这次请求创建一个会话,并生成一个唯一的 sessionID 来标注这个会话。
(2)然后服务端把这个 sessionID 写入到客户端浏览器的 cookie 里面,用来实现客户端状态的保存。
(3)在后续的请求里面,每次都会携带sessionID,服务器端就可以根据这个sessionID 来识别当前的会话状态。
所以,总的来说,Cookie 是客户端的存储机制,Session 是服务端的存储机制。
1.4 Cookie与Session存在问题
当项目的并发量越来越大的时候,我们一台服务器不够,想要有多台Tomcat来部署一个Tomcat集群时,由于Session是不能共享的,所以该场景不适用!
如果是前后端分离的项目,即前端代码和后端代码部署在不同的服务器上时,也是不可以使用Session进行登入的!
1.5 Token是什么
Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。
token 的组成:
token 是一串被加密后字符串,它通常使用 uid(用户唯一标识)、时间戳、签名以及一些其它参数加密而成。我们将 token 进行解密就可以拿到像 uid 这类的信息,然后通过 uid 来进行接下来的鉴权操作。
token 认证流程:
- 客户端发起登录请求,比如用户输入用户名和密码后登录。
- 服务端校验用户名和密码后,将用户 id 和一些其它信息进行加密,生成 token。
- 服务端将 token 响应给客户端。
- 客户端收到响应后将 token 存储下来。
- 下一次发送请求后需要将 token 携带上,比如放在请求头中或者其它地方。
- 服务端 token 后校验,校验通过则正常返回数据。
用图表示大致如下:
1.5.1 为什么要有token?
- Token 可以是无状态的,可以在多个服务间共享
- Token 可以避免 CSRF攻击(跨站点请求伪造)
- 减轻服务器压力。通常session是存储在内存中的,每个用户通过认证之后都会将session数据保存在服务器的内存中,而当用户量增大时,服务器的压力增大。
- Token完全由应用管理,避开同源策略。
1.6 Cookie和Session的区别
(1)存储位置不同
- cookie在客户端浏览器;
- session在服务器;
(2)存储容量不同
- cookie<=4K,一个站点最多保留20个cookie;
- session没有上线,出于对服务器的保护,session内不可存过多东西,并且要设置session删除机制;
(3)存储方式不同
- cookie只能保存ASCII字符串,并需要通过编码方式存储为Unicode字符或者二进制数据;
- session中能存储任何类型的数据,包括并不局限于String、integer、list、map等;
(4)隐私策略不同
- cookie对客户端是可见的,不安全;
- session存储在服务器上,安全;
(5)有效期不同
- 开发可以通过设置cookie的属性,达到使cookie长期有效的效果;
- session依赖于名为JESSIONID的cookie,而cookie JSESSIONID的过期时间默认为-1,只需关闭窗口该session就会失效,因而session达不到长期有效的效果;
(6)跨域支持上不同
- cookie支持跨域;
- session不支持跨域;
1.7 Token和Session的区别
1)session是一种记录服务器和客户端会话状态的机制,使服务端有状态化。而token是令牌,访问资源接口(API时)所需要的认证资源,token使服务器无状态化,不会存储会话信息。
2)session和token并不矛盾,作为身份认证token比session安全性更好,因为每一个请求都有签名,还能防止监听以及重放攻击,而session就必须依赖链路层来确保通讯安全了。
1.8 总结
2. 什么是单点登录
单点登录的英文名叫做:Single Sign On(简称SSO),指在同一帐号平台下的多个应用系统中,用户只需登录一次,即可访问所有相互信任的系统。简而言之,多个系统,统一登陆。
2.1 单点登录原理
sso需要一个独立的认证中心,所有子系统都通过认证中心的登录入口进行登录,登录时带上自己的地址,子系统只接受认证中心的授权,授权通过令牌(token)实现,sso认证中心验证用户的用户名密码正确,创建全局会话和token,token作为参数发送给各个子系统,子系统拿到token,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。
3. JWT落地的SSO
3.1 什么是JWT
具体可参见:https://jwt.io/
JSON Web Token (JWT) 是一个开源标准(RFC 7519),它定义了一种紧凑且自完备的方法用于在各参与方之间以JSON对象传递信息。以该种方式传递的信息已经被数字签名,因而可以被验证并且被信任。它是目前最流行的跨域身份验证解决方案。它本质是token票据,在认证登录过程中表明用户的登录标识。JWT既可以使用盐(secret)(HMAC算法)进行签名,也可以使用基于RSA/ECDSA算法的公钥/秘钥对进行签名。
3.2 为什么要使用JWT?
JWT的精髓在于:“去中心化”,就是数据保存在各个客户端而不是服务器
3.3 JWT认证登录流程
- 用户直接访问资源服务器,未登录状态
- 资源服务器校验JWT 失败,返回未登录信息
- 访问登录 提交username password
- 登录认证服务器校验用户信息,
- 校验用户名是否存在
- 校验密码是否正确
- 校验其他必要条件:锁定 开启 过期等
- 返回JWT,其中jwt携带信息
- 用户名
- 用户id
- 用户权限
- 用户浏览器携带JWT访问资源服务器
- 资源服务器校验JWT
- 解析用户信息
- 解析用户权限
- 判断授权是否允许
- 数据返回
- 如果解析成功授权成功返回正确数据json
- 如果解析成功授权失败返回权限不足
- 如果解析失败返回未认证
3.4 JWT作用
- JWT作用是什么(数据格式三部分 头,有效载荷,签名)
参考:- 携带用户的身份信息,一般包括id username.但是不会携带一些敏感信息(password不会携带),例如:
sub:“{‘userId’:‘1’,‘username’,‘王翠花’}” - 保证携带的信息不会在传输过程中遭到篡改(解析端使用同样的算法对比签名)
- 携带用户的身份信息,一般包括id username.但是不会携带一些敏感信息(password不会携带),例如:
- JWT携带的信息是什么(网络传输的数据,所以他不易过大)
- userId(必带)
- username(展示哪个带哪个)
- nickname(展示哪个带哪个,一般都是nickname)
- autorities:用户的权限信息(取决于权限系统是否独立)
- JWT在传输过程中泄露传递信息怎么办
参考: JWT本身不是加密安全的,基本上是明文的传递,如果需要信息安全,可以对JWT在生成时进行加密,在解析时进行解密操作.(RSA非对称加密.生成jwt一端加密,解析jwt一端去解密,外界破解的可能性微乎其微)
授权系统
3.5 授权流程
和没有授权系统对比,加入授权系统的流程仅仅是在单体架构系统拿到JWT之后有变化
- 解析JWT 获取用户信息,携带userId访问授权系统
- 授权系统根据userId从数据库或者缓存拿到权限信息
- 这时候的权限如果是从数据库读的就是实时的,如果是引入缓存需要考虑缓存一致性问题
- 授权系统返回权限信息
- 单体架构系统授权判断
- 根据判断结果返回给用户数据
- 授权通过,返回正确数据
- 授权失败,返回权限不足信息
3.6 JWT结构
Json Web Token由以下三部分组成:
- Header(头部)
- Payload(载荷)
- Signature(签名)
一个典型的JWT形式如:xxx.yyy.zzz,由两个点.间隔。
3.6.1 Header
典型的JWT header包含两个部分:(1) token类型,即JWT。(2) 所使用的签名算法,比如HMAC SHA256或者RSA。
{
"alg": "HS256",
"typ": "JWT"
}
该JSON以Base64Url加密(可以被对称解密)后形成了JWT的第一部分:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
3.6.2 Payload
JWT token的第二部分是包含了声明的payload,声明是一个实体的表述加上额外信息,一共有三种形式的声明:注册、共有和私有。
- 注册声明(建议但不强制):
- iss: jwt签发者
- sub: jwt使用者
- aud: jwt接收者
- exp: jwt过期时间,该时间必须大于签发时间
- nbf: 定义在某个时间之前,该jwt都是不可用的
- iat: jwt签发时间
- jti: jwt唯一身份标注,主要用于一次性token,从而避免重放攻击
(注:为了保持紧凑,注册声明都是三个字母)
- 公有声明:
公有声明可以加入任何信息,一般会添加用户相关信息或者业务需要的信息,但不建议添加敏感信息,因为该部分会在客户端解密。 - 私有声明:
私有声明是提供者和使用者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密,基本等同于明文信息。
{
"iss":"#发行人",
"exp":"#到期时间",
"sub":"主题(自定义数据)",
"aud":"用户",
"nbf":"在此之前不可用",
"iat":"发布时间"
}
然后对其进行base64加密,得到JWT的第二部分:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
3.6.3 Signature
JWT的第三部分是签名信息,Signature由三部分组成:
- header(base64加密后)
- payload(base64加密后)
- secret (盐)
Signature需要将base64加密后的header和payload使用.连接,然后通过header所使用的加密方式进行加盐(secret)组合加密,产生了jwt的第三部分。以使用HMAC SHA256算法为例,signature以如下方式产生:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
Signature被用于验证信息在传输过程中没有被更改,或者在token以私钥加密的条件下,也可以验证JWT的发送者是否是其所自称的身份。
注:secret保存在服务端,jwt的签发也发生在服务端,secret是用来进行jwt的签发和验证的。因为,它就是服务端的私钥,任何场景下都不应该被泄露出去。
如图是一个完整的jwt token示例:
3.7 JWT认证的优势
- 简洁:JWT Token数据量小,传输速度也很快
- 因为JWT Token是以JSON加密形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持
- 不需要在服务端保存会话信息,也就是说不依赖于cookie和session,所以没有了传统session认证的弊端,特别适用于分布式微服务
- 单点登录友好:使用Session进行身份认证的话,由于cookie无法跨域,难以实现单点登录。但是,使用token进行认证的话, token可以被保存在客户端的任意位置的内存中,不一定是cookie,所以不依赖cookie,不会存在这些问题
- 适合移动端应用:使用Session进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到Cookie(需要 Cookie 保存 SessionId),所以不适合移动端
因为这些优势,目前无论单体应用还是分布式应用,都更加推荐用JWT token的方式进行用户认证