终于有人把前端鉴权讲明白了

本文详细介绍了前端鉴权的多种方式,包括Session-Cookie认证、Token认证、单点登录以及OAuth2和OIDC。讨论了各种认证方式的来源、流程、优缺点,同时涉及了Cookie的安全问题、Google验证器的2FA双因素认证,为前端身份验证提供了全面的理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是鉴权

鉴权也叫身份认证,指验证用户是否有系统的访问权限。就很像我们经常乘坐动车的票据(对应的标识,一定的时间范围)。

认证方式

接下来介绍几种我们工作中通常用到的认证方式。

Session-Cookie 认证

利用服务端的 Session(会话)和浏览器(客户端)的 Cookie 来实现的前后端通信认证模式。

来源

由于 HTTP 请求时是无状态的,服务端正常情况下无法得知请求发送者的身份。这个时候如果我们要记录状态,就需要在服务端创建会话,将相同客户端的请求都维护在各自的会话记录中,每当请求到达服务端时,先校验请求中的用户标识是否存在于 Session 中,如果有则表示已经认证成功,否则表示认证失败。

流程

file

实践

boss(我们的一个产品) 这边 Session ID 存在数据库里面,在 Memcached 里面做缓存。客户端每次调用接口的时候会通过 response headers 里面的 Set-Cookie 更新过期时间(boss 这边设置的是 6 个小时),这样做的作用是防止你在做一些复杂操作的时候,cookie 突然过期。

⚠️整个过程是比较重的,因为每次的接口调用都得更新过期时间。

优缺点

优点:
  • 简单易用,浏览器会自动带上
缺点:
  • 脱离浏览器没法用,比如原生应用

关于 Cookie 的安全问题

Cookie 属性:

file

提高安全性的办法

  • Expires/Max-Age 设置合理过期时间

  • HttpOnly 设置为 true

  • Secure 设置为 true(使用 https)

Token 认证

来源

负载均衡多服务器的情况,不好确认当前用户是否登录,因为多服务器不共享 Session。这个问题也可以将 Session 存在一个服务器中来解决,但是就不能完全达到负载均衡的效果。
Token 和 Session-Cookie 认证方式中的 Session ID 不同,并非只是一个标识符。Token 一般会包含用户的相关信息,通过验证 Token 不仅可以完成身份校验,还可以获取预设的信息。
客户端可以将 token 存放于 localStroage 等容器中。客户端每次访问都传递 token,服务端解密 token,服务端就不需要存储 Session 占用存储空间,就很好的解决负载均衡多服务器的问题了。

流程

file

实践

平常用的最多的就是 JSON Web Token(JWT),也是目前最流行的跨域身份验证解决方案。
JWT 组成:头部. 载荷. 签名
头部和载荷用 base64 编码
签名计算:

	HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload) , secret)

使用方法:

	Authorization: Bearer <token>

但是 JWT 有个大缺点是服务器不保存会话状态,所以在使用期间不可能取消令牌或更改令牌的权限。也就是说,一旦 JWT 签发,在有效期内将会一直有效。

⚠️载荷的内容任何人都可以读到,不要放入敏感信息

jwt 存储位置的争论:我觉得如果存储信息多,天然防止 csrf 的话,放到 localStorage 或者 sessionStoraged 都行。
除了 JWT 可以提升 token 的安全性,Refresh token 也可以。
业务接口用来鉴权的 token,我们称之为 access token。越是权限敏感的业务,我们越希望 access token 有效期足够短,以避免被盗用。但是过短的有效期会造成 access token 经常过期,过期后怎么办呢?
一种办法是,让用户重新登录获取新 token,显然不够友好,要知道有的 access token 过期时间可能只有几分钟。
另外一种办法是,再来一个 token,一个专门生成 access token 的 token,我们称为 refresh token。
refresh token 的过期时间一般比较长,比如 6 个小时,access token 的过期时间比较短,比如 10 分钟。我们在实际业务中,api 调用时只传递 access token 进行鉴权。如果 access token 过期,则使用 refresh token 去授权服务器更新 access token。最终 refresh token 也过期了,这时候用户就得重新登陆了。

优缺点

优点:
  • 轻量,服务端不用存储,移动端可用
缺点:
  • 一旦派发出去,失效之前都是有效的(虽然可以解决,但是就类似于 Session 机制了)

单点登录

来源

但当我们业务线越来越多,就会有更多业务系统分散到不同域名下,就需要「一次登录,全线通用」的能力,叫做「单点登录」。

流程

file

对浏览器来说,SSO 域下返回的数据要怎么存,才能在访问 A 的时候带上?这就需要也只能由 A 提供 A 域下存储凭证的能力。

file

实践

OIDC

  1. OIDC 登陆点击,重定向到登录的 OpenID 网站

  2. 输入用户名密码,如果验证成功。则会重定向到登陆回调(之前设置好的地址)

  3. 回调地址里面有个 code 参数,code 验证正确后,下发 sk,boss 系统登陆成功

  4. 前端通过添加 iframe 的方式轮询 authing 链接实现单点登出

