单点登录平台Casdoor搭建与使用,集成gitlab同步创建删除账号

一,简介

一般来说,公司有很多系统使用,为了实现统一的用户名管理和登录所有系统(如 GitLab、Harbor 等),并在员工离职时只需删除一个主账号即可实现权限清除,可以采用 单点登录 (SSO) 和 集中式身份认证 系统。以下工具都可使用。

Keycloak(开源,功能强大且易于部署)

Okta/Auth0(商业化解决方案,支持更多高级功能)

LDAP(轻量级目录访问协议,可搭配 FreeIPA)

Casdoor (轻量级的身份认证和授权平台)

上面试用了keycloak和casdoor,keycloak配置复杂,而且界面不是很友好,所以选择了Casdoor。

casdoor用他们的云需要付费,自己服务器搭建则免费,所以这里自建就行了。

Casdoor是一款轻量级的身份认证和授权平台,支持多种协议(OIDC、OAuth2、SAML、CAS 等),并提供了简单易用的界面和丰富的功能。主要有以下特点:

1,,多协议支持

支持 OIDC、OAuth2、SAML、CAS 等协议,可以适配不同的应用系统。

支持单点登录(SSO),登录一次即可访问多个系统。

2,集中用户管理

提供用户增删改查功能,支持直接管理用户账户。

支持与 LDAP 集成,实现统一的用户信息管理。

3,权限控制

支持角色权限管理,可以为用户分配不同的权限和角色。

删除用户后,其在其他系统中的访问权限也会被移除。

4,易集成

提供 SDK 和 API,可以轻松集成到 GitLab、Jenkins、Harbor 等支持标准协议的系统中。

5,支持多种登录方式

支持密码登录、短信/邮件验证码登录,以及社交登录(如 Google、GitHub 等)。

满足企业和用户对灵活认证方式的需求。

如果公司有现成的LDAP使用,或者比较老的系统,可以使用Casdoor+LDAP方式,可以同步至casdoor集中管理。

二,Casdoor搭建

casdoor支持多语言,界面语言支持都非常友好,看文档也不费力,这也是选择它的一个重要原因。

这里测试,用最简单的docker搭建即可。生成环境建议其他正式安装方式。

创建文件夹,将配置文件挂载到此文件夹,便于配置。

官方配置文件下载:https://github.com/casdoor/casdoor/blob/master/conf/app.conf

bash 复制代码
mkdir /casdoor
cd /casdoor
vim app.conf

最后启动

bash 复制代码
docker run -d -p 8000:8000 -v /root/casdoor:/conf casbin/casdoor-all-in-one

由于与gitlab集成,必须需要SSL证书,所以用nginx反代绑定域名操作,这里示例casdoor.ywbj.cc

登录网站,默认账号密码admin 123

三,Casdoor基本使用

1,组织用户管理

用户管理,只有一个默认系统组织 built-in,这里新建一个公司组织company,便于管理。

company下,创建一个开发群组develop。

群组下有一个用户zhangsan(组织下有应用时才可以创建)

2,应用管理

在'身份认证'的'应用',里面,默认有一个应用app-built-in,主要功能是登录casdoor自己系统的。

目前此应用默认只有 默认组织 built-in 可以登录。其他不在此组织的用户登录都无法登录。

如果创建新组织新用户,需要登录casdoor,就还需要修改一下配置。

编辑默认应用,在组织选择模式,选择 "选择"或者"输入",都可以。

再次打开页面,可以选择组织,如这里测试的zhangsan只有选择company后,才能登录。

四,创建应用集成gitlab

1,casdoor创建应用

官网有集成说明文档,也可以参考:https://casdoor.org/zh/docs/integration/ruby/gitlab

在应用里面创建一个新的应用,用于gitlab集成。我这里gitlab测试网址为:gitlab.ywbj.cc

填写主要的信息就2个,组织选择company

重定向url,根据自己gitlab网址来写,这里示例为:

bash 复制代码
https://gitlab.ywbj.cc/users/auth/openid_connect/callback

记下ID和密钥,在gitlab配置需要用到。

2,gitlab配置

在gitlab服务器,修改配置文件

bash 复制代码
vi /etc/gitlab/gitlab.rb

在OmniA添加配置,当然在可以任何地方都可以添加,我添加这里便于管理。

将casdoor的ID和密钥,url写入即可。

bash 复制代码
gitlab_rails['gitlab_username_changing_enabled'] = false
gitlab_rails['omniauth_auto_link_user'] = true                              
gitlab_rails['omniauth_allow_single_sign_on'] = ['openid_connect']          
gitlab_rails['omniauth_block_auto_created_users'] = false                                              
                                                                            
