Langchain-Chatchat 结合 JWT 实现安全认证访问
在企业级 AI 应用日益普及的今天,一个突出的问题浮出水面:如何在享受大语言模型强大能力的同时,确保敏感数据不外泄?尤其是金融、医疗、军工等对信息安全要求极高的行业,直接调用云端 API 已经不再可接受。数据必须“不出内网”,成为硬性红线。
正是在这种背景下,Langchain-Chatchat 这类本地化知识库系统迅速崛起。它允许企业将内部文档(如制度文件、技术手册、合同模板)转化为可问答的知识助手,所有处理过程都在本地完成。听起来很理想——但很快就会遇到另一个问题:谁都可以访问这个接口吗?
如果部署后任何人都能通过简单的 POST 请求调用 /ask 接口,那所谓的“数据安全”就形同虚设。攻击者只需知道 API 地址,就能批量爬取你的知识库内容。这显然无法满足企业级系统的安全性要求。
于是,我们自然会想到引入身份认证机制。而在这类前后端分离、无状态服务架构中,JWT(JSON Web Token) 成为了最合适的解决方案之一。
Langchain-Chatchat 本质上是一个基于 LangChain 框架构建的本地问答引擎。它支持多种文档格式(PDF、Word、PPT 等),利用嵌入模型将文本切片并存入向量数据库(如 FAISS 或 Chroma),当用户提问时,系统先进行语义检索,再结合本地运行的大模型(如 ChatGLM3-6B 或 Qwen)生成回答。整个流程完全脱离外部网络,真正实现了“数据零上传”。
但原生项目默认并未集成任何认证机制——它的 FastAPI 后端是公开暴露的。这意味着一旦部署上线,任何知道接口地址的人都可能发起请求。对于需要多人协作使用的场景,缺乏用户身份识别和权限控制更是致命缺陷。
这时候,JWT 就派上了用场。
JWT 是一种自包含的身份凭证,由 Header、Payload 和 Signature 三部分组成,形如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xeyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
它最大的优势在于“无状态”:服务端无需维护 session,只要能用密钥验证签名有效,并检查过期时间等声明,就可以信任该 Token 所携带的身份信息。这对于部署在局域网内的轻量级系统尤其友好——不需要额外搭建 Redis 或数据库来管理登录状态。
更重要的是,JWT 支持在 Payload 中自定义字段,比如 role: "admin" 或 dept: "finance",为后续实现细粒度权限控制打下基础。你可以让普通员工只能查询通用制度,而管理员才能访问核心策略文档。
当然,JWT 并非银弹。它的主要短板是“不可撤销性”——一旦签发,在过期前无法主动失效。因此实践中通常采用较短的有效期(例如 30 分钟),并配合 Refresh Token 机制延长会话。同时,绝对不能在 Payload 中存放密码、身份证号等敏感信息,因为 Base64 编码是可以被解码查看的。
那么,如何把 JWT 集成进 Langchain-Chatchat 的现有架构中?
该项目后端基于 FastAPI 构建,天然支持中间件和依赖注入机制,非常适合添加全局认证拦截。我们可以编写一个 JWT 验证装饰器或依赖项,统一保护所有 /api/* 开头的接口。
下面是一个简化但实用的实现示例,使用 PyJWT 库与 Python Flask 模拟逻辑(实际项目中替换为 FastAPI 即可无缝迁移):
from flask import Flask, request, jsonify
import jwt
import datetime
from functools import wraps
app = Flask(__name__)
SECRET_KEY = "your-super-secret-jwt-key" # 务必从环境变量加载
ALGORITHM = "HS256"
TOKEN_EXPIRE_MINUTES = 30
# 模拟用户凭证(生产环境应对接数据库或 LDAP)
users = {
"admin": "password123",
"user": "demo123"
}
def generate_token(username):
payload = {
"sub": username,
"iat": datetime.datetime.utcnow(),
"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=TOKEN_EXPIRE_MINUTES),
"role": "admin" if username == "admin" else "user"
}
token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
return token
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
token = None
auth_header = request.headers.get("Authorization")
if auth_header and auth_header.startswith("Bearer "):
token = auth_header.split(" ")[1]
if not token:
return jsonify({"error": "Missing authorization token"}), 401
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
request.user = payload # 将用户信息挂载到请求对象
except jwt.ExpiredSignatureError:
return jsonify({"error": "Token has expired"}), 401
except jwt.InvalidTokenError:
return jsonify({"error": "Invalid token"}), 401
return f(*args, **kwargs)
return decorated
@app.route("/login", methods=["POST"])
def login():
data = request.json
username = data.get("username")
password = data.get("password")
if not username or not password:
return jsonify({"error": "Username and password required"}), 400
if users.get(username) == password:
token = generate_token(username)
return jsonify({"token": token})
else:
return jsonify({"error": "Invalid credentials"}), 401
@app.route("/api/v1/ask", methods=["POST"])
@require_auth
def ask_question():
data = request.json
question = data.get("question")
if not question:
return jsonify({"error": "Question is required"}), 400
# 此处调用 Langchain-Chatchat 的本地问答逻辑
answer = f"[Mock] Answer to: {question}"
return jsonify({"answer": answer})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080, debug=False)
这段代码展示了完整的认证闭环:用户登录获取 Token,后续请求携带 Authorization: Bearer <token> 头部,服务端通过中间件自动校验合法性。若验证通过,则放行至问答接口;否则返回 401 错误。
值得注意的是,虽然这里用了 Flask 示例便于理解,但 Langchain-Chatchat 原生使用的是 FastAPI,其依赖系统更为强大。你可以将其改写为 FastAPI 的 Depends() 依赖:
from fastapi import Depends, HTTPException, Request
from starlette.status import HTTP_401_UNAUTHORIZED
async def verify_jwt(request: Request):
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail="Missing or invalid token")
token = auth_header.split(" ")[1]
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
request.state.user = payload
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail="Token expired")
except jwt.InvalidTokenError:
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail="Invalid token")
然后在路由中使用:
@app.post("/api/v1/ask")
async def ask(question: str, _: None = Depends(verify_jwt)):
# 处理逻辑
这样就能实现全接口级别的统一防护。
在整个系统架构中,JWT 层位于 Nginx 反向代理之后、Langchain-Chatchat 核心服务之前,形成一道安全屏障:
+------------------+ +----------------------+
| 前端 Web 页面 |<----->| Nginx / API Gateway |
+------------------+ +-----------+----------+
|
+--------------------v---------------------+
| Langchain-Chatchat (FastAPI) |
| |
| +-------------------+ +-------------+ |
| | 文档解析与向量化 | | JWT 认证中间件 | |
| +-------------------+ +-------------+ |
| | |
| v |
| +------------------------+ |
| | 向量数据库 (FAISS/Chroma)| |
| +------------------------+ |
| | |
| v |
| +----------------------------+ |
| | 本地大语言模型 (LLM) 推理引擎 | |
| +----------------------------+ |
+------------------------------------------+
前端仅在登录成功后获得 Token,并在每次请求时自动附加。即使有人探测到 API 路径,没有合法 Token 也无法获取任何响应。同时,由于 JWT 自带角色信息,未来可以轻松扩展 RBAC 权限模型,比如限制某些部门只能访问特定知识库。
部署时还需注意几个关键点:
- 密钥必须保密:
SECRET_KEY不应硬编码在代码中,应通过.env文件或 KMS 服务动态加载; - 强制启用 HTTPS:避免 Token 在传输过程中被嗅探截获;
- 合理设置过期时间:建议 15~60 分钟,平衡安全与体验;
- 前端存储方式:推荐将 Token 存入内存变量而非 LocalStorage,防止 XSS 攻击窃取;
- 日志脱敏:记录请求日志时务必过滤 Authorization 头,防止意外泄露。
这套方案不仅解决了基本的访问控制问题,还为企业后续建设统一身份体系打下了基础。未来可进一步对接 LDAP/AD、OAuth2 认证中心,甚至实现基于用户属性的动态知识过滤——比如只返回与其岗位相关的操作指南。
从更广的视角看,这正是 AI 落地企业的真实路径:不是简单地接入一个聊天机器人,而是将其深度融入组织的安全框架之中。只有当智能与安全并重,技术才能真正服务于业务。
Langchain-Chatchat 提供了强大的本地化能力,而 JWT 则补上了最后一块拼图——可信的身份通道。两者结合,既保障了数据主权,又实现了可控的智能化升级。这才是面向未来的、负责任的企业 AI 实践。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
477

被折叠的 条评论
为什么被折叠?



