之前公司使用的chatgpt-web开源项目已经正式宣布停止更新了, 现在准备使用open-webui进行代替...那么就需要进行一下二次开发以便支持钉钉登录
原理很简单, 获取钉钉登录后的用户信息,自动生成一个邮箱账号和密码,邮箱账号我直接使用的md5(钉钉昵称),密码直接使用的open_id+盐,这里只作为一个方法展示,不探讨密码安全性
其实非docker版应该操作方式大差不差
接下来是假设你已经安装并成功运行了open-webui
解决方案
先是对/app/backend/open_webui/routers/auths.py
文件的修改,主要修改有两个地方,第一个是修改signup
函数让其支持在关闭注册权限的情况下进行自动注册;第二个是增加signin_dingding
函数以便支持钉钉自动注册、登录,修改如下:
修改
signup
函数,中文备注为修改位置,最好手动修改,不要直接覆盖,不保证后续版本的代码和下面一致!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# 接收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
函数以便支持钉钉自动创建账号、登录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
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
文件底部追加如下内容
1 2 3 4 5 6 7 8 9 10 11 |
<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
, 完成!