gitlab_rails['omniauth_providers'] = [                                      
    {                                                                       
        name: "openid_connect",                                             
        label: "Casdoor", # 可选的登录按钮标签,默认为 "Openid Connect"     
        args: {                                                                     
            name: "openid_connect",                                                 
            scope: ["openid", "profile", "email"],                                  
            response_type: "code",                                                  
            issuer:  "https://casdoor.ywbj.cc",                                     
            client_auth_method: "query",                                            
            discovery: true,                                                        
            verify_ssl: false,                                                      
            uid_field: "preferred_username",                                        
            client_options: {                                                       
                identifier: "8f14334a3bd68bf336a9",                                 
                secret: "9dd1b27f0e1a30226d01385c73b5aabdfd4a91bd",                 
                redirect_uri: "https://gitlab.ywbj.cc/users/auth/openid_connect/callback"
            }                                                                            
        }                                                                                
    }                                                                                    
] 

配置文件说明,官方说明没有以下配置,需要加上去,否则登录失败报错:Signing in using your Casdoor account without a pre-existing account in gitlab.example.com is not allowed.

原因GitLab 不允许通过 Casdoor 登录的用户自动创建 GitLab 账户。这是 GitLab 的默认行为,因为 OpenID Connect 登录要求用户账户在 GitLab 中事先存在。

bash 复制代码
#为了账号名称统一管理,禁止用户更改登录用户名。默认用户可以更改自己的用户名。
gitlab_rails['gitlab_username_changing_enabled'] = false

#omniauth_auto_link_user: 自动链接现有 GitLab 账户(如果 email 匹配)。
gitlab_rails['omniauth_auto_link_user'] = true

#omniauth_allow_single_sign_on: 允许通过 OpenID Connect 登录自动创建账户。
gitlab_rails['omniauth_allow_single_sign_on'] = ['openid_connect']

#omniauth_block_auto_created_users: 设置为 false,允许自动创建的账户默认启用。
gitlab_rails['omniauth_block_auto_created_users'] = false

配置完成后,重新加载配置

bash 复制代码
gitlab-ctl reconfigure

3,登录测试

在casdoor 的用户管理,创建一个用户,组织company,应用选择刚才创建的gitlab。

然后登录,可以看到gitlab应用。

点击应用跳转,或者直接打开gitlab.ywbj.cc 登录界面,可以直接使用casdoor用户登录。

点击casdoor,可以直接点击登录,或者输入casdoor账号密码登录即可。

登录成功后,gitlab会自动创建同样的名称的账号,账号集成完成。

五,webhook自动删除锁定用户

上面集成了成功创建了gitlab账号,但是在casdoor删除用户时,gitlab是不会单独删除的,gitlab有自己的用户数据库。

所以这时需要搭建一个中间服务,用于检测casdoor操作并传送到gitlab执行。

casdoor支持webhook,可以检测并发送到其他api的执行。

这里检测两个动作,一个封锁或解封用户,一个删除用户。

1,创建中间服务

要实现真正的全自动化并且灵活、安全、可扩展,最佳选择仍然是使用一个中间服务器。

中间服务器的实现方式,可以参考以下实现方案:

技术选型:

语言:Python(Flask/FastAPI)、Node.js(Express)、Go 等都可以。

部署:可以运行在 Docker 容器中,方便与现有系统集成。

这里选用python的flask搭建。

首先在gitlab使用管理员创建一个访问令牌token,权限为api和sudo权限。

python安装flask,编辑中间服务。

bash 复制代码
pip install flask
vim wehook.py

最后完整代码如下:

python 复制代码
from flask import Flask, request, jsonify
import requests
import json
import logging

app = Flask(__name__)

# 设置日志
logging.basicConfig(
    filename='webhook.log',
    level=logging.INFO,
    format='%(asctime)s %(levelname)s: %(message)s'
)

GITLAB_API_URL = "https://gitlab.ywbj.cc/api/v4"
GITLAB_ACCESS_TOKEN = "glpat-CQk9oDfm-Dcz3f9NHojw"

