对Yii2中开启`authenticator`后出现的跨域问题-修复

发布于:2025-07-19 ⋅ 阅读:(14) ⋅ 点赞:(0)

针对Yii2中开启authenticator后出现的跨域问题,以下是优化后的解决方案。主要修复点包括正确处理OPTIONS预检请求修复认证器配置优化CORS设置

<?php
namespace app\controllers;

use app\models\SystemLog;
use Yii;
use yii\filters\Cors;
use yii\rest\Controller;
use yii\filters\auth\HttpBearerAuth;
use yii\web\UnauthorizedHttpException;

class ApiController extends Controller
{
    // 日志级别常量
    const LOG_LEVEL_INFO = 'info';
    const LOG_LEVEL_WARNING = 'warning';
    const LOG_LEVEL_ERROR = 'error';

    public function behaviors()
    {
        $behaviors = parent::behaviors();

        // 移除默认的认证器
        unset($behaviors['authenticator']);

        // CORS 优先 - 修复关键点:允许Authorization头
        $behaviors['corsFilter'] = [
            'class' => Cors::class,
            'cors' => [
                'Origin' => Yii::$app->params['allowedOrigins'] ?? ['http://localhost:3002', 'http://api.mfe.local'],
                'Access-Control-Allow-Credentials' => true,
                'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
                'Access-Control-Request-Headers' => ['*'], // 允许所有头
                'Access-Control-Expose-Headers' => ['*'],  // 暴露所有头给客户端
                'Access-Control-Max-Age' => 86400,
            ],
        ];

        // 认证器配置 - 修复关键点:正确排除OPTIONS
        $behaviors['authenticator'] = [
            'class' => HttpBearerAuth::class,
            'except' => array_merge(['options'], $this->authOptional()),
        ];

        // 日志过滤器(最后执行)
        $behaviors['requestLogger'] = new class extends \yii\base\ActionFilter {
            public function beforeAction($action)
            {
                if (!Yii::$app->request->isOptions) {
                    Yii::info([
                        'action' => $action->id,
                        'request' => Yii::$app->request->rawBody,
                        'headers' => Yii::$app->request->headers->toArray(),
                    ], 'api_requests');
                }
                return parent::beforeAction($action);
            }
        ];

        return $behaviors;
    }

    // 确保OPTIONS方法被排除 - 修复关键点
    protected function authOptional()
    {
        return ['options'];
    }

    // 处理OPTIONS请求 - 新增方法
    public function actionOptions()
    {
        Yii::$app->response->statusCode = 200;
        return [];
    }

    // 认证失败处理 - 跳过OPTIONS请求
    public function beforeAction($action)
    {
        if (!parent::beforeAction($action)) {
            return false;
        }

        // 关键修复:跳过OPTIONS请求的认证检查
        if (Yii::$app->request->isOptions) {
            return true;
        }

        if (Yii::$app->user->isGuest && !in_array($action->id, $this->authOptional())) {
            throw new UnauthorizedHttpException('登录已过期,请重新登录');
        }

        return true;
    }

    /* 其余方法保持不变(getId, getWid, afterAction, success, error等) */
    // ...
}

关键修复说明:

  1. CORS配置优化

    'Access-Control-Request-Headers' => ['*'],     // 允许所有请求头
    'Access-Control-Expose-Headers' => ['*'],      // 暴露所有响应头
    

    确保浏览器能传递和处理Authorization

  2. 认证器配置修复

    unset($behaviors['authenticator']); // 移除默认认证器
    $behaviors['authenticator'] = [
        'except' => array_merge(['options'], $this->authOptional()),
    ];
    

    显式排除OPTIONS请求,避免预检请求被拦截

  3. OPTIONS请求处理

    public function actionOptions()
    {
        Yii::$app->response->statusCode = 200;
        return [];
    }
    

    添加专门的OPTIONS处理器返回空响应

  4. 认证检查跳过OPTIONS

    public function beforeAction($action)
    {
        // ...
        if (Yii::$app->request->isOptions) {
            return true; // 直接放行OPTIONS请求
        }
        // ...
    }
    
  5. 跨域白名单扩展(可选):
    在配置文件中增加更多允许的源:

    // config/params.php
    return [
        'allowedOrigins' => [
            'http://localhost:3002',
            'http://api.mfe.local',
            'https://your-production-domain.com'
        ],
    ];
    

补充建议:

  1. Nginx/Apache配置
    确保服务器配置允许跨域头:

    # Nginx配置示例
    add_header 'Access-Control-Allow-Origin' $http_origin always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers' '*' always;
    
  2. 前端Axios配置
    确保请求携带凭证:

    // 前端请求配置
    axios.defaults.withCredentials = true;
    
  3. 测试预检请求
    使用curl测试OPTIONS请求:

    curl -X OPTIONS http://api.mfe.local/api/user/info \
      -H "Origin: http://localhost:3002" \
      -H "Access-Control-Request-Method: GET" \
      -H "Access-Control-Request-Headers: authorization" \
      -I
    

这些修改确保:

  1. OPTIONS预检请求正确处理
  2. Authorization头能被跨域传递
  3. 认证中间件不会拦截预检请求
  4. 服务器返回正确的CORS响应头

修复后,带Bearer Token的跨域请求流程:

浏览器 API服务器 OPTIONS预检请求 跳过认证检查 返回CORS头(200) 实际请求(GET/POST...) 检查Bearer Token 返回数据(200) 返回401错误 alt [认证成功] [认证失败] 浏览器 API服务器

实测成功
【关键在于】
在这里插入图片描述

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到