Files
4an-workorder/CASDOOR_INTEGRATION_GUIDE.md
T

12 KiB
Raw Blame History

Casdoor 集成经验总结

本文档总结了在 Flask 项目中集成 Casdoor 并使用现有登录页面时遇到的所有问题和解决方案,帮助后续项目避免踩坑。

目录

  1. 集成方式选择
  2. 配置步骤
  3. 常见问题与解决方案
  4. 最佳实践
  5. 代码示例

集成方式选择

方式一:OAuth 重定向方式(不推荐用于现有登录页面)

特点:

  • 用户点击登录后跳转到 Casdoor 登录页面
  • 登录成功后回调到项目
  • 适合新项目或不需要保留现有登录页面的场景

缺点:

  • 无法使用项目现有的登录页面
  • 用户体验不够统一

方式二:API 方式(推荐)

特点:

  • 使用 OAuth 2.0 password grant 方式
  • 在项目现有登录页面输入用户名密码
  • 通过 API 直接验证,无需跳转
  • 用户体验更好,界面统一

推荐使用方式二


配置步骤

1. 环境变量配置

.env 文件或系统环境变量中添加:

# Casdoor 服务地址
CASDOOR_ENDPOINT=https://your-casdoor-server.com

# Casdoor 应用配置(在 Casdoor 管理界面创建应用后获取)
CASDOOR_CLIENT_ID=your_client_id
CASDOOR_CLIENT_SECRET=your_client_secret

# 组织名称和应用名称(通常在 Casdoor 中配置)
CASDOOR_ORGANIZATION_NAME=your_organization
CASDOOR_APPLICATION_NAME=your_application

# 会话 Cookie 配置(跨域 SSO 时建议设置)
SESSION_COOKIE_SAMESITE=Lax  # 或 None(跨域时)
SESSION_COOKIE_SECURE=False  # HTTPS 时设为 True

2. 在 Casdoor 中创建应用

  1. 登录 Casdoor 管理界面
  2. 进入 "Applications" 页面
  3. 创建新应用,记录 Client IDClient Secret
  4. 重要:确保应用启用了 password grant 方式(如果 Casdoor 版本支持)

3. 代码集成

参考项目中的以下文件:

  • app/utils/casdoor_auth.py - Casdoor 认证工具类
  • app/views/auth.py - 认证路由处理
  • app/templates/login.html - 登录页面模板

常见问题与解决方案

问题 1:登录后提示 "Please log in to access this page"

原因:

  • Flask-Login 的默认提示消息是英文
  • 登录成功后可能有残留的 flash 消息

解决方案:

app.py 中配置 Flask-Login

login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
login_manager.login_message = '请先登录以访问此页面'  # 设置中文提示
login_manager.login_message_category = 'info'

在登录成功后清除 flash 消息:

# 在 auth.py 的登录成功处理中
session.pop('_flashes', None)

在模板中过滤已登录用户的未登录提示:

<!-- base.html -->
{% with messages = get_flashed_messages() %}
    {% if messages %}
        {% for message in messages %}
            {% if not (current_user.is_authenticated and '请先登录' in message) %}
                <!-- 显示消息 -->
            {% endif %}
        {% endfor %}
    {% endif %}
{% endwith %}

问题 2API 登录返回 "Unauthorized operation"

原因:

  • 使用了错误的 API 端点
  • 参数格式不正确
  • 未使用 OAuth 2.0 password grant 方式

解决方案:

使用正确的 OAuth 2.0 password grant 端点:

login_url = f"{endpoint}/api/login/oauth/access_token"

data = {
    'grant_type': 'password',  # 必须指定 grant_type
    'username': username,
    'password': password,
    'client_id': client_id,     # 必须包含 client_id
    'client_secret': client_secret,  # 必须包含 client_secret
    'scope': 'openid profile email phone'
}

注意: 某些 Casdoor 版本可能不支持 password grant,需要检查 Casdoor 文档或使用 OAuth 授权码方式。

