Selaa lähdekoodia

新增单点登录功能

liu 1 vuosi sitten
vanhempi
commit
017e816edb

+ 5 - 0
backend/open_webui/env.py

@@ -340,6 +340,11 @@ WEBUI_AUTH = os.environ.get("WEBUI_AUTH", "True").lower() == "true"
 WEBUI_AUTH_TRUSTED_EMAIL_HEADER = os.environ.get(
     "WEBUI_AUTH_TRUSTED_EMAIL_HEADER", None
 )
+# 信任token
+WEBUI_AUTH_TRUSTED_TOKEN_HEADER = os.environ.get(
+    "WEBUI_AUTH_TRUSTED_TOKEN_HEADER", None
+)
+
 WEBUI_AUTH_TRUSTED_NAME_HEADER = os.environ.get("WEBUI_AUTH_TRUSTED_NAME_HEADER", None)
 
 BYPASS_MODEL_ACCESS_CONTROL = (

+ 31 - 1
backend/open_webui/routers/auths.py

@@ -4,6 +4,7 @@ import time
 import datetime
 import logging
 from aiohttp import ClientSession
+import jwt
 
 from open_webui.models.auths import (
     AddUserForm,
@@ -24,6 +25,7 @@ from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
 from open_webui.env import (
     WEBUI_AUTH,
     WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
+    WEBUI_AUTH_TRUSTED_TOKEN_HEADER,
     WEBUI_AUTH_TRUSTED_NAME_HEADER,
     WEBUI_AUTH_COOKIE_SAME_SITE,
     WEBUI_AUTH_COOKIE_SECURE,
@@ -327,7 +329,35 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
 
 @router.post("/signin", response_model=SessionUserResponse)
 async def signin(request: Request, response: Response, form_data: SigninForm):
-    if WEBUI_AUTH_TRUSTED_EMAIL_HEADER:
+    if WEBUI_AUTH_TRUSTED_TOKEN_HEADER:
+        if WEBUI_AUTH_TRUSTED_TOKEN_HEADER not in request.headers:
+            raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_TRUSTED_HEADER)
+
+        trusted_token = request.headers[WEBUI_AUTH_TRUSTED_TOKEN_HEADER]
+        # 讲token解密,获取用户名称和邮箱
+        try:
+            payload = jwt.decode(trusted_token, "WEBUI_SECRET_KEY", algorithms=["HS256"])
+        except jwt.ExpiredSignatureError:
+            raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_TRUSTED_HEADER)
+        except jwt.InvalidTokenError:
+            raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_TRUSTED_HEADER)
+        trusted_email = payload.get("email")
+        trusted_name = payload.get("name")
+        
+        if not trusted_email or not trusted_name:
+            raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_TRUSTED_HEADER)
+        
+        # 验证用户是否存在
+        if not Users.get_user_by_email(trusted_email.lower()):
+            await signup(
+                request,
+                response,
+                SignupForm(email=trusted_email, password=str(uuid.uuid4()), name=trusted_name),
+            )
+        user = Auths.authenticate_user_by_trusted_header(trusted_email)
+        
+        
+    elif WEBUI_AUTH_TRUSTED_EMAIL_HEADER:
         if WEBUI_AUTH_TRUSTED_EMAIL_HEADER not in request.headers:
             raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_TRUSTED_HEADER)
 

+ 48 - 0
backend/open_webui/test/token_for_signin.py

@@ -0,0 +1,48 @@
+import jwt
+from datetime import datetime, timedelta
+
+def generate_jwt_token(
+    email: str,
+    name: str,
+    secret_key: str = "WEBUI_SECRET_KEY",  # 建议通过环境变量注入实际密钥
+    expires: int = 3600  # 默认有效期 1 小时(秒)
+) -> str:
+    """
+    生成包含用户信息的 JWT Token
+    
+    :param email: 用户邮箱(必需)
+    :param name: 用户名(必需)
+    :param secret_key: 加密密钥(默认示例值)
+    :param expires: Token 有效期(秒)
+    :return: JWT Token 字符串
+    """
+    payload = {
+        "email": email,
+        "name": name,
+        # 可添加其他字段
+        "exp": datetime.utcnow() + timedelta(seconds=expires)
+    }
+
+    return jwt.encode(
+        payload,
+        secret_key,
+        algorithm="HS256"
+    )
+
+# 示例用法
+if __name__ == "__main__":
+    # 生成有效 Token
+    valid_token = generate_jwt_token(
+        email="ygtx@ygtx.cn",
+        name="ygtx",
+        expires=3600
+    )
+    print("Valid Token:", valid_token)
+
+    # 生成已过期 Token(用于测试过期场景)
+    expired_token = generate_jwt_token(
+        email="expired@example.com",
+        name="Expired User",
+        expires=-10  # 负值强制过期
+    )
+    print("Expired Token:", expired_token)

+ 17 - 5
nginx-1.26.3/conf/nginx.conf

@@ -44,11 +44,20 @@ http {
         #access_log  logs/host.access.log  main;
 
         # 指向构建后的静态文件目录(使用相对路径)
-        root ../build;
-        index index.html;
-
         location / {
-            try_files $uri $uri/ /index.html;
+            proxy_pass http://localhost:8080;
+            proxy_set_header Host $host;
+            proxy_set_header X-Real-IP $remote_addr;
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto $scheme;
+        }
+
+        # 从 /auth 页面提取参数到变量
+        location /auth {
+            add_header Set-Cookie "auth_token=$arg_token; Path=/api/; HttpOnly";
+            # 存储参数到变量(需确保前端页面和 API 在同一个请求会话中)
+            set $auth_token $arg_token;
+            proxy_pass http://localhost:8080/auth;
         }
 
         # 代理后端API请求(关键配置)
@@ -63,6 +72,9 @@ http {
             proxy_set_header Host $host;
             proxy_set_header X-Real-IP $remote_addr;
             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+            # 将 Cookie 值作为请求头或查询参数传递
+            proxy_set_header X-Trusted-Token $cookie_auth_token;
         }
         # 处理/ws路径的Socket.IO连接(需添加协议升级头)
         location /ws/ {
@@ -76,7 +88,7 @@ http {
         }
 
         # 其他路由代理(根据FastAPI挂载路径)
-        location ~ ^/(ollama|openai|auth|users|chats|models|files|retrieval)/ {
+        location ~ ^/(ollama|openai|users|chats|models|files|retrieval)/ {
             proxy_pass http://localhost:8080;
             proxy_set_header Host $host;
             proxy_set_header X-Forwarded-Proto $scheme;