| title | 认证授权基础概念详解 | |||||||
|---|---|---|---|---|---|---|---|---|
| description | 认证与授权基础概念详解,讲解Authentication和Authorization的区别、Session、Token、OAuth2等核心知识。 | |||||||
| category | 系统设计 | |||||||
| tag |
|
|||||||
| head |
|
这是一个绝大多数人都会混淆的问题。首先先从读音上来认识这两个名词,很多人都会把它俩的读音搞混,所以我建议你先先去查一查这两个单词到底该怎么读,他们的具体含义是什么。
说简单点就是:
- 认证 (Authentication): 你是谁。
- 授权 (Authorization): 你有权限干什么。
稍微正式点(啰嗦点)的说法就是:
- Authentication(认证) 是验证您的身份的凭据(例如用户名/用户 ID 和密码),通过这个凭据,系统得以知道你就是你,也就是说系统存在你这个用户。所以,Authentication 被称为身份/用户验证。
- Authorization(授权) 发生在 Authentication(认证) 之后。授权嘛,光看意思大家应该就明白,它主要掌管我们访问系统的权限。比如有些特定资源只能具有特定权限的人才能访问比如 admin,有些对系统资源操作比如删除、添加、更新只能特定人才具有。
认证:
授权:
这两个一般在我们的系统中被结合在一起使用,目的就是为了保护我们系统的安全性。
系统权限控制最常采用的访问控制模型就是 RBAC 模型 。
什么是 RBAC 呢? RBAC 即基于角色的权限访问控制(Role-Based Access Control)。这是一种通过角色关联权限,角色同时又关联用户的授权的方式。
简单地说:一个用户可以拥有若干角色,每一个角色又可以被分配若干权限,这样就构造成“用户-角色-权限” 的授权模型。在这种模型中,用户与角色、角色与权限之间构成了多对多的关系。
在 RBAC 权限模型中,权限与角色相关联,用户通过成为包含特定角色的成员而得到这些角色的权限,这就极大地简化了权限的管理。
为了实现 RBAC 权限模型,数据库表的常见设计如下(一共 5 张表,2 张用户建立表之间的联系):
通过这个权限模型,我们可以创建不同的角色并为不同的角色分配不同的权限范围(菜单)。
通常来说,如果系统对于权限控制要求比较严格的话,一般都会选择使用 RBAC 模型来做权限控制。
Cookie 和 Session 都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。
维基百科是这样定义 Cookie 的:
Cookies是某些网站为了辨别用户身份而储存在用户本地终端上的数据(通常经过加密)。
简单来说:Cookie 存放在客户端,一般用来保存用户信息。
下面是 Cookie 的一些应用案例:
- 我们在
Cookie中保存已经登录过的用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了。除此之外,Cookie还能保存用户首选项,主题和其他设置信息。 - 使用
Cookie保存SessionId或者Token,向后端发送请求的时候带上Cookie,这样后端就能取到Session或者Token了。这样就能记录用户当前的状态了,因为 HTTP 协议是无状态的。 Cookie还可以用来记录和分析用户行为。举个简单的例子你在网上购物的时候,因为 HTTP 协议是没有状态的,如果服务器想要获取你在某个页面的停留状态或者看了哪些商品,一种常用的实现方式就是将这些信息存放在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 。
Session 的主要作用就是通过服务端记录用户的状态。 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。
Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。相对来说 Session 安全性更高。如果使用 Cookie 的一些敏感信息不要写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。
那么,如何使用 Session 进行身份验证?
很多时候我们都是通过 SessionID 来实现特定的用户,SessionID 一般会选择存放在 Redis 中。举个例子:
- 用户成功登陆系统,然后返回给客户端具有
SessionID的Cookie。 - 当用户向后端发起请求的时候会把
SessionID带上,这样后端就知道你的身份状态了。
关于这种认证方式更详细的过程如下:
- 用户向服务器发送用户名、密码、验证码用于登陆系统。
- 服务器验证通过后,会为这个用户创建一个专属的 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 方案就要面临挑战了。
举个例子:假如我们部署了两份相同的服务 A,B,用户第一次登陆的时候 ,Nginx 通过负载均衡机制将用户请求转发到 A 服务器,此时用户的 Session 信息保存在 A 服务器。结果,用户第二次访问的时候 Nginx 将请求路由到 B 服务器,由于 B 服务器没有保存 用户的 Session 信息,导致用户需要重新进行登陆。
我们应该如何避免上面这种情况的出现呢?
有几个方案可供大家参考:
- 某个用户的所有请求都通过特性的哈希策略分配给同一个服务器处理。这样的话,每个服务器都保存了一部分用户的 Session 信息。服务器宕机,其保存的所有 Session 信息就完全丢失了。
- 每一个服务器保存的 Session 信息都是互相同步的,也就是说每一个服务器都保存了全量的 Session 信息。每当一个服务器的 Session 信息发生变化,我们就将其同步到其他服务器。这种方案成本太大,并且,节点越多时,同步成本也越高。
- 单独使用一个所有服务器都能访问到的数据节点(比如缓存)来存放 Session 信息。为了保证高可用,数据节点尽量要避免是单点。
- Spring Session 是一个用于在多个服务器之间管理会话的项目。它可以与多种后端存储(如 Redis、MongoDB 等)集成,从而实现分布式会话管理。通过 Spring Session,可以将会话数据存储在共享的外部存储中,以实现跨服务器的会话同步和共享。
这是一道经典的面试题!
一般是通过 Cookie 来保存 SessionID ,假如你使用了 Cookie 保存 SessionID 的方案的话, 如果客户端禁用了 Cookie,那么 Session 就无法正常工作。
但是,服务端 Session 并不等于必须使用 Cookie。非浏览器客户端可以通过明确约定的请求头携带会话凭据。不过,不要把 SessionID 放进 URL:即使对它进行加密,它仍然是可以直接使用的身份凭据,并可能泄露到浏览器历史、访问日志、监控系统和 Referer 中。URL 重写只适合不得不兼容的遗留场景,不应作为新系统的登录方案。
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 校验等机制。
需要注意的是:不论是 Cookie 还是 Token,认证机制本身都无法避免 跨站脚本攻击(Cross Site Scripting)XSS。HttpOnly 可以降低脚本直接读取 Cookie 的风险,但 XSS 仍可能以用户身份发起请求,因此还需要正确的输出编码、必要时的 HTML 净化以及 CSP 等纵深防御。
跨站脚本攻击(Cross Site Scripting)缩写为 CSS 但这会与层叠样式表(Cascading Style Sheets,CSS)的缩写混淆。因此,有人将跨站脚本攻击缩写为 XSS。
XSS 中攻击者会用各种方式将恶意代码注入到其他用户的页面中。就可以通过脚本盗用信息比如 Cookie 。
推荐阅读:如何防止 CSRF 攻击?—美团技术团队
安全实践还可以参考:
SSO(Single Sign On)即单点登录说的是用户登陆多个子系统的其中一个就有权访问与其相关的其他系统。举个例子我们在登陆了京东金融之后,我们同时也成功登陆京东的京东超市、京东国际、京东生鲜等子系统。
- 用户角度 :用户能够做到一次登录多次使用,无需记录多套用户名和密码,省心。
- 系统管理员角度 : 管理员只需维护好一个统一的账号中心就可以了,方便。
- 新系统开发角度: 新系统开发时只需直接对接统一的账号中心即可,简化开发流程,省时。
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 第三方登录的示意图:
推荐阅读:
- 不要用 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