问题 3:退出时跳转到 Casdoor 404 页面

原因:

  • 退出时尝试跳转到 Casdoor 的退出页面
  • Casdoor 退出 URL 不正确

解决方案:

如果不需要同时退出 Casdoor,只退出本地会话:

@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('login'))

如果需要同时退出 Casdoor,使用正确的退出 URL

# 注意:需要确保 redirect_uri 在 Casdoor 应用的回调白名单中
logout_url = f"{casdoor_endpoint}/login/oauth/logout?client_id={client_id}&redirect_uri={login_url}"

问题 4:语法错误 - try-except 块不匹配

原因:

  • 代码重构时 try 块未正确闭合
  • 在 try 块外使用了 try 块内定义的变量

解决方案:

确保所有逻辑都在完整的 try-except 块中:

try:
    # 所有逻辑代码
    response = requests.post(...)
    result = response.json()
    # ... 处理逻辑
    return result
except requests.exceptions.Timeout:
    # 处理超时
    return None
except requests.exceptions.RequestException as e:
    # 处理请求异常
    return None
except Exception as e:
    # 处理其他异常
    return None

检查要点:

  • 每个 try 必须有对应的 exceptfinally
  • 变量作用域要正确(在 try 块内定义的变量不能在块外使用)

问题 5API 响应格式解析错误

原因:

  • Casdoor API 返回格式可能不同
  • 未处理各种可能的响应格式

解决方案:

兼容多种响应格式:

result = response.json()

# 检查错误
if isinstance(result, dict) and result.get('status') == 'error':
    error_msg = result.get('msg', '未知错误')
    return None

# 尝试多种格式获取 token
access_token = None
if isinstance(result, dict):
    # 标准 OAuth 2.0 格式
    access_token = result.get('access_token')
    
    # Casdoor 格式 {"status": "ok", "data": {...}}
    if not access_token and result.get('status') == 'ok':
        token_data = result.get('data', {})
        if isinstance(token_data, dict):
            access_token = token_data.get('access_token')
    
    # 其他可能的字段
    if not access_token:
        access_token = result.get('token') or result.get('accessToken')

问题 6:用户信息同步问题

原因:

  • Casdoor 返回的用户信息字段名可能不同
  • 本地数据库字段映射不正确

解决方案:

使用多个可能的字段名:

# 从 Casdoor 用户信息中提取数据
casdoor_id = user_info.get('sub') or user_info.get('id') or user_info.get('name')
phone = user_info.get('phone') or user_info.get('phoneNumber') or casdoor_id
name = user_info.get('name') or user_info.get('displayName') or username
branch = user_info.get('affiliation', '')
role = user_info.get('type', '')

原因:

  • 跨域时 Cookie 无法正确设置
  • SameSite 和 Secure 属性配置不当

解决方案:

config.py 中配置:

# 跨域 SSO 时
SESSION_COOKIE_SAMESITE = 'None'  # 需要配合 Secure=True
SESSION_COOKIE_SECURE = True      # HTTPS 必须

# 同域时
SESSION_COOKIE_SAMESITE = 'Lax'
SESSION_COOKIE_SECURE = False     # HTTP 开发环境

注意: SameSite=None 必须配合 Secure=True 使用,且需要 HTTPS。


最佳实践

1. 错误处理

  • 始终使用 try-except 包裹 API 调用
  • 记录详细的错误日志,便于排查
  • 对用户显示友好的错误提示
try:
    result = casdoor_auth.login_with_password(username, password)
    if not result:
        flash('用户名或密码错误')
        return render_template('login.html')
except Exception as e:
    app.logger.error(f"登录异常: {str(e)}")
    flash('登录失败,请稍后重试')
    return render_template('login.html')

2. 日志记录

  • 记录登录尝试(不记录密码)
  • 记录 API 调用结果
  • 记录用户信息同步情况