关于 OIDC

OIDC 是一个 OAuth2 上层的简单身份层协议。它允许客户端验证用户的身份并获取基本的用户配置信息。OIDC 使用 JSON Web Token(JWT)作为信息返回,通过符合 OAuth2 的流程来获取。

关于 OAuth2

OAuth2 最终目的是为第三方应用颁发一个有时效性的令牌 token。使得第三方应用能够通过该令牌获取相关的资源。当你想要登录某个论坛,但没有账号,而这个论坛接入了如 QQ、Facebook 等登录功能,在你使用 QQ 登录的过程中就使用的 OAuth 2.0 协议。

file

  • Client 请求 Resource Owner 的授权。授权请求可以直接向 Resource Owner 请求,也可以通过 Authorization Server 间接的进行。

  • Client 获得授权许可。

  • Client 向 Authorization Server 请求访问令牌。

  • Authorization Server 验证授权许可,如果有效则颁发访问令牌。

  • Client 通过访问令牌从 Resource Server 请求受保护资源。

  • Resource Server 验证访问令牌,有效则响应请求。

关于 LDAP

LDAP (Light Directory Access Portocol),中文名轻量目录访问协议,是一个开放、广泛被使用的工业标准。比如我们的 Jira、Confluence、Yapi。
但是 LDAP 并不能做到单点登录 SSO,只是可以用同样的用户名和密码可以登陆不同的系统,但达不到一次登陆之后可以访问多个系统。

Others 认证方式

2FA(双因素认证)

线上的 boss 必须开启二次认证,会生成一个二维码,那个二维码就是一个 SecretKey,通过 CryptoJS.HmacSHA1(默认算法),每次会计算出一个 6 位(默认长度)随机数。计算公式为

file

⚠️因为默认是 30s 内有效,所以用户手机时间要比较准确

Google 验证器

密钥二维码

http://otpauth//totp/ 青云 QingCloud 云计算管理平台:deanchen@yunify.com?secret=xxx&issuer = 青云 QingCloud 云计算管理平台
xxx: 大写的字母数字 16 位

使用 base32 的解码密钥

密钥必须大写没有空格

获取 Unix 时间戳

	let epoch = Math.round(new Date().getTime() / 1000.0);if (localStorage.offset) {
		epoch = epoch + Number(localStorage.offset);
	}
	counter = Math.floor(epoch / period); // period 一般为30 这个失效实现的想法太棒了

计算签名

	const time = this.leftpad(this.dec2hex(counter), 16, "0");const key = this.base32tohex(secret)
	CryptoJS.HmacSHA1(
			CryptoJS.enc.Hex.parse(time),
			CryptoJS.enc.Hex.parse(key)
	)

得出结果

	const len = 6;const result = otp.substr(otp.length - len, len).toString() // 默认取的最后6位

作者

Dean 青云科技高级工程师

本文由博客一文多发平台 OpenWrite 发布!

