Web Authentication & Authorization
Related Glossary
认证(Authentication):通俗讲认证即验证用户是账号的主人。指验证用户的身份,确保用户是其声称的身份。常见的认证方式包括用户名和密码登录、邮箱或手机验证码、生物识别(如指纹或面部识别)等。这一过程确保系统识别用户并为其提供适当的访问权限。
授权(Authorization):指控制用户能够访问的资源和可
以执行的操作。用户在通过身份验证后,授权系统决定该用户可以使用哪些资源、数据或执行哪些操作。授权机制通常基于用户的角色、权限组或具体的权限设置。此外,授权还包括用户允许第三方应用访问其某些资源(如头像、昵称、地区等信息)的权限授予。
凭证(Credentials):认证过程中用户提供的验证信息,用于证明其身份。常见的凭证包括用户名/密码组合、API 密钥、访问令牌等。凭证验证通过后,系统确认用户身份并允许其继续操作。
单点登录(Single Sign-On,SSO):单点登录,指在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
例如你登录网易账号中心(https://reg.163.com/ )之后访问以下站点都是登录状态。
- 网易直播 https://v.163.com
- 网易博客 https://blog.163.com
- 网易花田 https://love.163.com
- 网易考拉 https://www.kaola.com
- 网易 Lofter http://www.lofter.com
Cookie
在 Web 开发中,Cookie、Token 和 Session 这三者常常被一起讨论,特别是在会话管理和身份验证的语境下
- Cookie 是一种客户端与服务器之间的键值对(Key-Value)信息载体。浏览器会将 Cookie 存储在用户的设备上,并在后续请求中自动发送给服务器。Cookie 可用于保存会话信息、用户偏好、身份认证令牌等
- Session 是服务器端保存用户状态的一种机制
- Token 是一种用于身份验证的凭证,通常用于无状态的身份验证系统
总结:Cookie 是信息载体,Session&Token 为信息本身
Cookie 作为一种存储方法,用于在客户端存储少量的 KV 数据。这些数据可以有下面这些属性:
Name
:Cookie 的名称,用于标识不同的 Cookie。创建后名称不可更改Value
:Cookie 的值,即存储的具体数据。如果值为 Unicode 字符,需要为字符编码。如果为二进制数据,则需要使用 BASE64 编码maxAge
Cookie 失效时间(seconds)。正数表示该 Cookie 在 maxAge 秒后失效。负数表示 Cookie 在关闭浏览器后就失效。0
表示删除该 Cookie;默认值-1
Domain
:指定 Cookie 可以被哪些domain
下的页面访问。例如,设置为.example.com
表示所有子域名下的页面都可以访问该 Cookie。起始字符必须为.
Path
:指定 Cookie 可以被哪些路径下的页面访问。例如,设置为/admin
表示只有/admin
路径下的页面可以访问该 Cookie。secure
,httpOnly
:用于提高安全性的字段- …
chromium 内核的浏览器可以按 F12 进入 devtools,在 application 可以查看网站存储的 cookie,localstorage,session storage 等一系列数据。
工作原理
- 当用户访问一个网站时,服务器可以通过 HTTP 响应报文中添加 Set-Cookie 头,将 Cookie 数据发送给用户的浏览器。
- 浏览器会将这些 Cookie 存储起来,并在后续请求同一网站时通过 Cookie 请求头自动发送回服务器。
应用场景
Cookie 用途广泛,以下是常见的应用场景:
- 会话管理:Cookie 可用于存储用户的 Session ID,帮助服务器在用户访问多个页面时维持会话。
- 偏好设置:网站可以通过 Cookie 保存用户的语言选择、主题偏好等信息。
- 身份验证:浏览器可以通过存储 Token(如 JWT 或 OAuth 令牌)在 Cookie 中,实现自动发送身份验证信息给服务器。
- 跟踪行为:记录分析用户行为
CORS 指 Cross-Origin Resource Sharing,出于安全和隐私保护的考虑,Cookie 一般会被限制跨域共享
比如,example.com
设置的 Cookie,只有example.com
及其子域(如sub.example.com
)可以访问该 Cookie,而otherdomain.com
是不能访问或使用这个 Cookie 的。
当然也有一些策略可以应对 Cookie 的跨域问题,比如设置Access-Control-Allow-Credentials
为true
,设置SameSite
为Lax
或None
等等,此处不作展开
Other Storage Method
实际上,浏览器有很多存储机制,以 LocalStorage 为例,它相比 Cookie 可以存储更多的信息。此外,客户端也不局限于 Web 网页,部分小程序,移动端 app 不支持 cookie,此时就需要使用其它存储方式。
存储方式 | 类型 | 描述 | 存储限制 | 持久性 |
---|---|---|---|---|
Cookies | 标准 | 由浏览器存储的小块数据,随每次 HTTP 请求一起发送回服务器。主要用于会话管理、身份验证和跟踪用户行为。 | 每个 Cookie 大约 ~4KB | 通常设置有到期日期;可以是会话性或持久性。 |
Local Storage | 标准 | 浏览器中的键值对存储,即使浏览器关闭后数据仍然存在。适合存储需要跨会话保持的小量数据。 | 每个域名大约 ~5-10MB | 持久性,直到明确删除为止。 |
Session Storage | 标准 | 类似于 Local Storage,但数据仅在页面会话期间可用。当页面或标签关闭时数据将被清除。 | 每个域名大约 ~5-10MB | 会话性。 |
IndexedDB | 标准 | 一种用于存储大量结构化数据(包括文件和二进制数据)的低级 API。提供支持事务、搜索和索引的强大解决方案。 | 取决于浏览器实现;通常为几个 GB | 持久性,直到明确删除为止。 |
WebSQL | 非标准(已弃用) | 使用 SQL 语法的数据库存储机制。它曾被设计为 Local Storage 的更强大替代品,但由于 IndexedDB 的出现而被弃用。 | 类似于 IndexedDB | 持久性,直到明确删除为止。 |
File System Access API | 非标准(新兴标准) | 允许 Web 应用程序读取和写入本地文件系统,为管理用户设备上的文件提供了强大的方式。 | 取决于用户权限和浏览器实现。 | 持久性,直到应用程序或用户明确删除。 |
Session
HTTP 是一个无状态协议(stateless),Client 每次发出请求都是相互独立的,新的请求无法得知上一次请求所包含的状态数据。
假设 Server 端某个网页(下面将其称作 Private Page A)需要用户登录后才可以访问。Client 首先发起 HTTP Request 完成登录后,然后在下一个 HTTP Request 中发起对 Private Page A 资源的请求。但鉴于 HTTP 是无状态的,Server 端收到该请求时,无法得知用户是否已经登录,从而无法确定是否可以访问 Private Page A。
针对该问题,有几种解决方案。Cookie+Session 便是其中之一
Session 工作流程
- 用户登录成功后,服务器为用户创建一个 Session,并给该 Session 生成一个唯一的 Session ID;服务器可以使用
SetCookie
将 Session ID 保存在 Cookie 中,通过响应报文将 Session ID 发送给客户端;
Java Servlet 规范规定,会话跟踪 cookie 的名称应为JSESSIONID - Session 数据会被存储在服务器端,可以放在内存、数据库或文件中。常用的方式是将 Session ID 作为键,与对应的 Session 用户身份数据进行关联
- 当用户发来一个新的 HTTP Request 时,浏览器会在报文中携带 Session ID(存储在 Cookie 中),Server 端根据 Session ID 查找对应的 Session 数据,从而获得用户的状态信息(比如登录状态,购物车内容,个人设置等等)。并可以根据业务需求对其中的状态信息执行增删改查。
- Session 会设置有效期限。一般通过设置一个固定的时间,或者在一定时间内没有用户活动时会将 Session 标记为过期。当 Session 过期时,服务器会销毁对应的 Session 数据,释放内存或其他资源。
sequenceDiagram participant Browser as browser participant WebServer as webserver participant Database as database Note over Browser: 用户登录流程 Browser->>WebServer: POST /login (name:gjx,pwd:admin) activate WebServer Note over WebServer,Database: 验证用户凭据 WebServer->>Database: SELECT * FROM users WHERE name='gjx' activate Database Database-->>WebServer: User data (id:001, name:gjx, password_hash) deactivate Database Note over WebServer: 验证密码 WebServer->>WebServer: Verify password hash Note over WebServer,Database: 创建会话 WebServer->>Database: INSERT INTO sessions (session_id, user_id, created_at) VALUES ('qwe0asd', 001, NOW()) activate Database Database-->>WebServer: Session created successfully deactivate Database WebServer->>Browser: Response (Set-Cookie:SESSION_ID=qwe0asd) deactivate WebServer Note over Browser,WebServer: 会话建立完成 Browser->>WebServer: GET /dashboard (Cookie:SESSION_ID=qwe0asd) activate WebServer Note over WebServer,Database: 验证会话有效性 WebServer->>Database: SELECT user_id FROM sessions WHERE session_id='qwe0asd' AND expired_at > NOW() activate Database Database-->>WebServer: Valid session (user_id: 001) deactivate Database WebServer->>Browser: Return dashboard page deactivate WebServer Note over Browser: 用户成功访问仪表板
有些简单的客户端应用可能没有读写用户文件的权限,或者用户禁用了 Cookie。此时 Cookie 可以采取别的方案进行工作
- URL Rewriting:可以在每个请求的 URL 中附加 Session ID 参数,例如
https://vluv.space/?Session_id=xxx
。服务器在接收到请求时,解析 URL 中的 Session ID,并与对应的 Session 数据进行关联。这种方式适用于没有禁用地址栏中的参数传递的情况。 - 隐藏表单字段:可以将 Session ID 作为隐藏表单字段的方式传递给服务器。当用户提交表单时,Session ID 将随着表单数据一起发送给服务器,服务器据此建立与当前会话的关联。
Session 的管理
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,可以将会话数据存储在共享的外部存储中,以实现跨服务器的会话同步和共享。
Token
令牌 Token 是一种用于身份验证的凭证,通常用于无状态的身份验证系统。Token 可以是 JSON Web Token(JWT)或 OAuth 中的访问令牌。与 Session 机制不同,Token 在认证过程中不需要服务器端存储用户状态,而是通过生成一段加密的字符串来标识和验证用户身份,减少了服务器的负担。这种机制在微服务架构、跨域认证以及移动应用中得到了广泛的应用。
Token 的特点:
- Token 可存储在客户端的 Cookie、Local Storage 或 Session Storage 中。
- 与 Session 不同,Token 可以在服务器无状态的情况下完成身份验证。
- Token 常用于分布式系统,尤其是需要跨多个服务进行验证的场景。
- 相比于 Session,Token 可以放置CSRF(Cross Site Request Forgery)攻击,但两者都不能防止XSS(Cross Site Scripting)攻击,详见前端安全系列(二):如何防止 CSRF 攻击?
Token 类型
- 访问 Token(Access Token):用于访问受保护的资源,一般有较短的有效期。
- 刷新 Token(Refresh Token):用于获取新的访问 Token,通常有较长的有效期或不设置有效期。
Token 工作流程
- 用户登录:用户通过用户名、密码等方式发送登录请求给服务器。
- 生成 Token:服务器验证用户身份成功后,生成一个 Token,并将其返回给客户端。
- 客户端存储 Token:客户端(通常是浏览器或移动应用)将 Token 存储在 localStorage、sessionStorage 或 Cookie 中。
- 携带 Token 访问资源:客户端每次向服务器发起请求时,将 Token 放入请求头中(通常使用
Authorization: Bearer <token>
格式)(也可以 post 请求的数据体或 cookie 里,但存放在使用 cookie 里可能存在) - 服务器验证 Token:服务器接收到请求后,验证 Token 的有效性,若有效则处理请求并返回响应。
sequenceDiagram participant Browser as browser participant WebServer as webserver participant Database as database Note over Browser: 用户登录流程 Browser->>WebServer: POST /api/auth/login
{username:"gjx", password:"admin123"} activate WebServer Note over WebServer: 输入验证和预处理 WebServer->>WebServer: Validate input & Hash password WebServer->>Database: SELECT id,username,password_hash,salt,status
FROM users WHERE username='gjx' activate Database Database-->>WebServer: {id:1001, username:"gjx", password_hash:"$2b$10...", salt:"abc123", status:"active"} deactivate Database Note over WebServer: 密码验证和用户状态检查 WebServer->>WebServer: bcrypt.compare(password, hash)
Check user status alt 验证成功 Note over WebServer: 生成JWT和刷新令牌 WebServer->>Database: INSERT INTO user_sessions
(user_id, refresh_token, expires_at, ip_address) activate Database Database-->>WebServer: Session logged deactivate Database WebServer->>Browser: 200 OK
{
"access_token": "eyJhbGci...JWT",
"refresh_token": "rt_abc123...",
"expires_in": 3600,
"user": {"id":1001, "username":"gjx"}
} else 验证失败 WebServer->>Database: INSERT INTO login_attempts
(username, ip_address, status, attempted_at) activate Database Database-->>WebServer: Attempt logged deactivate Database WebServer->>Browser: 401 Unauthorized
{"error": "Invalid credentials"} end deactivate WebServer Note over Browser,WebServer: 访问受保护资源 Browser->>WebServer: GET /api/dashboard
Authorization: Bearer eyJhbGci...JWT activate WebServer Note over WebServer: JWT验证流程 WebServer->>WebServer: 1. Verify JWT signature
2. Check expiration
3. Extract user claims alt JWT有效 Note over WebServer: 可选:检查用户状态和权限 WebServer->>Database: SELECT status, permissions
FROM users WHERE id=1001 activate Database Database-->>WebServer: {status:"active", permissions:["read_dashboard"]} deactivate Database WebServer->>Browser: 200 OK
Dashboard data else JWT无效或过期 WebServer->>Browser: 401 Unauthorized
{"error": "Token expired", "code": "TOKEN_EXPIRED"} end deactivate WebServer Note over Browser: 令牌刷新流程 Browser->>WebServer: POST /api/auth/refresh
{"refresh_token": "rt_abc123..."} activate WebServer WebServer->>Database: SELECT user_id, expires_at FROM user_sessions
WHERE refresh_token='rt_abc123...' activate Database Database-->>WebServer: {user_id:1001, expires_at:"2024-01-15"} deactivate Database alt 刷新令牌有效 WebServer->>Database: UPDATE user_sessions SET last_used=NOW()
WHERE refresh_token='rt_abc123...' activate Database Database-->>WebServer: Updated deactivate Database WebServer->>Browser: 200 OK
{"access_token": "eyJhbGci...NEW_JWT", "expires_in": 3600} else 刷新令牌无效 WebServer->>Browser: 401 Unauthorized
{"error": "Invalid refresh token"} end deactivate WebServer
JWT
JWT (JSON Web Token) 是一种常见的 Token 格式,由三部分组成,用.
分隔,形如xxxxx.yyyyy.zzzzz
- Header(头部):通常由两部分组成:令牌的类型(JWT)和使用的签名算法(如 HMAC SHA256 或 RSA)。
- Payload(负载):存储声明(Claims),声明分为 registered claims, public claims, private claims 三种。
- iss(Issuer):签发者
- sub(Subject):面向的用户
- exp(Expiration Time):过期时间
- aud(Audience):接收方
- nbf(Not Before):生效时间
- iat(Issued At):签发时间
- jti(JWT ID): 标识符
- Signature(签名):确保 JWT 的完整性,防止其在传输过程中被篡改。签名部分是对 Header 和 Payload 的签名,通常使用 HMAC SHA-256 或 RSA 算法。签名的生成过程如下:
1
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
secret:用于生成签名的密钥,通常为服务器端定义的,不能泄露给客户端。
算出签名后,将 header,payload,signature 三者进行 Base64Url 编码,拼接在一起,用.
分隔,得到最终的 JWT 字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Header.Payload.Signature
{
{
"alg": "HS256", // 签名算法,例如HMAC SHA-256
"typ": "JWT" // 令牌类型,JWT
},
// payload,可使用官方字段/自定义字段
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
},
"signature": "SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
}
Pros
- 跨域支持:JWT 是无状态的,令牌中携带了用户信息,因此任何服务器都可以验证 JWT,无需服务器之间共享 Session。
- 安全性:JWT 采用签名机制(通常使用 HMAC 或 RSA)保证令牌的完整性,防止数据被篡改;一些复杂的 token 机制中,一个 token 令牌可以包含地理位置信息,网络信息,客户端属性,包括浏览器指纹,即便 token 泄漏,其他人拿着这个 token 也难以请求成功;此外双 token 方案也能一定程度上保证安全性(access token+refresh token)
- 高效传输:JWT 采用了 Base64Url 编码,将数据压缩为较短的字符串格式,可以安全地放入 URL 或 HTTP 头中。
Cons - Payload 可解码:虽然 JWT 的 Payload 是签名的,但它是可读的(JWT 只进行了编码而未进行加密),所以敏感信息不应放入 Payload 中;如果需存储敏感信息,应对敏感内容进行加密
Ref
HTTP cookie - wikipedia
Using HTTP cookies - HTTP|MDN
Cookies vs. LocalStorage: Storing Session Data and Beyond
京东面试:说说 Cookie、Session 和 Token 的区别?
What is a JSESSIONID in Java
jwt.io-Introduction to JSON Web Tokens
JSON Web Token 入门教程
JavaGuide-多服务器节点下 Session-Cookie 方案如何做?
前端安全系列(二):如何防止 CSRF 攻击?
Cookie、Session、Token、JWT 一次性讲完-bilibili 织点代码
Web Authentication & Authorization