# 认证授权设计
# 认证和授权的区别?
这两个一般在我们的系统中被结合在一起使用,目的就是为了保护我们系统的安全性 Authentication(认证) 是验证您的身份的凭据(例如用户名/用户 ID 和密码),通过这个凭据,系统得以知道你就是你,也就是说系统存在你这个用户。 所以,Authentication 被称为身份/用户验证。 Authorization(授权) 发生在 Authentication(认证) 之后。授权嘛,光看意思大家应该就明白,它主要掌管我们访问系统的权限。 比如有些特定资源只能具有特定权限的人才能访问比如 admin,有些对系统资源操作比如删除、添加、更新只能特定人才具有。
说简单点就是:
- 认证 (Authentication): 你是谁。
- 授权 (Authorization): 你有权限干什么。
# 权限系统模型
# RBAC 模型
什么是 RBAC 呢?RBAC 即基于角色的权限访问控制(Role-Based Access Control)。这是一种通过角色关联权限,角色同时又关联用户的授权的方式。 简单地说:一个用户可以拥有若干角色,每一个角色又可以被分配若干权限,这样就构造成“用户-角色-权限” 的授权模型。 在这种模型中,用户与角色、角色与权限之间构成了多对多的关系,如下图:
当使用 RBAC模型 时,通过分析用户的实际情况,基于共同的职责和需求,授予他们不同角色。这种 用户 -> 角色 -> 权限 间的关系, 让我们可以不用再单独管理单个用户权限,用户从授予的角色里面获取所需的权限。以一个简单的场景(Gitlab 的权限系统)为例, 用户系统中有 Admin、Maintainer、Operator 三种角色,这三种角色分别具备不同的权限, 比如只有 Admin 具备创建代码仓库、删除代码仓库的权限,其他的角色都不具备。我们授予某个用户 Admin 这个角色, 他就具备了 创建代码仓库 和 删除代码仓库 这两个权限。通过 RBAC模型 ,当存在多个用户拥有相同权限时, 我们只需要创建好拥有该权限的角色,然后给不同的用户分配不同的角色,后续只需要修改角色的权限,就能自动修改角色内所有用户的权限。
# ABAC 模型
基于属性的访问控制(Attribute-Based Access Control,简称 ABAC) 是一种比 RBAC模型 更加灵活的授权模型, 它的原理是通过各种属性来动态判断一个操作是否可以被允许。这个模型在云系统中使用的比较多,比如 AWS,阿里云等。
考虑下面这些场景的权限控制:
- 授权某个人具体某本书的编辑权限
- 当一个文档的所属部门跟用户的部门相同时,用户可以访问这个文档
- 当用户是一个文档的拥有者并且文档的状态是草稿,用户可以编辑这个文档
- 早上九点前禁止 A 部门的人访问 B 系统
- 在除了上海以外的地方禁止以管理员身份访问 A 系统
用户对 2022-06-07 之前创建的订单有操作权限可以发现上述的场景通过 RBAC模型 很难去实现, 因为 RBAC模型 仅仅描述了用户可以做什么操作,但是操作的条件,以及操作的数据,RBAC模型 本身是没有这些限制的。但这恰恰是 ABAC模型 的长处,ABAC模型 的思想是基于用户、访问的数据的属性、以及各种环境因素去动态计算用户是否有权限进行操作。
在 ABAC模型 中,一个操作是否被允许是基于对象、资源、操作和环境信息共同动态计算决定的。 对象:对象是当前请求访问资源的用户。用户的属性包括 ID,个人 资源,角色,部门和组织成员身份等资源:资源是当前用户要访问的资产或对象,例如文件,数据,服务器,甚至 API 操作:操作是用户试图对资源进行的操作。常见的操作包括“读取”,“写入”,“编辑”,“复制”和“删除” 环境:环境是每个访问请求的上下文。环境属性包含访问的时间和位置,对象的设备,通信协议和加密强度等
在 ABAC模型 的决策语句的执行过程中,决策引擎会根据定义好的决策语句,结合对象、资源、操作、环境等因素动态计算出决策结果。 每当发生访问请求时,ABAC模型 决策系统都会分析属性值是否与已建立的策略匹配。如果有匹配的策略,访问请求就会被通过。
# 权限面试问题总结
# Cookie与Session的作用
Cookie 和 Session 都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。
Cookie 存放在客户端,一般用来保存用户信息。我们在 Cookie 中保存已经登录过的用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了。 除此之外,Cookie 还能保存用户首选项,主题和其他设置信息。使用 Cookie 保存 SessionId 或者 Token , 向后端发送请求的时候带上 Cookie,这样后端就能取到 Session 或者 Token 了。这样就能记录用户当前的状态了,因为 HTTP 协议是无状态的。 Cookie 还可以用来记录和分析用户行为。举个简单的例子你在网上购物的时候,因为 HTTP 协议是没有状态的, 如果服务器想要获取你在某个页面的停留状态或者看了哪些商品,一种常用的实现方式就是将这些信息存放在 Cookie。
# Session-Cookie身份验证原理
很多时候我们都是通过 SessionID 来实现特定的用户,SessionID 一般会选择存放在 Redis 中。举个例子:用户成功登陆系统, 然后返回给客户端具有 SessionID 的 Cookie 。当用户向后端发起请求的时候会把 SessionID 带上,这样后端就知道你的身份状态了。
- 用户向服务器发送用户名、密码、验证码用于登陆系统。
- 服务器验证通过后,服务器为用户创建一个 Session,并将 Session 信息存储起来。
- 服务器向用户返回一个 SessionID,写入用户的 Cookie。
- 当用户保持登录状态时,Cookie 将与每个后续请求一起被发送出去。
- 服务器可以将存储在 Cookie 上的 SessionID 与存储在内存中或者数据库中的 Session 信息进行比较,以验证用户的身份,返回给用户客户端响应信息的时候会附带用户当前的状态。
使用 Session 的时候需要注意下面几个点:
- 依赖 Session 的关键业务一定要确保客户端开启了 Cookie。
- 注意 Session 的过期时间。
# 分布式服务Session-Cookie设计
Session-Cookie 方案在单体环境是一个非常好的身份认证方案。但是,当服务器水平拓展成多节点时,Session-Cookie 方案就要面临挑战了。 举个例子:假如我们部署了两份相同的服务 A,B,用户第一次登陆的时候 ,Nginx 通过负载均衡机制将用户请求转发到 A 服务器, 此时用户的 Session 信息保存在 A 服务器。结果,用户第二次访问的时候 Nginx 将请求路由到 B 服务器, 由于 B 服务器没有保存 用户的 Session 信息,导致用户需要重新进行登陆。
我们应该如何避免上面这种情况的出现呢?有几个方案可供大家参考:
- 某个用户的所有请求都通过特性的哈希策略分配给同一个服务器处理。这样的话,每个服务器都保存了一部分用户的 Session 信息。服务器宕机,其保存的所有 Session 信息就完全丢失了。
- 每一个服务器保存的 Session 信息都是互相同步的,也就是说每一个服务器都保存了全量的 Session 信息。每当一个服务器的 Session 信息发生变化, 我们就将其同步到其他服务器。这种方案成本太大,并且,节点越多时,同步成本也越高。
- 单独使用一个所有服务器都能访问到的数据节点(比如缓存)来存放 Session 信息。为了保证高可用,数据节点尽量要避免是单点。
如果没有 Cookie 的话 Session 还能用吗
一般是通过 Cookie 来保存 SessionID ,假如你使用了 Cookie 保存 SessionID 的方案的话, 如果客户端禁用了 Cookie,那么 Session 就无法正常工作。但是,并不是没有 Cookie 之后就不能用 Session 了, 比如你可以将 SessionID 放在请求的 url 里面https://javaguide.cn/?Session_id=xxx 。这种方案的话可行, 但是安全性和用户体验感降低。当然,为了你也可以对 SessionID 进行一次加密之后再传入后端。
为什么Cookie 无法防止CSRF攻击,而Token可以
CSRF(Cross Site Request Forgery) 一般被翻译为跨站请求伪造 。那么什么是 跨站请求伪造呢?
说简单用你的身份去发送一些对你不友好的请求。举个简单的例子: 小壮登录了某网上银行,他来到了网上银行的帖子区,看到一个帖子下面有一个链接写着“科学理财,年盈利率过万”,小壮好奇的点开了这个链接, 结果发现自己的账户少了 10000 元。这是这么回事呢?原来黑客在链接中藏了一个请求,这个请求直接利用小壮的身份给银行发送了一个转账请求, 也就是通过你的 Cookie 向银行发出请求。
<a src=http://www.mybank.com/Transfer?bankId=11&money=10000>科学理财,年盈利率过万</>
上面也提到过,进行 Session 认证的时候,我们一般使用 Cookie 来存储 SessionId,当我们登陆后后端生成一个 SessionId 放在 Cookie 中返回给客户端 ,服务端通过 Redis 或者其他存储工具记录保存着这个 SessionId,客户端登录以后每次请求都会带上这个 SessionId,服务端通过这个 SessionId 来标示你这个人。 如果别人通过 Cookie 拿到了 SessionId 后就可以代替你的身份访问系统了。
Session 认证中 Cookie 中的 SessionId 是由浏览器发送到服务端的,借助这个特性,攻击者就可以通过让用户误点攻击链接,达到攻击效果。
但是,我们使用 Token 的话就不会存在这个问题,在我们登录成功获得 Token 之后,一般会选择存放在 localStorage (浏览器本地存储)中。 然后我们在前端通过某些方式会给每个发到后端的请求加上这个 Token,这样就不会出现 CSRF 漏洞的问题。因为, 即使有个你点击了非法链接发送了请求到服务端,这个非法请求是不会携带 Token 的,所以这个请求将是非法的。
需要注意的是:不论是 Cookie 还是 Token 都无法避免 跨站脚本攻击(Cross Site Scripting)XSS
# JWT认证方案
JWT (JSON Web Token) 是目前最流行的跨域认证解决方案,是一种基于 Token 的认证授权机制。 从 JWT 的全称可以看出,JWT 本身也是 Token, 一种规范化之后的 JSON 结构的 Token。JWT 自身包含了身份验证所需要的所有信息,因此,我们的服务器不需要存储 Session 信息。 这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。
JWT 本质上就是一组字串,通过(.)切分成三个为Base64编码的部分:
- Header : 描述 JWT 的元数据,定义了生成签名的算法以及 Token 的类型。
- Payload : 用来存放实际需要传递的数据
- Signature(签名):服务器通过 Payload、Header 和一个密钥(Secret)使用 Header 里面指定的签名算法(默认是 HMAC SHA256)生成。
# JWT进行身份验证原理
在基于 JWT 进行身份验证的的应用程序中,服务器通过 Payload、Header 和 Secret(密钥)创建 JWT 并将 JWT 发送给客户端。 客户端接收到 JWT 之后,会将其保存在 Cookie 或者 localStorage 里面,以后客户端发出的所有请求都会携带这个令牌。
- 简化后的步骤如下:用户向服务器发送用户名、密码以及验证码用于登陆系统。
- 如果用户用户名、密码以及验证码校验正确的话,服务端会返回已经签名的 Token,也就是 JWT。
- 用户以后每次向后端发请求都在 Header 中带上这个 JWT 。
- 服务端检查 JWT 并从中获取用户相关信息。
- 建议将 JWT 存放在 localStorage 中,放在 Cookie 中会有 CSRF 风险。
- 请求服务端并携带 JWT 的常见做法是将其放在 HTTP Header 的 Authorization 字段中(Authorization: Bearer Token)。
# 如何防止JWT被篡改
有了签名之后,即使 JWT 被泄露或者截获,黑客也没办法同时篡改 Signature、Header、Payload。 这是为什么呢?因为服务端拿到 JWT 之后,会解析出其中包含的 Header、Payload 以及 Signature 。 服务端会根据 Header、Payload、密钥再次生成一个 Signature。拿新生成的 Signature 和 JWT 中的 Signature 作对比, 如果一样就说明 Header 和 Payload 没有被修改。不过,如果服务端的秘钥也被泄露的话, 黑客就可以同时篡改 Signature、Header、Payload 了。黑客直接修改了 Header 和 Payload 之后,再重新生成一个 Signature 就可以了。 密钥一定保管好,一定不要泄露出去。JWT 安全的核心在于签名,签名安全的核心在密钥。
使用安全系数高的加密算法。使用成熟的开源库,没必要造轮子。
- JWT 存放在 localStorage 中而不是 Cookie 中,避免 CSRF 风险。
- 一定不要将隐私信息存放在 Payload 当中。
- 密钥一定保管好,一定不要泄露出去。JWT 安全的核心在于签名,签名安全的核心在密钥。
- Payload 要加入 exp (JWT 的过期时间),永久有效的 JWT 不合理。并且,JWT 的过期时间不易过长。