Files
4an-workorder/CASDOOR_INTEGRATION_GUIDE.md
T

455 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Casdoor 集成经验总结
本文档总结了在 Flask 项目中集成 Casdoor 并使用现有登录页面时遇到的所有问题和解决方案,帮助后续项目避免踩坑。
## 目录
1. [集成方式选择](#集成方式选择)
2. [配置步骤](#配置步骤)
3. [常见问题与解决方案](#常见问题与解决方案)
4. [最佳实践](#最佳实践)
5. [代码示例](#代码示例)
---
## 集成方式选择
### 方式一:OAuth 重定向方式(不推荐用于现有登录页面)
**特点:**
- 用户点击登录后跳转到 Casdoor 登录页面
- 登录成功后回调到项目
- 适合新项目或不需要保留现有登录页面的场景
**缺点:**
- 无法使用项目现有的登录页面
- 用户体验不够统一
### 方式二:API 方式(推荐)
**特点:**
- 使用 OAuth 2.0 password grant 方式
- 在项目现有登录页面输入用户名密码
- 通过 API 直接验证,无需跳转
- 用户体验更好,界面统一
**推荐使用方式二**
---
## 配置步骤
### 1. 环境变量配置
`.env` 文件或系统环境变量中添加:
```bash
# 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 ID``Client 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
```python
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
login_manager.login_message = '请先登录以访问此页面' # 设置中文提示
login_manager.login_message_category = 'info'
```
在登录成功后清除 flash 消息:
```python
# 在 auth.py 的登录成功处理中
session.pop('_flashes', None)
```
在模板中过滤已登录用户的未登录提示:
```html
<!-- 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 端点:
```python
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,只退出本地会话:
```python
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('login'))
```
如果需要同时退出 Casdoor,使用正确的退出 URL
```python
# 注意:需要确保 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 块中:
```python
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` 必须有对应的 `except``finally`
- 变量作用域要正确(在 try 块内定义的变量不能在块外使用)
### 问题 5:API 响应格式解析错误
**原因:**
- Casdoor API 返回格式可能不同
- 未处理各种可能的响应格式
**解决方案:**
兼容多种响应格式:
```python
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 返回的用户信息字段名可能不同
- 本地数据库字段映射不正确
**解决方案:**
使用多个可能的字段名:
```python
# 从 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', '')
```
### 问题 7:会话 Cookie 配置问题
**原因:**
- 跨域时 Cookie 无法正确设置
- SameSite 和 Secure 属性配置不当
**解决方案:**
`config.py` 中配置:
```python
# 跨域 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 调用
- 记录详细的错误日志,便于排查
- 对用户显示友好的错误提示
```python
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 调用结果
- 记录用户信息同步情况
```python
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 相关逻辑封装在独立的工具类中
- 保持路由处理简洁
- 使用配置类管理所有配置项
---
## 代码示例
### 完整的认证工具类结构
```python
# 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
```
### 登录路由处理
```python
# 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 官方文档](https://casdoor.org/docs/overview)
- [OAuth 2.0 Password Grant](https://oauth.net/2/grant-types/password/)
- [Flask-Login 文档](https://flask-login.readthedocs.io/)
---
## 总结
集成 Casdoor 到现有项目时,主要注意以下几点:
1. **选择正确的集成方式**API 方式更适合保留现有登录页面
2. **使用正确的 API 端点和参数**OAuth 2.0 password grant 方式
3. **完善的错误处理**:处理各种异常情况和响应格式
4. **用户体验优化**:清除残留提示,保持界面统一
5. **安全性考虑**:使用 HTTPS,不在日志中记录敏感信息
遵循以上实践,可以避免大部分常见问题,顺利完成 Casdoor 集成。