Java Web —— 认证&授权

Java Web —— 认证&授权

认证(Authentication):通俗讲认证即验证用户是账号的主人。指验证用户的身份,确保用户是其声称的身份。常见的认证方式包括用户名和密码登录、邮箱或手机验证码、生物识别(如指纹或面部识别)等。这一过程确保系统识别用户并为其提供适当的访问权限。
授权(Authorization):指控制用户能够访问的资源和可以执行的操作。用户在通过身份验证后,授权系统决定该用户可以使用哪些资源、数据或执行哪些操作。授权机制通常基于用户的角色、权限组或具体的权限设置。此外,授权还包括用户允许第三方应用访问其某些资源(如头像、昵称、地区等信息)的权限授予。
凭证(Credentials):认证过程中用户提供的验证信息,用于证明其身份。常见的凭证包括用户名/密码组合、API密钥、访问令牌等。凭证验证通过后,系统确认用户身份并允许其继续操作。
单点登录(Single Sign-On,SSO):单点登录,指在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

例如你登录网易账号中心(https://reg.163.com/ )之后访问以下站点都是登录状态。

在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。
  • securehttpOnly:用于提高安全性的字段

chromium 内核的浏览器可以按F12进入devtools,在application可以查看网站存储的cookie,localstorage,session storage等一系列数据。

工作原理

  • 当用户访问一个网站时,服务器可以通过 HTTP 响应报文中添加 Set-Cookie 头,将 Cookie 数据发送给用户的浏览器。
  • 浏览器会将这些 Cookie 存储起来,并在后续请求同一网站时通过 Cookie 请求头自动发送回服务器。

应用场景

Cookie用途广泛,以下是常见的应用场景:

  • 会话管理:Cookie可用于存储用户的Session ID,帮助服务器在用户访问多个页面时维持会话。
  • 偏好设置:网站可以通过Cookie保存用户的语言选择、主题偏好等信息。
  • 身份验证:浏览器可以通过存储Token(如JWT或OAuth令牌)在Cookie中,实现自动发送身份验证信息给服务器。
  • 跟踪行为:记录分析用户行为

Cookie CORS

CORS指Cross-Origin Resource Sharing,出于安全和隐私保护的考虑,Cookie一般会被限制跨域共享

比如,example.com设置的Cookie,只有example.com及其子域(如sub.example.com)可以访问该Cookie,而otherdomain.com是不能访问或使用这个Cookie的。
当然也有一些策略可以应对Cookie的跨域问题,比如设置Access-Control-Allow-Credentialstrue,设置SameSiteLaxNone等等,此处不作展开

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工作流程

  1. 用户登录成功后,服务器为用户创建一个Session,并给该Session生成一个唯一的Session ID;服务器可以使用 SetCookie将 Session ID 保存在 Cookie 中,通过响应报文将Session ID发送给客户端;
    Java Servlet规范规定,会话跟踪cookie的名称应为JSESSIONID
  2. Session数据会被存储在服务器端,可以放在内存、数据库或文件中。常用的方式是将 Session ID 作为键,与对应的 Session 用户身份数据进行关联
  3. 当用户发来一个新的HTTP Request时,浏览器会在报文中携带Session ID(存储在Cookie中),Server端根据 Session ID 查找对应的 Session 数据,从而获得用户的状态信息(比如登录状态,购物车内容,个人设置等等)。并可以根据业务需求对其中的状态信息执行增删改查。
  4. Session 会设置有效期限。一般通过设置一个固定的时间,或者在一定时间内没有用户活动时会将 Session 标记为过期。当 Session 过期时,服务器会销毁对应的 Session 数据,释放内存或其他资源。

禁用Cookie后还可以使用Session吗

有些简单的客户端应用可能没有读写用户文件的权限,或者用户禁用了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 方案在单体环境是一个非常好的身份认证方案。但是,当服务器水平拓展成多节点时,Session-Cookie 方案就要面临挑战了。
举个例子:假如我们部署了两份相同的服务 A,B,用户第一次登陆的时候 ,Nginx 通过负载均衡机制将用户请求转发到 A 服务器,此时用户的 Session 信息保存在 A 服务器。结果,用户第二次访问的时候 Nginx 将请求路由到 B 服务器,由于 B 服务器没有保存 用户的 Session 信息,导致用户需要重新进行登陆。

  1. 某个用户的所有请求都通过特性的哈希策略分配给同一个服务器处理。这样的话,每个服务器都保存了一部分用户的 Session 信息。服务器宕机,其保存的所有 Session 信息就完全丢失了。
  2. 每一个服务器保存的 Session 信息都是互相同步的,也就是说每一个服务器都保存了全量的 Session 信息。每当一个服务器的 Session 信息发生变化,我们就将其同步到其他服务器。这种方案成本太大,并且,节点越多时,同步成本也越高。
  3. 单独使用一个所有服务器都能访问到的数据节点(比如缓存)来存放 Session 信息。为了保证高可用,数据节点尽量要避免是单点。
  4. 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工作流程

  1. 用户登录:用户通过用户名、密码等方式发送登录请求给服务器。
  2. 生成Token:服务器验证用户身份成功后,生成一个Token,并将其返回给客户端。
  3. 客户端存储Token:客户端(通常是浏览器或移动应用)将Token存储在 localStorage、sessionStorage 或 Cookie 中。
  4. 携带Token访问资源:客户端每次向服务器发起请求时,将Token放入请求头中(通常使用Authorization: Bearer <token>格式)(也可以post请求的数据体或cookie里,但存放在使用cookie里可能存在)
  5. 服务器验证Token:服务器接收到请求后,验证Token的有效性,若有效则处理请求并返回响应。

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织点代码

Java Web —— 认证&授权

https://vluv.space/Dev/Java/java_web1/

作者

Jiaxing Gao

发布于

2024-10-17

更新于

2024-10-19

许可协议

评论

}