docker版open-webui增加钉钉登录
之前公司使用的chatgpt-web开源项目已经正式宣布停止更新了, 现在准备使用open-webui进行代替...那么就需要进行一下二次开发以便支持钉钉登录
原理很简单, 获取钉钉登录后的用户信息,自动生成一个邮箱账号和密码,邮箱账号我直接使用的md5(钉钉昵称),密码直接使用的open_id+盐,这里只作为一个方法展示,不探讨密码安全性
其实非docker版应该操作方式大差不差
接下来是假设你已经安装并成功运行了open-webui
解决方案
先是对 /app/backend/open_webui/routers/auths.py 文件的修改,主要修改有两个地方,第一个是修改 signup 函数让其支持在关闭注册权限的情况下进行自动注册;第二个是增加 signin_dingding 函数以便支持钉钉自动注册、登录,修改如下:
修改
signup函数,中文备注为修改位置,最好手动修改,不要直接覆盖,不保证后续版本的代码和下面一致!
# 接收dingding参数
async def signup(request: Request, response: Response, form_data: SignupForm, dingding=False):
if WEBUI_AUTH:
if (
(not request.app.state.config.ENABLE_SIGNUP
or not request.app.state.config.ENABLE_LOGIN_FORM) and not dingding # 增加钉钉判断,注意前面加了一对括号
):
raise HTTPException(
status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
)
else:
if Users.get_num_users() != 0:
raise HTTPException(
status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
)
if not validate_email_format(form_data.email.lower()):
raise HTTPException(
status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT
)
if Users.get_user_by_email(form_data.email.lower()):
raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
try:
role = (
"admin"
if Users.get_num_users() == 0
else request.app.state.config.DEFAULT_USER_ROLE
)
if Users.get_num_users() == 0:
# Disable signup after the first user is created
request.app.state.config.ENABLE_SIGNUP = False
hashed = get_password_hash(form_data.password)
user = Auths.insert_new_auth(
form_data.email.lower(),
hashed,
form_data.name,
form_data.profile_image_url,
role,
)
if user:
expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN)
expires_at = None
if expires_delta:
expires_at = int(time.time()) + int(expires_delta.total_seconds())
token = create_token(
data={"id": user.id},
expires_delta=expires_delta,
)
datetime_expires_at = (
datetime.datetime.fromtimestamp(expires_at, datetime.timezone.utc)
if expires_at
else None
)
# Set the cookie token
response.set_cookie(
key="token",
value=token,
expires=datetime_expires_at,
httponly=True, # Ensures the cookie is not accessible via JavaScript
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
secure=WEBUI_AUTH_COOKIE_SECURE,
)
if request.app.state.config.WEBHOOK_URL:
post_webhook(
request.app.state.config.WEBHOOK_URL,
WEBHOOK_MESSAGES.USER_SIGNUP(user.name),
{
"action": "signup",
"message": WEBHOOK_MESSAGES.USER_SIGNUP(user.name),
"user": user.model_dump_json(exclude_none=True),
},
)
user_permissions = get_permissions(
user.id, request.app.state.config.USER_PERMISSIONS
)
return {
"token": token,
"token_type": "Bearer",
"expires_at": expires_at,
"id": user.id,
"email": user.email,
"name": user.name,
"role": user.role,
"profile_image_url": user.profile_image_url,
"permissions": user_permissions,
}
else:
raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_USER_ERROR)
except Exception as err:
raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
增加
signin_dingding函数以便支持钉钉自动创建账号、登录
import hashlib
import json
import requests
from fastapi.responses import HTMLResponse
@router.get("/signin_dingding", response_class=HTMLResponse)
async def signin_dingding(request: Request, response: Response, code: str='', authCode: str=''):
req = requests.post('https://api.dingtalk.com/v1.0/oauth2/userAccessToken', data=json.dumps({
'clientSecret': 'clientSecret', # 自行替换
'clientId': 'clientId', # 自行替换
'code': code,
'grantType': 'authorization_code'
}), headers={
'Content-Type': 'application/json'
}
)
json_data = req.json()
if json_data['corpId'] != 'xxx': # 自行替换, 用于防止其他企业的钉钉账号登录
raise HTTPException(400, detail="错误的企业身份")
req = requests.get('https://api.dingtalk.com/v1.0/contact/users/me', headers={
'Content-Type': 'application/json',
'x-acs-dingtalk-access-token': json_data['accessToken']
}
)
json_data = req.json()
print(json_data, flush=True)
open_id = json_data['openId']
nickname = json_data['nick']
email = '%s@virtual.com' % hashlib.md5(nickname.encode('utf-8')).hexdigest()
if not Users.get_user_by_email(email):
await signup(
request,
response,
SignupForm(
email=email, password='%s_@password' % open_id, name=nickname
),
dingding=True,
)
user = Auths.authenticate_user(email, '%s_@password' % open_id)
if user:
expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN)
expires_at = None
if expires_delta:
expires_at = int(time.time()) + int(expires_delta.total_seconds())
token = create_token(
data={"id": user.id},
expires_delta=expires_delta,
)
datetime_expires_at = (
datetime.datetime.fromtimestamp(expires_at, datetime.timezone.utc)
if expires_at
else None
)
# Set the cookie token
response.set_cookie(
key="token",
value=token,
expires=datetime_expires_at,
httponly=True, # Ensures the cookie is not accessible via JavaScript
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
secure=WEBUI_AUTH_COOKIE_SECURE,
)
user_permissions = get_permissions(
user.id, request.app.state.config.USER_PERMISSIONS
)
return """登录成功, 正在跳转...
<script>
window.localStorage.setItem("token", "%s");
setTimeout(function(){
document.location.href = '/';
}, 1000);
</script>
""" % token
else:
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
好了。后端文件的修改就完成了,接下来是前端代码,本着能偷懒就不多干的原则,这里我就没有直接修改ui源码,然后进行编译,我是直接修改的 /app/build/index.html 文件,直接做了个循环,主打一个能用就行
在
/app/build/index.html文件底部追加如下内容
<script>
t = setInterval(function(){
if(location.href.indexOf('/auth') > -1 && document.querySelectorAll("#dingding_div").length == 0 &&
document.querySelectorAll("button").length > 0){
let dingdingDiv = document.createElement("div");
dingdingDiv.id = 'dingding_div';
dingdingDiv.innerHTML = "<a href='https://login.dingtalk.com/oauth2/challenge.htm?client_id=自行替换&response_type=code&scope=corpid&prompt=consent&redirect_uri=https://自行替换.com/api/v1/auths/signin_dingding'>使用钉钉登录</a>"
document.querySelectorAll("button")[document.querySelectorAll("button").length - 1].parentElement.appendChild(dingdingDiv);
}
}, 100);
</script>
显示效果如图所示,最后在钉钉后台设置回调地址为 https://xxxx.com/api/v1/auths/signin_dingding, 完成!

评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。