Azure Web应用部署24小时后SSO失效,数据库令牌过期报错
令牌过期导致SSO失效问题排查与解决
问题根源分析
错误提示显示数据库登录用的<token-identified principal>令牌过期,核心原因有两点:
- SQLAlchemy连接池复用旧连接:应用启动时创建的数据库连接池会长期复用连接,这些连接携带的令牌过期后不会自动更新。
- 全局变量的进程隔离问题:Azure Web Apps是多进程运行环境,
get_fresh_token中的全局变量_cached_token和_token_expiry无法在进程间共享,导致部分进程的令牌未及时刷新。
具体解决方案
1. 修改数据库连接初始化逻辑
确保每次创建数据库连接时都使用最新的令牌,而非复用初始化时的旧令牌。示例代码:
import urllib.parse from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() def init_db_connection(app): fresh_token = get_fresh_token() if not fresh_token: raise Exception("无法获取有效的数据库访问令牌") # 动态构建包含最新令牌的连接字符串 conn_str = ( f"DRIVER={{ODBC Driver 17 for SQL Server}};" f"SERVER={app.config['DB_SERVER']};" f"DATABASE={app.config['DB_NAME']};" f"UID=AnyString;" f"PWD={fresh_token};" f"Authentication=ActiveDirectoryPassword" ) encoded_conn_str = urllib.parse.quote_plus(conn_str) # 配置SQLAlchemy引擎,加入连接池回收策略 app.config['SQLALCHEMY_DATABASE_URI'] = f"mssql+pyodbc:///?odbc_connect={encoded_conn_str}" app.config['SQLALCHEMY_ENGINE_OPTIONS'] = { 'pool_recycle': 3500, # 3500秒后自动回收连接(小于令牌默认有效期3600秒) 'pool_pre_ping': True # 获取连接前自动校验连接有效性 } db.init_app(app)
2. 替换全局变量为应用缓存
避免多进程环境下令牌不一致问题,改用Flask缓存存储令牌:
from flask_caching import Cache from datetime import datetime, timedelta import requests # 初始化缓存(生产环境建议用Redis缓存) cache = Cache(config={ 'CACHE_TYPE': 'SimpleCache', 'CACHE_DEFAULT_TIMEOUT': 3500 # 提前5分钟过期 }) def get_fresh_token(): cached_token = cache.get('db_access_token') token_expiry = cache.get('db_token_expiry') # 校验令牌是否仍有效 if cached_token and token_expiry and datetime.now() < token_expiry - timedelta(minutes=5): return cached_token # 请求新令牌 token_url = f'https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token' token_data = { 'grant_type': 'client_credentials', 'client_id': CLIENT_ID, 'client_secret': CLIENT_SECRET, 'scope': RESOURCE_URL + '/.default' } try: token_response = requests.post(token_url, data=token_data) token_response.raise_for_status() response_json = token_response.json() token = response_json.get('access_token') expires_in = int(response_json.get('expires_in', 3600)) token_expiry = datetime.now() + timedelta(seconds=expires_in) # 更新缓存 cache.set('db_access_token', token, timeout=expires_in - 300) cache.set('db_token_expiry', token_expiry) return token except Exception as e: logger.error(f"获取令牌失败: {e}") return None
3. 优化回调函数中的数据库连接调用
移除回调函数中重复的init_db_connection调用,确保每次请求都使用最新的连接配置:
@bp.route('/callback') def callback(): # ... 原有逻辑省略 ... try: # 确保使用最新令牌初始化数据库连接 init_db_connection(current_app) return redirect(url_for('main')) except Exception as e: logger.error(f"认证错误: {str(e)}", exc_info=True) flash(f'认证错误: {str(e)}', 'error') return redirect(url_for('auth.login'))
验证步骤
- 部署修改后的应用到Azure Web Apps
- 观察24小时后是否再出现令牌过期错误
- 查看日志确认令牌刷新逻辑正常执行
内容的提问来源于stack exchange,提问作者KNG
相关产品推荐
相关产品推荐

