from flask import render_template, request, redirect, url_for, flash, session import urllib.parse from flask_login import login_user, login_required, logout_user, current_user from scripts.models import db, User from utils.casdoor_auth import casdoor_auth def init_auth_routes(app): # 初始化 Casdoor 认证 casdoor_auth.init_app(app) @app.route('/login', methods=['GET', 'POST']) def login(): # 如果已登录,直接跳转到仪表盘 if current_user.is_authenticated: return redirect(url_for('dashboard')) # 未登录展示登录页(登录页会自动跳转 Casdoor,如有错误提示则不跳转) return render_template('login.html') @app.route('/casdoor/login') def casdoor_login(): """重定向到 Casdoor 登录页面""" if not app.config.get('CASDOOR_CLIENT_ID'): flash('Casdoor 未配置,无法登录') return redirect(url_for('login')) auth_url = casdoor_auth.get_authorization_url() app.logger.info('重定向到 Casdoor 授权页面') return redirect(auth_url) @app.route('/callback') def casdoor_callback(): """Casdoor OAuth 回调处理""" code = request.args.get('code') state = request.args.get('state') error = request.args.get('error') if error: app.logger.error(f'Casdoor 认证失败: {error}') flash(f'登录失败: {error}') return redirect(url_for('login')) if not code: app.logger.warning('Casdoor 回调缺少授权码') flash('登录失败:未收到授权码') return redirect(url_for('login')) # 验证 state 参数 if not casdoor_auth.verify_state(state): app.logger.warning('Casdoor state 验证失败,可能存在 CSRF 攻击') flash('登录失败:安全验证失败') return redirect(url_for('login')) # 获取访问令牌 token_data = casdoor_auth.get_token(code) if not token_data or 'access_token' not in token_data: app.logger.error('获取 Casdoor token 失败') flash('登录失败:无法获取访问令牌') return redirect(url_for('login')) access_token = token_data['access_token'] # 获取用户信息 user_info = casdoor_auth.get_user_info(access_token) if not user_info: app.logger.error('获取 Casdoor 用户信息失败') flash('登录失败:无法获取用户信息') return redirect(url_for('login')) # 获取完整账户信息(包含标签) account_info = casdoor_auth.get_account_info(access_token) if not account_info: app.logger.error('获取 Casdoor 账户信息失败') flash('登录失败:无法获取账户信息') return redirect(url_for('login')) # 从 Casdoor 用户信息中提取数据 # Casdoor 返回的用户信息可能包含:name, email, phone, id 等字段 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 '未知用户' # 基于标签的访问控制:用户标签必须等于项目名 project_name = app.config.get('PROJECT_NAME') if not project_name: app.logger.error('未配置 PROJECT_NAME 环境变量,拒绝登录') flash('登录失败:未配置项目名') return redirect(url_for('login')) raw_tags = account_info.get('tag') or account_info.get('tags') if isinstance(raw_tags, str): user_tags = [t.strip() for t in raw_tags.split(',') if t.strip()] elif isinstance(raw_tags, list): user_tags = [str(t).strip() for t in raw_tags if str(t).strip()] else: user_tags = [] if project_name not in user_tags: app.logger.warning(f'用户 {name}({phone}) 标签 {user_tags} 不匹配项目 {project_name}') flash('您没有本系统的访问权限,请联系管理员处理') return redirect(url_for('login')) app.logger.info(f'Casdoor 用户信息: {user_info}') # 查找或创建本地用户 user = User.query.filter_by(phone=phone).first() if not user: user = User( phone=phone, name=name, branch=user_info.get('affiliation', ''), role=user_info.get('type', '') ) db.session.add(user) db.session.commit() app.logger.info(f'创建新用户: {name}({phone})') else: user.name = name if user_info.get('affiliation'): user.branch = user_info.get('affiliation') if user_info.get('type'): user.role = user_info.get('type') db.session.commit() app.logger.info(f'更新用户信息: {name}({phone})') login_user(user) app.logger.info(f'用户 {user.name}({user.phone}) 通过 Casdoor 登录成功') # 清除可能残留的未登录提示 session.pop('_flashes', None) return redirect(url_for('dashboard')) @app.route('/logout') @login_required def logout(): app.logger.info(f'用户 {current_user.name}({current_user.phone}) 登出') logout_user() # 仅退出本地会话,保留 Casdoor 登录态 return redirect(url_for('login'))