<think>嗯,用户现在想了解如何通过JWT完成用户认证与限控制,并且要详细的步骤。首先,我需要确认用户对JWT的理解程度。可能他们已经有了一些基础知识,但需要具体的实现步骤,或者他们可能刚开始接触,需要从基础起。 接下来,我应该先回顾JWT的基本概念,确保用户明白JWT的结构和用途。然后分步骤解用户认证和限控制的流程。用户认证部分需要包括登录流程、生成JWT、返回给客户端,而限控制则需要中间件或装饰器来验证令牌和检查限。 需要注意用户可能的实际应用场景,比如他们可能在开发Web应用,使用特定的后端框架,比如Node.js、Python的Django或Flask,或者是其他语言。所以在示例代码部分,可能需要提供不同语言的例子,或者至少说明通用的步骤,让用户可以根据自己的技术栈调整。 另外,用户可能关心安全性问题,比如如何安全存储密钥,如何防止令牌被盗用,是否需要设置令牌的黑名单,如何处理令牌过期等。这些也需要在步骤中提及,或者作为注意事项补充。 限控制部分,可能需要详细说明如何根据角色或限进行访问控制,比如基于声明的限,或者更复杂的RBAC(基于角色的访问控制)系统。需要给出具体的实现方法,比如中间件如何解析JWT,提取用户信息,然后根据限决定是否允许访问。 还需要考虑实际开发中的常见问题,比如跨域问题(CORS)、令牌的刷新机制、如何在前端存储令牌(localStorage vs cookies)等。虽然这些问题可能超出了JWT本身的范围,但用户可能在实现过程中遇到,适当提及可以增加回答的实用性。 此外,用户可能需要知道如何测试这些流程,比如使用Postman或curl发送请求,检查返回的令牌,以及如何验证令牌的有效性。提供一些测试示例会更有帮助。 最后,总结关键点,确保用户理解整个流程,并强调最佳实践和安全性措施,比如使用HTTPS、合理设置令牌过期时间、避免敏感数据存储在JWT中等等。</think>以下是基于JWT(JSON Web Token)实现用户认证与限控制的详细步骤,涵盖**核心流程**、**代码示例**和**安全注意事项**: --- ### **一、JWT 认证流程概述** 1. **用户登录** → 2. **服务端生成JWT** → 3. **客户端存储JWT** → 4. **后续请求携带JWT** → 5. **服务端验证JWT并** --- ### **二、详细步骤与代码实现(以Python Flask为例)** #### **1. 用户登录并生成JWT** ```python from flask import Flask, request, jsonify import jwt import datetime app = Flask(__name__) SECRET_KEY = "your_secure_secret_key" # 实际生产环境需使用强密钥并保密 @app.route('/login', methods=['POST']) def login(): # 1. 验证用户名密码(此处简化为示例) username = request.json.get('username') password = request.json.get('password') if not (username == "admin" and password == "123456"): return jsonify({"error": "Invalid credentials"}), 401 # 2. 生成JWT(包含用户ID、角色和过期时间) payload = { "user_id": 1, "role": "admin", "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1) # 1小时后过期 } token = jwt.encode(payload, SECRET_KEY, algorithm="HS256") return jsonify({"token": token}) ``` #### **2. 客户端存储JWT** - **前端**:登录成功后,将JWT存储在`localStorage`或`cookie`中: ```javascript // 示例:使用fetch发送登录请求 fetch('/login', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({username: 'admin', password: '123456'}) }) .then(response => response.json()) .then(data => { localStorage.setItem('jwt_token', data.token); // 存储到localStorage }); ``` #### **3. 后续请求携带JWT** - **前端**:在请求头中添加`Authorization`字段: ```javascript fetch('/protected', { headers: { 'Authorization': `Bearer ${localStorage.getItem('jwt_token')}` } }); ``` #### **4. 服务端验证JWT(中间件实现)** ```python from functools import wraps def token_required(f): @wraps(f) def decorator(*args, **kwargs): token = None # 从请求头获取token if 'Authorization' in request.headers: token = request.headers['Authorization'].split(" ")[1] if not token: return jsonify({"error": "Token missing"}), 401 try: # 解码并验证JWT data = jwt.decode(token, SECRET_KEY, algorithms=["HS256"]) current_user = {"user_id": data['user_id'], "role": data['role']} except jwt.ExpiredSignatureError: return jsonify({"error": "Token expired"}), 401 except jwt.InvalidTokenError: return jsonify({"error": "Invalid token"}), 401 # 将用户信息传递到路由函数 return f(current_user, *args, **kwargs) return decorator @app.route('/protected') @token_required def protected_route(current_user): return jsonify({"message": f"Hello {current_user['role']} user!"}) ``` #### **5. 限控制(基于角色)** ```python def role_required(required_role): def wrapper(f): @wraps(f) def decorator(current_user, *args, **kwargs): if current_user['role'] != required_role: return jsonify({"error": "Permission denied"}), 403 return f(current_user, *args, **kwargs) return decorator return wrapper @app.route('/admin') @token_required @role_required('admin') def admin_panel(current_user): return jsonify({"message": "Welcome to admin dashboard"}) ``` --- ### **三、安全增强措施** 1. **密钥管理** - 使用环境变量或密钥管理服务(如AWS KMS)存储`SECRET_KEY`,禁止硬编码在代码中 - 定期轮换密钥(需配合JWT黑名单机制) 2. **JHTTPS强制使用** - 所有涉及JWT的请求必须通过HTTPS传输,防止中间人攻击 3. **令牌时效性控制** - 设置短有效期(如15分钟)的Access Token + 长有效期(如7天)的Refresh Token - 示例Refresh Token流程: ```python # 生成Refresh Token(存储到数据库或Redis) refresh_token = jwt.encode( {"user_id": 1, "exp": datetime.datetime.utcnow() + datetime.timedelta(days=7)}, SECRET_KEY, algorithm="HS256" ) ``` 4. **敏感数据隔离** - 不要在JWT payload中存储密码、手机号等敏感信息 --- ### **四、测试流程** 1. **登录获取Token** ```bash curl -X POST http://localhost:5000/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}' ``` 2. **访问受保护接口** ```bash curl http://localhost:5000/protected \ -H "Authorization: Bearer <your_token>" ``` --- ### **五、常见问题解决方案** | **问题** | **解决方法** | |-------------------------|-----------------------------------------------------------------------------| | 令牌泄露 | 立即吊销令牌(维护黑名单),缩短令牌有效期 | | 跨域请求(CORS) | 配置`Access-Control-Allow-Headers: Authorization` | | 高并发下的性能瓶颈 | 使用无状态JWT验证,避免频繁查询数据库 | | 需要实时限更新 | 结合短有效期令牌 + 限变更时强制重新登录 | --- 通过以上步骤,可完整实现基于JWT的认证与限控制。实际部署时需根据业务需求调整角色系统、令牌刷新策略等细节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值