@app.route('/casdoor-webhook', methods=['POST'])
def handle_webhook():
    # 记录请求内容
    logging.info("Headers: %s", request.headers)

    # Step 1: 解析 Webhook 数据
    data = request.data.decode('utf-8')
    try:
        json_data = json.loads(data)
    except json.JSONDecodeError:
        logging.error("Invalid JSON payload")
        return jsonify({"error": "Invalid JSON payload"}), 400

    #logging.info("Parsed JSON: %s", json_data)

    if json_data.get("action") == "update-user":
        object_data = json.loads(json_data["object"])
        name = object_data.get('name')
        email = object_data.get("email")
        is_forbidden = object_data.get("isForbidden", False)
        logging.info('Processing update-user for name: %s, email: %s, isForbidden: %s', name, email, is_forbidden)

        # Step 3: 在 GitLab 中查找用户 ID
        response = requests.get(
            f"{GITLAB_API_URL}/users?search={name}",
            headers={"Authorization": f"Bearer {GITLAB_ACCESS_TOKEN}"}
        )

        users = response.json()
        if not users or len(users) == 0:
            logging.warning("User not found in GitLab: %s", name)
            return jsonify({"error": "User not found"}), 404

        user_id = users[0]["id"]
        
        # Step 4: 根据 isForbidden 状态封锁或解封用户
        if is_forbidden:
            block_response = requests.post(
                f"{GITLAB_API_URL}/users/{user_id}/block",
                headers={"Authorization": f"Bearer {GITLAB_ACCESS_TOKEN}"}
            )
            logger.info("Block user response: %s", block_response.status_code)
            if block_response.status_code == 201:
                logger.info("User blocked successfully: %s", name)
                return jsonify({"message": "User blocked successfully"}), 200
            else:
                logger.error("Failed to block user: %s", name)
                return jsonify({"error": "Failed to block user"}), 500
        else:
            unblock_response = requests.post(
                f"{GITLAB_API_URL}/users/{user_id}/unblock",
                headers={"Authorization": f"Bearer {GITLAB_ACCESS_TOKEN}"}
            )
            logger.info("Unblock user response: %s", unblock_response.status_code)
            if unblock_response.status_code == 201:
                logger.info("User unblocked successfully: %s", name)
                return jsonify({"message": "User unblocked successfully"}), 200
            else:
                logger.error("Failed to unblock user: %s", name)
                return jsonify({"error": "Failed to unblock user"}), 500

    if json_data.get("action") == "delete-user":
        object_data = json.loads(json_data["object"])
        name = object_data.get('name')
        email = object_data.get("email")
        logging.info('Processing delete-user for name: %s, email: %s', name, email)

        # Step 3: 在 GitLab 中查找用户 ID
        response = requests.get(
            f"{GITLAB_API_URL}/users?search={name}",
            headers={"Authorization": f"Bearer {GITLAB_ACCESS_TOKEN}"}
        )
        #logging.info("GitLab search response: %s", response.json())

        users = response.json()
        if not users or len(users) == 0:
            logging.warning("User not found in GitLab: %s", name)
            return jsonify({"error": "User not found"}), 404

        user_id = users[0]["id"]

        # Step 4: 删除用户
        delete_response = requests.delete(
            f"{GITLAB_API_URL}/users/{user_id}",
            headers={"Authorization": f"Bearer {GITLAB_ACCESS_TOKEN}"}
        )
        if delete_response.status_code == 204:
            logging.info("User deleted successfully: %s", name)
            return jsonify({"message": "User deleted successfully"}), 200
        else:
            logging.error("Failed to delete user: %s", name)
            return jsonify({"error": "Failed to delete user"}), 500

    logging.warning("Invalid action: %s", json_data.get("action"))
    return jsonify({"error": "Invalid action"}), 400

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

最后运行即可测试。

bash 复制代码
python wehook.py

2,创建webhook

组织同样使用company。

链接:需要传给中间服务api的链接。

我这里用了nginx反代监听5000端口,使用域名https://webhook.ywbj.cc/casdoor-webhook作为api链接。

事件:检测update-user,delete-user两个动作,用于检测封锁还是删除用户。

3,casdoor封锁删除用户测试

在casdoor用户管理,编辑,打开被禁用,保存退出。

在gitlab管理员查看用户

同样,直接删除casdoor的用户,gitlab也直接删除。

在日志文件查看日志,也可以看到相关信息。

bash 复制代码
root@ht2024061430711:~/webhook# cat webhook.log 

2024-12-19 12:59:27,133 INFO: Processing update-user for name: zhangsan, email: 1i88kf@example.com, isForbidden: True
2024-12-19 12:59:28,059 INFO: Block user response: 201
2024-12-19 12:59:28,060 INFO: User blocked successfully: zhangsan
2024-12-19 12:59:28,062 INFO: 127.0.0.1 - - [19/Dec/2024 12:59:28] "POST /casdoor-webhook HTTP/1.0" 200 -
2024-12-19 13:01:42,837 INFO: Headers: Host: webhook.ywbj.cc

