在这Trusted Headers(可信头部) 方案架构下,Open WebUI 的角色仅仅是一个“受信任的执行端”,它完全放弃了自身的密码验证和账号注册体系,将身份识别权 100% 交给了你的 Nginx 和自定义认证程序。
核心实现原理
- 你的现有系统:负责用户的注册、登录和鉴权。
- Nginx 与认证程序:用户访问 Open WebUI 时,Nginx 向你的认证程序发起内部请求。认证程序验证通过后,返回用户的
Email和Name。Nginx 将这些信息写入X-User-Email和X-User-Name请求头,透传给 Open WebUI。 - Open WebUI 的自动创建机制:Open WebUI 接收到这两个头部后,会去本地数据库查找该邮箱。如果不存在,Open WebUI 会自动在后台静默创建一个对应邮箱的普通用户账号,并直接完成登录。
具体配置指南
要实现“不在 Open WebUI 中手动注册用户”,你需要确保 Open WebUI 处于启用认证但信任外部头部的状态。
1. 启动 Open WebUI 容器
在启动 Docker 容器时,不要设置 WEBUI_AUTH=False(这会变成单用户模式),而是保持默认的认证开启状态,并关闭注册功能(可选):
docker run -d \
-p 3000:8080 \
-v open-webui:/app/backend/data \
-e ENABLE_SIGNUP=False \ # 关闭前端注册入口,防止用户自行注册
--name open-webui \
ghcr.io/open-webui/open-webui:main
2. Nginx 注入 Trusted Headers
确保你的 Nginx 配置中,在代理请求到 Open WebUI 时,准确注入了以下两个头部:
location / {
# 1. 调用你的认证程序
auth_request /custom-auth-verify;
# 2. 获取认证程序返回的用户信息
auth_request_set $user $upstream_http_x_user_name;
auth_request_set $email $upstream_http_x_user_email;
# 3. 注入 Open WebUI 识别的 Trusted Headers
proxy_set_header X-User-Name $user;
proxy_set_header X-User-Email $email;
# 4. 转发请求
proxy_pass http://open-webui-backend:8080;
}
进阶:如何控制现有用户的权限?
既然用户是自动创建的,你可能需要控制他们在 Open WebUI 中的权限(例如:哪些人可以做管理员,哪些人只能使用基础模型)。Open WebUI 支持通过 Trusted Headers 传递用户角色或组:
-
传递角色:在 Nginx 中额外注入
X-User-Role头部。auth_request_set $role $upstream_http_x_user_role; proxy_set_header X-User-Role $role; # 值可以是 admin, user, pending你可以在你的认证程序中根据现有系统的数据库判断该用户是否为管理员,并返回对应的角色。
-
传递用户组:注入
X-User-Groups头部。auth_request_set $groups $upstream_http_x_user_groups; proxy_set_header X-User-Groups $groups; # 例如 "ai-users,hr-department"Open WebUI 会自动将这些组与你在后台配置的“权限组”进行匹配,从而控制该用户能访问哪些模型或知识库。
⚠️ 再次强调安全底线
由于 Open WebUI 会无条件信任 X-User-Email 头部并自动创建账号,你必须确保 Open WebUI 的 8080 端口绝对无法被外部直接访问。如果端口暴露,任何人都可以通过伪造请求头,使用任意邮箱(如 admin@company.com)自动创建管理员账号并接管系统。
按照这个方案,你的现有系统用户只需在浏览器中访问,Nginx 验证通过后,Open WebUI 就会在后台瞬间为他们建好账号并放行,整个过程对现有用户完全无感知。
实现实例
基于 Spring Boot 和 java-jwt (Auth0) 库的认证程序示例。它专门用于配合 Nginx 的 auth_request 模块,解析 JWT 并将用户信息通过 HTTP 响应头返回给 Nginx。
1. 添加 Maven 依赖
首先,在你的 Spring Boot 项目中引入 JWT 处理库:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version> <!-- 建议使用最新稳定版 -->
</dependency>
2. 编写认证接口代码
这个 Controller 提供了一个 /api/verify 端点,专门供 Nginx 内部调用:
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class CustomAuthController {
// 从配置文件中读取 JWT 密钥,切勿硬编码
@Value("${jwt.secret-key}")
private String secretKey;
@GetMapping("/verify")
public ResponseEntity<Void> verifyToken(
@RequestHeader(value = "Cookie", required = false) String cookieHeader) {
// 1. 从 Cookie 中提取 JWT Token (假设 Cookie 名为 sso_token)
String token = extractTokenFromCookie(cookieHeader);
if (token == null || token.isEmpty()) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
try {
// 2. 初始化 JWT 验证器
Algorithm algorithm = Algorithm.HMAC256(secretKey);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
// 3. 提取用户信息
String email = jwt.getSubject(); // 假设 JWT 的 subject 存储的是邮箱
String name = jwt.getClaim("name").asString(); // 提取自定义的 name 声明
String role = jwt.getClaim("role").asString(); // 提取角色
// 4. 将用户信息放入响应头,供 Nginx 捕获并透传
return ResponseEntity.ok()
.header("X-User-Email", email)
.header("X-User-Name", name != null ? name : email)
.header("X-User-Role", role != null ? role : "user")
.build();
} catch (Exception e) {
// Token 无效、过期或签名错误,返回 401
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}
/**
* 简单的 Cookie 解析工具方法
*/
private String extractTokenFromCookie(String cookieHeader) {
if (cookieHeader == null) return null;
String[] cookies = cookieHeader.split(";");
for (String cookie : cookies) {
String[] parts = cookie.trim().split("=");
if (parts.length == 2 && "sso_token".equals(parts[0])) {
return parts[1];
}
}
return null;
}
}
3. 配置 Nginx 对接该程序
在你的 Nginx 配置中,确保将请求头(特别是 Cookie)传递给这个 Java 程序:
nginx
编辑
# 定义认证程序端点
location = /custom-auth-verify {
internal; # 仅允许 Nginx 内部调用
proxy_pass http://your-java-auth-service:8080/api/verify;
# 关键:不转发请求体,提高性能
proxy_pass_request_body off;
proxy_set_header Content-Length "";
# 关键:将原始 Cookie 传递给 Java 程序进行 JWT 解析
proxy_set_header Cookie $http_cookie;
}
💡 架构优势
- 统一鉴权:你的 Java 程序只负责验证,验证成功后返回
200 OK和用户头;验证失败返回401 Unauthorized,Nginx 会自动拦截并重定向到登录页。 - 无状态:JWT 本身包含了用户信息,Java 程序无需查询数据库,性能极高,非常适合 Nginx 的高并发子请求场景。
- 安全性:通过
internal指令,外部用户无法直接访问/custom-auth-verify接口,防止了越权验证。
按照这个方案,你的 Java 认证服务就能完美接管 Open WebUI 的身份验证工作了。
配置部署
为了让你的 Java 认证程序(/custom-auth-verify)能够安全、稳定地运行并配合 Nginx 工作,以下是一份完整的 Spring Boot 配置文件示例。
推荐使用 application.yml 格式,它的结构更清晰,且非常适合管理 JWT 相关的复杂配置。
1. 核心配置文件 (application.yml)
将此文件放在项目的 src/main/resources/ 目录下:
server:
# 1. 端口设置:认证程序通常作为内部微服务运行
# 建议修改为非标准端口(如 8081),避免与 Open WebUI 或其他服务冲突
port: 8081
# 2. JWT 核心配置
jwt:
# ⚠️ 安全警告:HS256 算法要求密钥长度至少为 32 位(256 bit)
# 生产环境切勿将真实密钥硬编码在代码或配置文件中!
secret-key: ${JWT_SECRET_KEY:your-super-secret-key-at-least-32-chars-long}
# Token 过期时间(毫秒),示例为 24 小时
expire: 86400000
# 3. 日志配置(可选,方便排查 Nginx auth_request 的问题)
logging:
level:
root: INFO
com.yourpackage: DEBUG # 将 com.yourpackage 替换为你的实际包名
2. 多环境配置最佳实践(强烈推荐)
为了防止 JWT 密钥泄露,强烈建议将敏感信息拆分到不同的环境配置文件中。
主配置文件 (application.yml) :只保留占位符和通用配置。
server:
port: 8081
jwt:
# 从环境变量读取,如果没有则使用默认值(仅限开发环境)
secret-key: ${JWT_SECRET_KEY:dev-default-key-at-least-32-chars}
expire: 86400000
spring:
profiles:
active: dev # 默认激活开发环境
生产环境配置 (application-prod.yml) :
jwt:
# 生产环境不提供默认值,强制要求通过环境变量注入
secret-key: ${JWT_SECRET_KEY}
3. 如何在代码中优雅地读取配置?
与其在 Controller 中使用多个 @Value 注解,不如创建一个配置类,将 JWT 相关配置绑定到一个 Java Bean 上,这样更符合面向对象的设计。
创建配置类:
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtProperties {
private String secretKey;
private long expire;
}
在 Controller 中注入使用:
@RestController
@RequestMapping("/api")
public class CustomAuthController {
@Autowired
private JwtProperties jwtProperties; // 直接注入配置类
@GetMapping("/verify")
public ResponseEntity<Void> verifyToken(@RequestHeader(value = "Cookie", required = false) String cookieHeader) {
String token = extractTokenFromCookie(cookieHeader);
if (token == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
try {
// 使用 jwtProperties.getSecretKey() 获取密钥
Algorithm algorithm = Algorithm.HMAC256(jwtProperties.getSecretKey());
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
// ... 后续返回 Header 逻辑不变
return ResponseEntity.ok()
.header("X-User-Email", jwt.getSubject())
.build();
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}
}
4. 部署与启动说明
本地开发启动:
直接运行 Spring Boot 应用即可,它会读取 application.yml 中的默认密钥。
生产环境启动(Docker / 服务器):
通过环境变量注入真实的 JWT 密钥,确保代码库中没有敏感信息:
# 方式一:通过命令行参数覆盖
java -jar auth-service.jar --jwt.secret-key=PROD_SUPER_SECRET_KEY_1234567890
# 方式二:通过操作系统环境变量
export JWT_SECRET_KEY=PROD_SUPER_SECRET_KEY_1234567890
java -jar auth-service.jar
配合 Nginx 的端口对接:
确保你的 Nginx 配置中,proxy_pass 指向了正确的端口(与 server.port 一致):
location = /custom-auth-verify {
internal;
proxy_pass http://127.0.0.1:8081/api/verify; # 注意这里的端口是 8081
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Cookie $http_cookie;
}
按照这套配置,你的认证服务不仅具备了极高的安全性(密钥外部化),而且在工程结构上也非常规范,方便后续的维护和扩展。