我有一个 yii2 应用程序作为后端,我正在使用 React 作为前端。 我现在尝试通过 api 调用登录功能。但是当我尝试在前端登录时出现此错误:
login/:1 Access to XMLHttpRequest at 'http://localhost:8080/v1/user/login' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
我已经尝试过了。我找到了这个链接:
https://www.yiiframework.com/doc/api/2.0/yii-filters-cors
但没有解决问题。
所以我尝试从 yii2 配置控制器,它可以与前端通信。
控制器:
<?php
namespace app\modules\v1\controllers;
use Yii;
use app\models\User;
use yii\rest\ActiveController;
use yii\web\Response;
use yii\filters\Cors;
class UserController extends ActiveController
{
public $modelClass = 'app\models\User';
protected $_verbs = ['POST','OPTIONS'];
public function behaviors(){
$behaviors = parent::behaviors();
// remove auth filter before cors if you are using it
unset($behaviors['authenticator']);
// add CORS filter
$behaviors['corsFilter'] = [
'class' => \yii\filters\Cors::class,
'cors' => [
'Origin' => ['*'],
'Access-Control-Allow-Origin' => ['*', 'http://localhost:3000'],
'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
'Access-Control-Request-Headers' => ['*'],
'Access-Control-Allow-Credentials' => true,
'Access-Control-Max-Age' => 86400,
'Access-Control-Expose-Headers' => ['X-Pagination-Current-Page'],
],
];
$behaviors['authenticator']['except'] = ['options', 'login'];
return $behaviors;
}
public function actionLogin()
{
$username = Yii::$app->request->post('username');
$password = Yii::$app->request->post('password');
$user = User::findByUsername($username);
if ($user && $user->validatePassword($password)) {
return ['status' => 'success', 'message' => 'Login successful'];
} else {
Yii::$app->response->statusCode = 401;
return ['status' => 'error', 'message' => 'Invalid username or password'];
}
}
public $username;
public $password;
public $rememberMe = true;
private $_user;
public function rules()
{
return [
[['username', 'password'], 'required'],
['rememberMe', 'boolean'],
['password', 'validatePassword'],
];
}
/**
* Validates the password.
* This method serves as the inline validation for password.
*
* @param string $attribute the attribute currently being validated
* @param array $params the additional name-value pairs given in the rule
*/
public function validatePassword($attribute, $params)
{
if (!$this->hasErrors()) {
$user = $this->getUser();
if (!$user || !$user->validatePassword($this->password)) {
$this->addError($attribute, 'Incorrect username or password.');
}
}
}
protected function getUser()
{
if ($this->_user === null) {
$this->_user = User::findByUsername($this->username);
}
return $this->_user;
}
和用户:
<?php
// phpcs:ignoreFile
namespace app\models;
use Yii;
use yii\base\NotSupportedException;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;
use yii\filters\Cors;
/**
* This is the model class for table "user".
*
*/
class User extends \yii\db\ActiveRecord implements IdentityInterface
{
const STATUS_DELETED = 0;
const STATUS_INACTIVE = 9;
const STATUS_ACTIVE = 10;
public function behaviors()
{
return [
TimestampBehavior::class,
];
}
public static function tableName()
{
return '{{%user}}';
}
public function rules()
{
return [
[['status', 'created_at', 'updated_at'], 'default', 'value' => null],
[['status', 'created_at', 'updated_at'], 'integer'],
[['username', 'password_hash', 'password_reset_token', 'email', 'verification_token'], 'string', 'max' => 255],
[['auth_key'], 'string', 'max' => 32],
[['email'], 'unique'],
[['password_reset_token'], 'unique'],
[['username'], 'unique'],
['status', 'default', 'value' => self::STATUS_ACTIVE],
['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_INACTIVE, self::STATUS_DELETED]],
];
}
public static function findIdentity($id)
{
return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
}
public static function findIdentityByAccessToken($token, $type = null)
{
return static::findOne(['auth_key' => $token]);
}
public function getId()
{
return $this->getPrimaryKey();
}
public function getAuthKey()
{
return $this->auth_key;
}
}
和 web.php 文件:
<?php
// phpcs:ignoreFile
$params = require __DIR__ . '/params.php';
$db = require __DIR__ . '/db.php';
$config = [
'id' => 'basic',
'name' => 'internetsuite 2.0',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log'],
'aliases' => [
'@bower' => '@vendor/bower-asset',
'@npm' => '@vendor/npm-asset',
],
'modules' => [
'v1' => [
'class' => 'app\modules\v1\Module',
],
],
'components' => [
'request' => [
'parsers' => [
'application/json' => 'yii\web\JsonParser',
],
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
'cookieValidationKey' => 'OEtCunrAfQNETtmUSDnZw1JPHTB44i3A',
],
'cache' => [
'class' => 'yii\caching\FileCache',
],
'user' => [
'identityClass' => 'app\models\User',
'enableAutoLogin' => true,
],
'errorHandler' => [
'errorAction' => 'site/error',
],
'mailer' => [
'class' => \yii\symfonymailer\Mailer::class,
'viewPath' => '@app/mail',
// send all mails to a file by default.
'useFileTransport' => true,
],
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
[
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
],
],
],
'db' => $db,
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'rules' => [
'class' => 'yii\rest\UrlRule',
'controller' => 'v1/user',
'patterns'=>[
'POST' => 'signup',
'POST login' => 'login',
'OPTIONS login' => 'options',
],
],
],
],
'params' => $params,
];
if (YII_ENV_DEV) {
// configuration adjustments for 'dev' environment
$config['bootstrap'][] = 'debug';
$config['modules']['debug'] = [
'class' => 'yii\debug\Module',
// uncomment the following to add your IP if you are not connecting from localhost.
//'allowedIPs' => ['127.0.0.1', '::1'],
];
$config['bootstrap'][] = 'gii';
$config['modules']['gii'] = [
'class' => 'yii\gii\Module',
// uncomment the following to add your IP if you are not connecting from localhost.
//'allowedIPs' => ['127.0.0.1', '::1'],
];
}
return $config;
并对登录调用做出反应:
/* eslint-disable newline-before-return */
import { UserProfileToken } from "../models/User";
import axios from "axios";
import { handleError } from "../helpers/ErrorHandler";
const api = "http://localhost:8080/v1/";
export const loginAPI = async (username: string, password: string) => {
try {
const data = await axios.post<UserProfileToken>(api + "user/login", {
username: username,
password: password,
});
return data;
} catch (error) {
handleError(error);
}
};
}
问题:如何解决错误:
login/:1 Access to XMLHttpRequest at 'http://localhost:8080/v1/user/login' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
您的
UrlManager
规则设置存在一些问题。尽管使用了 yii\filters\Cors
,请求仍被 CORS 策略阻止也是这些问题的结果。
首先,这部分配置:
[
'class' => 'yii\rest\UrlRule',
'controller' => 'v1/user',
'POST' => 'v1/user/signup',
]
'POST' => 'v1/user/signup'
部分不起作用。我不确定您在复制代码进行提问时是否遗漏了太多内容,或者这是否就是您在项目中进行设置的方式。问题是,在 yii\rest\UrlRule
配置中,将方法与操作匹配必须是 $patterns
或 $extraPatterns
属性的一部分。这两个属性之间的主要区别在于 $patterns
包含 yii\rest\ActiveConroller
中的操作使用的一些默认模式。
因此其余规则配置应如下所示:
[
'class' => 'yii\rest\UrlRule',
'controller' => 'v1/user',
'patterns' => [
'POST' => 'signup',
// ... patterns for other HTTP methods
]
]
注册看起来有效的原因可能是因为请求与默认的发布模式匹配。看起来像这样
'POST' => 'create'
。用户的创建由 yii\rest\CreateAction
处理,它是默认 yii\rest\ActiveController
actions 的一部分。
另一个问题是,如果您希望休息控制器处理类似于
module/controller/action
(例如 v1/user/login
)格式的 url,您必须明确添加此路由的模式。这是因为 yii\rest\UrlRule
不使用通常的 controller/action
匹配标准规则。
因此,通过添加 v1/user/login
POST 请求模式,配置可能如下所示:
[
'class' => 'yii\rest\UrlRule',
'controller' => 'v1/user',
'patterns' => [
'POST' => 'signup',
'POST login' => 'login',
// ... patterns for other HTTP methods
]
]
现在我们终于进入 CORS 阻塞部分了。当浏览器要发送 AJAX POST 请求时,它所做的第一件事就是所谓的“CORS 预检”。这意味着它会尝试将 OPTIONS 请求发送到将首先发送 POST 请求以检索 CORS 标头的同一 URL。您的
UrlManager
不知道如何处理 v1/user/login
路线的 OPTIONS 请求。该请求未到达您的 UserController
,并且 yii\filters\Cors
永远不会应用于该请求。由于浏览器无法获取正确的 CORS 标头,并且 POST 请求被阻止。
为了避免这种情况,我们需要将 OPTIONS 请求的模式添加到我们的配置中,例如如下所示:
[
'class' => 'yii\rest\UrlRule',
'controller' => 'v1/user',
'patterns' => [
'POST' => 'signup',
'POST login' => 'login',
'OPTIONS login' => 'options',
// ... patterns for other HTTP methods
]
]
为了避免 CORS 阻塞,使用 options
默认操作中已经存在的
yii\rest\ActiveController
操作就足够了。对于正确的 REST API,您可能需要确保仅返回受支持的方法来响应 OPTIONS 请求。