# +====================================================================== # | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ] # +---------------------------------------------------------------------- # | Copyright (c) 2021-2026 一米阳光团队 版权所有 # +---------------------------------------------------------------------- # | 开源协议: Apache-2.0 # | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。 # +---------------------------------------------------------------------- # | 官方网站: https://www.djangoadmin.cn # +---------------------------------------------------------------------- # | 研发团队: 一米阳光团队 荣誉出品 # +====================================================================== # | 版权和免责声明: # | 一、知识产权声明 # | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、 # | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权, # | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。 # | 二、使用限制与禁止条款 # | 您理解并同意,禁止将本产品用于以下任何行为: # | 1. 任何违反中华人民共和国法律法规的用途; # | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为; # | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。 # | 三、责任豁免 # | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失 # | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三 # | 方提起索赔或行政机关进行调查,您应独立承担全部责任。 # | 四、违约与法律责任 # | 如发现任何违反本声明的行为,本团队有权: # | 1. 无条件配合公安机关及其他有权机关的调查取证工作; # | 2. 依法追究相关方的民事责任,并要求赔偿全部损失; # | 3. 保留进一步采取刑事控告等法律措施的权利。 # | 五、许可范围说明 # | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包 # | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通 # | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、 # | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造 # | 成的一切经济损失及商誉损害。 # | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件 # | 许可协议》附件为准。 # +====================================================================== import fnmatch import logging from flask import request, g from config import LOGIN_WHITELIST_EXACT, LOGIN_WHITELIST_DEBUG, LOGIN_WHITELIST_PREFIX, LOGIN_WHITELIST_WILDCARD from utils import R from utils.jwt import parse_payload, is_token_blacklisted, get_access_token # 获取日志记录器 logger = logging.getLogger(__name__) def is_whitelisted(path: str) -> bool: """ 检查请求路径是否在白名单中 支持三种匹配模式: 1. 精确匹配:路径完全相等 2. 前缀匹配:路径以指定前缀开头 3. 通配符匹配:支持 *(匹配任意字符)和 ?(匹配单个字符) Args: path: 请求路径(如:/job/sendSms) Returns: bool: True-在白名单中,False-不在白名单中 """ # ============================================================ # 1. 精确匹配检查 # ============================================================ # 示例:LOGIN_WHITELIST_EXACT=/login,/captcha # 当 path 为 /login 或 /captcha 时返回 True exact_urls = LOGIN_WHITELIST_EXACT if exact_urls: for url in exact_urls.split(','): if url.strip() and url.strip() == path: if LOGIN_WHITELIST_DEBUG: logger.debug(f"精确匹配白名单: {path}") return True # ============================================================ # 2. 前缀匹配检查 # ============================================================ # 示例:LOGIN_WHITELIST_PREFIX=/job/sendSms,/public # 当 path 以 /job/sendSms 或 /public 开头时返回 True prefix_urls = LOGIN_WHITELIST_PREFIX if prefix_urls: for prefix in prefix_urls.split(','): prefix = prefix.strip() if prefix and path.startswith(prefix): if LOGIN_WHITELIST_DEBUG: logger.debug(f"前缀匹配白名单: {path} 匹配 {prefix}") return True # ============================================================ # 3. 通配符匹配检查 # ============================================================ # 示例:LOGIN_WHITELIST_WILDCARD=/job/*/send*,/api/v1/*/public* # 当 path 匹配通配符模式时返回 True # - * 匹配任意多个字符 # - ? 匹配单个字符 wildcard_patterns = LOGIN_WHITELIST_WILDCARD if wildcard_patterns: for pattern in wildcard_patterns.split(','): pattern = pattern.strip() if pattern and fnmatch.fnmatch(path, pattern): if LOGIN_WHITELIST_DEBUG: logger.debug(f"通配符匹配白名单: {path} 匹配 {pattern}") return True return False def check_login(): """ 用户登录验证中间件 功能说明: 1. 检查请求路径是否在白名单中,白名单路径直接放行 2. OPTIONS请求(CORS预检)直接放行 3. 验证JWT Token的有效性 4. Token验证通过后将用户信息存储到g对象中 白名单配置(在.env文件中): - LOGIN_WHITELIST_EXACT: 精确匹配白名单 - LOGIN_WHITELIST_PREFIX: 前缀匹配白名单 - LOGIN_WHITELIST_WILDCARD: 通配符匹配白名单 Returns: None: 验证通过,继续处理请求 R.failed: 验证失败,返回错误响应(401未登录) 使用示例: # 在app.py中注册中间件 @app.before_request def before_request(): result = check_login() if result: return result """ current_path = request.path # ============================================================ # 步骤1:检查白名单 # ============================================================ # 白名单中的路径不需要验证Token(如:登录页、验证码、短信发送接口等) if is_whitelisted(current_path): if LOGIN_WHITELIST_DEBUG: logger.debug(f"白名单路径,跳过登录验证: {current_path}") return None # ============================================================ # 步骤2:OPTIONS请求放行 # ============================================================ # OPTIONS请求是CORS预检请求,不需要验证Token if request.method == "OPTIONS": if LOGIN_WHITELIST_DEBUG: logger.debug(f"OPTIONS请求,跳过登录验证: {current_path}") return None # ============================================================ # 步骤3:获取并验证Token # ============================================================ success, access_token, msg = get_access_token() # 如果没有Token if not success: logger.warning(f"未登录访问: {current_path}, IP: {request.remote_addr}") return R.failed(code=401, msg="请先登录") # JWT解密验证 result = parse_payload(access_token) # 验证失败处理 if result.get('code') != 0: logger.warning(f"Token验证失败: {result.get('msg')}, 路径: {current_path}") return R.failed(code=401, msg=result.get('msg', "Token无效,请重新登录")) # ============================================================ # 步骤3.5:检查Token黑名单 # ============================================================ # 退出登录时Token会被加入黑名单,黑名单中的Token即使未过期也应拒绝 if is_token_blacklisted(access_token): logger.warning(f"Token已被注销(黑名单): user_id={result.get('data', {}).get('userId')}, 路径: {current_path}") return R.failed(code=401, msg="Token已失效,请重新登录") # ============================================================ # 步骤4:存储用户信息 # ============================================================ # 将用户信息存储到g对象中,供后续的视图函数和权限验证使用 if result.get('data'): g.user_id = result['data'].get('userId') g.user_info = result['data'] if LOGIN_WHITELIST_DEBUG: logger.debug(f"用户登录验证通过: user_id={g.user_id}, 路径={current_path}") # 验证通过,返回None继续处理请求 return None