app.logger.info(f"尝试 Casdoor API 登录: username={username}")
app.logger.info(f"Casdoor 用户信息: {user_info}")
app.logger.info(f"用户 {user.name}({user.phone}) 通过 Casdoor API 登录成功")

3. 安全性

  • 永远不要在日志中记录密码
  • 使用 HTTPS(生产环境)
  • 验证和清理用户输入
  • 使用环境变量存储敏感配置

4. 用户体验

  • 提供清晰的错误提示
  • 登录成功后清除残留的提示消息
  • 保持登录页面的原有样式

5. 代码组织

  • 将 Casdoor 相关逻辑封装在独立的工具类中
  • 保持路由处理简洁
  • 使用配置类管理所有配置项

代码示例

完整的认证工具类结构

# app/utils/casdoor_auth.py
class CasdoorAuth:
    def __init__(self, app=None):
        self.app = app
        if app:
            self.init_app(app)
    
    def init_app(self, app):
        """初始化应用配置"""
        self.endpoint = app.config.get('CASDOOR_ENDPOINT')
        self.client_id = app.config.get('CASDOOR_CLIENT_ID')
        self.client_secret = app.config.get('CASDOOR_CLIENT_SECRET')
        self.organization_name = app.config.get('CASDOOR_ORGANIZATION_NAME', 'built-in')
        self.application_name = app.config.get('CASDOOR_APPLICATION_NAME', 'app-built-in')
    
    def login_with_password(self, username, password):
        """使用用户名和密码通过 API 登录"""
        login_url = f"{self.endpoint}/api/login/oauth/access_token"
        data = {
            'grant_type': 'password',
            'username': username,
            'password': password,
            'client_id': self.client_id,
            'client_secret': self.client_secret,
            'scope': 'openid profile email phone'
        }
        
        try:
            response = requests.post(login_url, data=data, timeout=10)
            # ... 处理响应
        except Exception as e:
            # ... 错误处理
            return None
    
    def get_user_info(self, access_token):
        """使用访问令牌获取用户信息"""
        # ... 实现
        pass

登录路由处理

# app/views/auth.py
@app.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('dashboard'))
    
    if request.method == 'POST':
        username = request.form.get('username', '').strip()
        password = request.form.get('password', '').strip()
        
        if not username or not password:
            flash('请输入用户名和密码')
            return render_template('login.html')
        
        # 使用 API 方式登录
        result = casdoor_auth.login_with_password(username, password)
        
        if not result:
            flash('用户名或密码错误')
            return render_template('login.html')
        
        # 处理用户信息同步
        user_info = result.get('user_info', {})
        # ... 创建或更新用户
        
        login_user(user)
        session.pop('_flashes', None)  # 清除残留提示
        return redirect(url_for('dashboard'))
    
    return render_template('login.html')

检查清单

在集成 Casdoor 时,确保完成以下检查:

  • 环境变量配置正确
  • Casdoor 应用已创建并获取 Client ID 和 Secret
  • 使用正确的 API 端点(/api/login/oauth/access_token
  • 使用 OAuth 2.0 password grant 方式
  • 所有 try-except 块正确闭合
  • 错误处理完善,有详细的日志记录
  • 登录成功后清除残留的 flash 消息
  • 用户信息同步逻辑正确
  • 会话 Cookie 配置正确(跨域时)
  • 登录页面样式保持一致
  • 测试各种错误场景(错误密码、网络错误等)

参考资源


总结

集成 Casdoor 到现有项目时,主要注意以下几点:

  1. 选择正确的集成方式API 方式更适合保留现有登录页面
  2. 使用正确的 API 端点和参数OAuth 2.0 password grant 方式
  3. 完善的错误处理:处理各种异常情况和响应格式
  4. 用户体验优化:清除残留提示,保持界面统一
  5. 安全性考虑:使用 HTTPS,不在日志中记录敏感信息

遵循以上实践,可以避免大部分常见问题,顺利完成 Casdoor 集成。