2024-12-19 13:01:42,839 INFO: Processing delete-user for name: zhangsan, email: 1i88kf@example.com
2024-12-19 13:01:43,328 INFO: User deleted successfully: zhangsan
2024-12-19 13:01:43,330 INFO: 127.0.0.1 - - [19/Dec/2024 13:01:43] "POST /casdoor-webhook HTTP/1.0" 200 -

六,构建docker持续运行(扩展)

以上已经实现基本功能,测试也可以直接用nohup 命令一直在后台运行。

但是便于稳定与管理,这里使用打包成docker镜像运行。

在运行flask应用时,经常看到警告

bash 复制代码
INFO: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. 

表明 Flask 当前正在使用其内置开发服务器,这种服务器仅适合开发和调试环境,不推荐在生产环境中使用。要优化并适用于生产环境,可以采用 WSGI 服务器,如 Gunicorn 或 uWSGI。

所以生产环境,这里采用Gunicorn。

1,在文件夹中创建必要的文件

创建Dockerfile构建文件

python 复制代码
# 使用官方 Python 基础镜像
FROM python:3.9-slim

# 设置工作目录
WORKDIR /app

# 复制当前目录到容器中
COPY . .

# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt

# 暴露 Flask 默认端口
EXPOSE 5000

# 使用 Gunicorn 启动 Flask 应用
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "webhook:app"]

Gunicorn 是一种高性能的 WSGI HTTP 服务器,适合生产环境,性能更稳定,支持多线程和多进程。

如果需要更高的性能,还可以调整 Gunicorn 参数,例如指定工作进程数和线程数:

bash 复制代码
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "3", "--threads", "2", "webhook:app"]

创建一个 requirements.txt 文件,用于存放 Python 依赖。内容如下:

bash 复制代码
flask
requests
gunicorn

创建一个 .dockerignore 文件,避免将不必要的文件复制到镜像中:

powershell 复制代码
__pycache__/
*.pyc
*.log
*.txt~
*.git

2,重新配置日志持久化

修改代码,确保日志写入到特定路径,例如 ./logs/webhook.log:

用于挂载日志文件到宿主机。

python 复制代码
# 确保日志路径
log_file_path = "./logs/webhook.log"

# 创建日志记录器
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.propagate = False

# 添加文件日志
file_handler = logging.FileHandler(log_file_path)
file_handler.setLevel(logging.INFO)
file_formatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s")
file_handler.setFormatter(file_formatter)

# 添加控制台日志
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.INFO)
stream_formatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s")
stream_handler.setFormatter(stream_formatter)

# 将日志处理器添加到记录器
logger.addHandler(file_handler)
logger.addHandler(stream_handler)

logger.info("Webhook server started. Logging initialized.")

后面loggin.info全部修改为logger.info即可

3,构建和运行 Docker 容器

bash 复制代码
#构建镜像
docker build -t flask-webhook .
#运行服务
docker run -d --restart unless-stopped --name flask-webhook -p 5000:5000 -v ./logs:/app/logs flask-webhook

查看服务并测试

bash 复制代码
root@ht2024061430711:~/webhook# docker ps
CONTAINER ID   IMAGE                       COMMAND                  CREATED         STATUS         PORTS                                       NAMES
0ffe0b07f6f7   flask-webhook               "gunicorn --bind 0.0..."   3 minutes ago   Up 3 minutes   0.0.0.0:5000->5000/tcp, :::5000->5000/tcp   flask-webhook

运行后查看日志

bash 复制代码
root@ht2024061430711:~/webhook# cat logs/webhook.log 
2024-12-19 15:36:38,991 INFO: Webhook server started. Logging initialized.
2024-12-19 15:36:38,991 INFO: Webhook server started. Logging initialized.
2024-12-19 07:53:26,415 INFO: Processing update-user for name: laowang, email: 1ul3ev@example.com, isForbidden: False
2024-12-19 07:53:27,171 INFO: Unblock user response: 201
2024-12-19 07:53:27,171 INFO: User unblocked successfully: laowang
2024-12-19 07:53:48,163 INFO: Headers: Host: webhook.ywbj.cc

2024-12-19 07:53:48,166 INFO: Processing delete-user for name: laowang, email: 1ul3ev@example.com
2024-12-19 07:53:48,583 INFO: User deleted successfully: laowang

运行正常。

下一步集成其他系统账号,有时间在更新。

相关推荐
A小辣椒2 小时前
TShark:基础知识
linux
AlfredZhao4 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao19 小时前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334661 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪1 天前
linux 拷贝文件或目录到指定的位置
linux
大树882 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质2 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5202 天前
Linux 11 动态监控指令top
linux