认证授权基础概念详解(JavaGuide 转载)
转载自 JavaGuide 原文,作者:Guide 与 JavaGuide 贡献者。
依据 Apache-2.0 许可证收录,见固定快照。
本站移除推广与外部图片嵌入、调整技术链接,技术勘误在文内标注;配图位置的“本站补充”由 IT枫斗者编写,并非原图;文中“我”指原作者。
认证 (Authentication) 和授权 (Authorization)的区别是什么?
这是一个绝大多数人都会混淆的问题。首先先从读音上来认识这两个名词,很多人都会把它俩的读音搞混,所以我建议你先先去查一查这两个单词到底该怎么读,他们的具体含义是什么。
说简单点就是:
- 认证 (Authentication): 你是谁。
- 授权 (Authorization): 你有权限干什么。
稍微正式点(啰嗦点)的说法就是:
- Authentication(认证) 是验证您的身份的凭据(例如用户名/用户 ID 和密码),通过这个凭据,系统得以知道你就是你,也就是说系统存在你这个用户。所以,Authentication 被称为身份/用户验证。
- Authorization(授权) 发生在 Authentication(认证) 之后。授权嘛,光看意思大家应该就明白,它主要掌管我们访问系统的权限。比如有些特定资源只能具有特定权限的人才能访问比如 admin,有些对系统资源操作比如删除、添加、更新只能特定人才具有。
认证:
本站图解(非原图)
认证登录的文字示例。以下账号、操作和结果均为本站假设,不复刻原登录界面。转载正文作者仍为 Guide 与 JavaGuide 贡献者;补充说明署名 IT枫斗者,原文来源与许可保持原样。
| 输入或状态 | 系统判断 |
|---|---|
| 用户提交账号与凭据 | 验证凭据,确认身份 |
| 身份已确认 | 仍须对具体资源和操作检查权限 |
认证成功不等于获得所有权限。官方依据:OWASP 授权指南。
授权:
本站图解(非原图)
无权限场景:假设用户已登录,只有订单读取权限,却请求删除订单。
| 检查 | 示例结果 |
|---|---|
| 当前身份是否有效 | 是 |
| 是否具有订单删除权限 | 否 |
| 服务端行为 | 拒绝删除,不改变订单 |
隐藏删除按钮只能改善界面体验,不能代替服务端逐请求授权。此表不推断原截图的状态码或提示语。官方依据:OWASP 逐请求检查权限。
这两个一般在我们的系统中被结合在一起使用,目的就是为了保护我们系统的安全性。
RBAC 模型了解吗?
系统权限控制最常采用的访问控制模型就是 RBAC 模型 。
什么是 RBAC 呢? RBAC 即基于角色的权限访问控制(Role-Based Access Control)。这是一种通过角色关联权限,角色同时又关联用户的授权的方式。
简单地说:一个用户可以拥有若干角色,每一个角色又可以被分配若干权限,这样就构造成“用户-角色-权限” 的授权模型。在这种模型中,用户与角色、角色与权限之间构成了多对多的关系。
本站图解(非原图)
RBAC 的核心关联,图中的“多对多”表示模型允许的关系,不代表每个用户必须分配多个角色。
用户通过角色获得相应操作权限;复杂的数据范围和上下文约束还需要额外策略。官方依据:NIST RBAC 概念资料(归档)。
在 RBAC 权限模型中,权限与角色相关联,用户通过成为包含特定角色的成员而得到这些角色的权限,这就极大地简化了权限的管理。
为了实现 RBAC 权限模型,数据库表的常见设计如下(一共 5 张表,2 张用户建立表之间的联系):
本站图解(非原图)
RBAC 五表设计的本站示例;表名和字段是教学约定,不是原图中的数据库定义。它实现上面的角色关联模型。
| 表 | 示例字段 | 作用 |
|---|---|---|
| users | user_id(主键) | 用户 |
| roles | role_id(主键) | 角色 |
| permissions | permission_id(主键)、action | 操作权限 |
| user_roles | user_id、role_id(联合主键) | 用户与角色关联,分别引用前两表 |
| role_permissions | role_id、permission_id(联合主键) | 角色与权限关联,分别引用角色表和权限表 |
联合主键避免重复关联,外键约束避免悬空引用。五表只是实现选择,并非 RBAC 标准规定的唯一表结构。角色关联的官方依据:NIST RBAC。
通过这个权限模型,我们可以创建不同的角色并为不同的角色分配不同的权限范围(菜单)。
本站图解(非原图)
角色配置的权限矩阵示例,不复刻原管理后台的菜单或勾选状态。
| 示例角色 | 读取订单 | 删除订单 |
|---|---|---|
| 查看员 | 允许 | 拒绝 |
| 订单管理员 | 允许 | 允许 |
角色与用户的关系复用RBAC 模型。菜单可见性与后端操作权限要一致,但即使用户手动构造请求,后端仍须按资源和操作授权;默认拒绝未授予的权限。官方依据:OWASP 授权指南。
通常来说,如果系统对于权限控制要求比较严格的话,一般都会选择使用 RBAC 模型来做权限控制。
什么是 Cookie ? Cookie 的作用是什么?
本站图解(非原图)
Cookie 的报文示例,sid_demo 仅为占位值,不可用于真实会话。
Set-Cookie: sid=sid_demo; Path=/; Secure; HttpOnly符合 Cookie 作用域与浏览器策略的后续请求可携带:
Cookie: sid=sid_demoCookie 值并不天然加密;Secure 限制安全传输,HttpOnly 限制脚本读取。真实会话应另配合适的 SameSite 策略及 CSRF 防护。官方依据:RFC 6265 §4.1、§4.2、OWASP 会话管理。
Cookie 和 Session 都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。
维基百科是这样定义 Cookie 的:
Cookies是某些网站为了辨别用户身份而储存在用户本地终端上的数据(通常经过加密)。
简单来说:Cookie 存放在客户端,一般用来保存用户信息。
下面是 Cookie 的一些应用案例:
- 我们在
Cookie中保存已经登录过的用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了。除此之外,Cookie还能保存用户首选项,主题和其他设置信息。 - 使用
Cookie保存SessionId或者Token,向后端发送请求的时候带上Cookie,这样后端就能取到Session或者Token了。这样就能记录用户当前的状态了,因为 HTTP 协议是无状态的。 Cookie还可以用来记录和分析用户行为。举个简单的例子你在网上购物的时候,因为 HTTP 协议是没有状态的,如果服务器想要获取你在某个页面的停留状态或者看了哪些商品,一种常用的实现方式就是将这些信息存放在Cookie- ……
如何在项目中使用 Cookie 呢?
我这里以 Spring Boot 项目为例。
1)设置 Cookie 返回给客户端
@GetMapping("/change-username")
public String setCookie(HttpServletResponse response) {
// 创建一个 cookie
Cookie cookie = new Cookie("username", "Jovan");
//设置 cookie过期时间
cookie.setMaxAge(7 * 24 * 60 * 60); // expires in 7 days
//添加到 response 中
response.addCookie(cookie);
return "Username is changed!";
}2) 使用 Spring 框架提供的 @CookieValue 注解获取特定的 cookie 的值
@GetMapping("/")
public String readCookie(@CookieValue(value = "username", defaultValue = "Atta") String username) {
return "Hey! My username is " + username;
}3) 读取所有的 Cookie 值
@GetMapping("/all-cookies")
public String readAllCookies(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
return Arrays.stream(cookies)
.map(c -> c.getName() + "=" + c.getValue()).collect(Collectors.joining(", "));
}
return "No cookies";
}更多关于如何在 Spring Boot 中使用 Cookie 的内容可以查看这篇文章:How to use cookies in Spring Boot 。
Cookie 和 Session 有什么区别?
Session 的主要作用就是通过服务端记录用户的状态。 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。
Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。相对来说 Session 安全性更高。如果使用 Cookie 的一些敏感信息不要写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。
那么,如何使用 Session 进行身份验证?
如何使用 Session-Cookie 方案进行身份验证?
很多时候我们都是通过 SessionID 来实现特定的用户,SessionID 一般会选择存放在 Redis 中。举个例子:
- 用户成功登陆系统,然后返回给客户端具有
SessionID的Cookie。 - 当用户向后端发起请求的时候会把
SessionID带上,这样后端就知道你的身份状态了。
关于这种认证方式更详细的过程如下:
本站图解(非原图)
Session-Cookie 登录时序,省略失败分支与业务细节。
会话失效或权限不足时应拒绝访问;登录等权限变化后应更新会话标识。存储可以在本机或共享服务中,不要求一定是 Redis。官方依据:OWASP 会话管理、RFC 6265。
- 用户向服务器发送用户名、密码、验证码用于登陆系统。
- 服务器验证通过后,会为这个用户创建一个专属的 Session 对象(可以理解为服务器上的一块内存,存放该用户的状态数据,如购物车、登录信息等)存储起来,并给这个 Session 分配一个唯一的
SessionID。 - 服务器通过 HTTP 响应头中的
Set-Cookie指令,把这个SessionID发送给用户的浏览器。 - 浏览器接收到
SessionID后,会将其以 Cookie 的形式保存在本地。当用户保持登录状态时,每次向该服务器发请求,浏览器都会自动带上这个存有SessionID的 Cookie。 - 服务器收到请求后,从 Cookie 中拿出
SessionID,就能找到之前保存的那个 Session 对象,从而知道这是哪个用户以及他之前的状态了。
使用 Session 的时候需要注意下面几个点:
- 客户端 Cookie 支持:依赖 Session 的核心功能要确保用户浏览器开启了 Cookie。
- Session 过期管理:合理设置 Session 的过期时间,平衡安全性和用户体验。
- Session ID 安全:为包含
SessionID的 Cookie 设置HttpOnly标志可以防止客户端脚本(如 JavaScript)窃取,设置 Secure 标志可以保证SessionID只在 HTTPS 连接下传输,增加安全性。
另外,Spring Session 提供了一种跨多个应用程序或实例管理用户会话信息的机制。如果想详细了解可以查看下面几篇很不错的文章:
- Getting Started with Spring Session
- Guide to Spring Session
- Sticky Sessions with Spring Session & Redis
多服务器节点下 Session-Cookie 方案如何做?
Session-Cookie 方案在单体环境是一个非常好的身份认证方案。但是,当服务器水平拓展成多节点时,Session-Cookie 方案就要面临挑战了。
举个例子:假如我们部署了两份相同的服务 A,B,用户第一次登陆的时候 ,Nginx 通过负载均衡机制将用户请求转发到 A 服务器,此时用户的 Session 信息保存在 A 服务器。结果,用户第二次访问的时候 Nginx 将请求路由到 B 服务器,由于 B 服务器没有保存 用户的 Session 信息,导致用户需要重新进行登陆。
我们应该如何避免上面这种情况的出现呢?
有几个方案可供大家参考:
- 某个用户的所有请求都通过特性的哈希策略分配给同一个服务器处理。这样的话,每个服务器都保存了一部分用户的 Session 信息。服务器宕机,其保存的所有 Session 信息就完全丢失了。
- 每一个服务器保存的 Session 信息都是互相同步的,也就是说每一个服务器都保存了全量的 Session 信息。每当一个服务器的 Session 信息发生变化,我们就将其同步到其他服务器。这种方案成本太大,并且,节点越多时,同步成本也越高。
- 单独使用一个所有服务器都能访问到的数据节点(比如缓存)来存放 Session 信息。为了保证高可用,数据节点尽量要避免是单点。
- Spring Session 是一个用于在多个服务器之间管理会话的项目。它可以与多种后端存储(如 Redis、MongoDB 等)集成,从而实现分布式会话管理。通过 Spring Session,可以将会话数据存储在共享的外部存储中,以实现跨服务器的会话同步和共享。
如果没有 Cookie 的话 Session 还能用吗?
这是一道经典的面试题!
一般是通过 Cookie 来保存 SessionID ,假如你使用了 Cookie 保存 SessionID 的方案的话, 如果客户端禁用了 Cookie,那么 Session 就无法正常工作。
但是,服务端 Session 并不等于必须使用 Cookie。非浏览器客户端可以通过明确约定的请求头携带会话凭据。不过,不要把 SessionID 放进 URL:即使对它进行加密,它仍然是可以直接使用的身份凭据,并可能泄露到浏览器历史、访问日志、监控系统和 Referer 中。URL 重写只适合不得不兼容的遗留场景,不应作为新系统的登录方案。
为什么基于 Cookie 的认证更需要关注 CSRF?
CSRF(Cross Site Request Forgery) 一般被翻译为 跨站请求伪造 。那么什么是 跨站请求伪造 呢?说简单点,就是用你的身份去发送一些对你不友好的请求。举个简单的例子:
小壮登录了某网上银行,他来到了网上银行的帖子区,看到一个帖子下面有一个链接写着“科学理财,年盈利率过万”,小壮好奇的点开了这个链接,结果发现自己的账户少了 10000 元。这是这么回事呢?原来黑客在链接中藏了一个请求,这个请求直接利用小壮的身份给银行发送了一个转账请求,也就是通过你的 Cookie 向银行发出请求。
<a href="http://www.mybank.com/Transfer?bankId=11&money=10000"
>科学理财,年盈利率过万</a
>上面也提到过,进行 Session 认证的时候,我们一般使用 Cookie 来存储 SessionId。浏览器登录以后会自动在符合 Cookie 作用域的请求中带上它,服务端通过 SessionId 识别用户。攻击者如果直接窃取了 SessionId,造成的是会话劫持;而 CSRF 通常不要求攻击者读取 Cookie,它利用的是浏览器会自动携带 Cookie 的特性。
Session 认证中 Cookie 中的 SessionId 是由浏览器发送到服务端的,借助这个特性,攻击者就可以通过让用户误点攻击链接,达到攻击效果。
如果客户端把 Token 作为 Bearer Token,显式放入 Authorization Header,浏览器不会像 Cookie 那样自动把它附带到跨站请求中,因此可以降低传统 CSRF 风险。这里起作用的是凭据的携带方式,而不是 Token 或 JWT 这种格式本身。
不要因此默认把 Token 存入 localStorage 或 sessionStorage。同源页面中的恶意脚本可以读取 Web Storage,一处 XSS 漏洞就可能直接泄露 Token。浏览器应用可以根据场景选择 Backend For Frontend(BFF),或者使用设置了 HttpOnly、Secure 和合适 SameSite 属性的 Cookie;使用 Cookie 时还应结合 CSRF Token、Origin/Referer 校验等机制。
本站图解(非原图)
CSRF 与 XSS 的对照说明,不复现原图中的攻击页面。
| 场景 | 关键机制 | 防护要点 |
|---|---|---|
| CSRF | 诱使浏览器发出携带环境凭据的非预期请求,通常不需读取 Cookie | CSRF Token、来源校验、合适的 SameSite 策略 |
| 显式 Bearer 请求头 | 跨站页面不会自动获得应用持有的 Token | 不能据此认为 JWT 格式天然防 CSRF |
| XSS | 恶意脚本在应用页面上下文执行 | 输出编码与必要的净化;HttpOnly 仍不能阻止脚本代用户操作 |
Web Storage 中的凭据可能被同源恶意脚本读取;不要因为改用 Token 就忽略 XSS。官方依据:OWASP CSRF 防护、OWASP 会话管理。
需要注意的是:不论是 Cookie 还是 Token,认证机制本身都无法避免 跨站脚本攻击(Cross Site Scripting)XSS。HttpOnly 可以降低脚本直接读取 Cookie 的风险,但 XSS 仍可能以用户身份发起请求,因此还需要正确的输出编码、必要时的 HTML 净化以及 CSP 等纵深防御。
跨站脚本攻击(Cross Site Scripting)缩写为 CSS 但这会与层叠样式表(Cascading Style Sheets,CSS)的缩写混淆。因此,有人将跨站脚本攻击缩写为 XSS。
XSS 中攻击者会用各种方式将恶意代码注入到其他用户的页面中。就可以通过脚本盗用信息比如 Cookie 。
推荐阅读:如何防止 CSRF 攻击?—美团技术团队
安全实践还可以参考:
什么是 JWT?JWT 由哪些部分组成?
如何基于 JWT 进行身份验证? 如何防止 JWT 被篡改?
什么是 SSO?
SSO(Single Sign On)即单点登录说的是用户登陆多个子系统的其中一个就有权访问与其相关的其他系统。举个例子我们在登陆了京东金融之后,我们同时也成功登陆京东的京东超市、京东国际、京东生鲜等子系统。
本站图解(非原图)
单点登录的概念示例,不描述任何具体商业网站的内部实现。
| 步骤 | 示例行为 |
|---|---|
| 访问应用 A | A 将用户引导到统一身份提供方认证,再建立 A 的本地会话 |
| 访问应用 B | B 也引导到同一身份提供方;其现有会话可能让用户免于再次输入凭据 |
| 返回 B | B 校验认证结果并建立自己的会话,独立检查业务权限 |
SSO 不要求多个域直接共享同一个 Cookie,也不保证永远无需重新认证。OpenID Connect 可作为实现方式之一,其身份层构建在 OAuth 2.0 之上。官方依据:OpenID Connect Core。
SSO 有什么好处?
- 用户角度 :用户能够做到一次登录多次使用,无需记录多套用户名和密码,省心。
- 系统管理员角度 : 管理员只需维护好一个统一的账号中心就可以了,方便。
- 新系统开发角度: 新系统开发时只需直接对接统一的账号中心即可,简化开发流程,省时。
如何设计实现一个 SSO 系统?
什么是 OAuth 2.0?
OAuth 是一个行业的标准授权协议,主要用来授权第三方应用获取有限的权限。而 OAuth 2.0 是对 OAuth 1.0 的完全重新设计,OAuth 2.0 更快,更容易实现,OAuth 1.0 已经被废弃。详情请见:rfc6749。
实际上它就是一种授权机制,它的最终目的是为第三方应用颁发一个有时效性的令牌 Token,使得第三方应用能够通过该令牌获取相关的资源。
OAuth 2.0 比较常用的场景就是第三方登录,当你的网站接入了第三方登录的时候一般就是使用的 OAuth 2.0 协议。
另外,现在 OAuth 2.0 也常见于支付场景(微信支付、支付宝支付)和开发平台(微信开放平台、阿里开放平台等等)。
下图是 Slack OAuth 2.0 第三方登录的示意图:
本站图解(非原图)
Slack OAuth 授权码流程的概念说明,并非原截图复原。下列是应用安装授权,不应直接等同于用户身份登录;身份登录可使用 OpenID Connect。
这是成功路径的简图,省略拒绝授权和错误处理;请求的 scope 不代表一定获准。官方依据:Slack 安装授权文档、OpenID Connect 身份层。
推荐阅读:
参考
- 不要用 JWT 替代 session 管理(上):全面了解 Token,JWT,OAuth,SAML,SSO:https://zhuanlan.zhihu.com/p/38942172
- Introduction to JSON Web Tokens:https://jwt.io/introduction
- JSON Web Token Claims:https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-token-claims
