Files
xk-api-yii/admin/foundation/Cors.php
2024-12-18 09:11:02 +08:00

323 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace admin\foundation;
use Yii;
use yii\base\ActionFilter;
use yii\base\InvalidConfigException;
use yii\web\Request;
use yii\web\Response;
/**
* Cors filter implements [Cross Origin Resource Sharing](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing).
*
* Make sure to read carefully what CORS does and does not. CORS do not secure your API,
* but allow the developer to grant access to third party code (ajax calls from external domain).
*
* You may use CORS filter by attaching it as a behavior to a controller or module, like the following,
*
* ```php
* public function behaviors()
* {
* return [
* 'corsFilter' => [
* 'class' => \yii\filters\Cors::className(),
* ],
* ];
* }
* ```
*
* The CORS filter can be specialized to restrict parameters, like this,
* [MDN CORS Information](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS)
*
* ```php
* public function behaviors()
* {
* return [
* 'corsFilter' => [
* 'class' => \yii\filters\Cors::className(),
* 'cors' => [
* // restrict access to
* 'Origin' => ['http://www.myserver.com', 'https://www.myserver.com'],
* // Allow only POST and PUT methods
* 'Access-Control-Request-Method' => ['POST', 'PUT'],
* // Allow only headers 'X-Wsse'
* 'Access-Control-Request-Headers' => ['X-Wsse'],
* // Allow credentials (cookies, authorization headers, etc.) to be exposed to the browser
* 'Access-Control-Allow-Credentials' => true,
* // Allow OPTIONS caching
* 'Access-Control-Max-Age' => 3600,
* // Allow the X-Pagination-Current-Page header to be exposed to the browser.
* 'Access-Control-Expose-Headers' => ['X-Pagination-Current-Page'],
* ],
*
* ],
* ];
* }
* ```
*
* For more information on how to add the CORS filter to a controller, see
* the [Guide on REST controllers](guide:rest-controllers#cors).
*
* @author Philippe Gaultier <pgaultier@gmail.com>
* @since 2.0
*/
class Cors extends ActionFilter
{
/**
* @var mixed
*/
private static $param;
/**
* @var Request the current request. If not set, the `request` application component will be used.
*/
public $request;
/**
* @var Response the response to be sent. If not set, the `response` application component will be used.
*/
public $response;
/**
* @var array define specific CORS rules for specific actions
*/
public $actions = [];
/**
* @var array Basic headers handled for the CORS requests.
*/
public $cors = [
'Origin' => ['*'],
'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
'Access-Control-Request-Headers' => ['*'],
'Access-Control-Allow-Credentials' => null,
'Access-Control-Max-Age' => 86400,
'Access-Control-Expose-Headers' => [],
];
/**
* {@inheritdoc}
*/
public function beforeAction($action)
{
$this->request = $this->request ?: Yii::$app->getRequest();
// 解密传参
$method = Yii::$app->request->getMethod();
self::$param = $method === 'GET' ? Yii::$app->request->get() : Yii::$app->request->post();
if (empty(self::$param)) {
self::$param = [];
} else {
self::$param = self::aseDecode(self::$param);
}
if ($method === 'GET') {
Yii::$app->request->setQueryParams(self::$param);
} else {
// 创建一个新的Request对象并设置修改后的POST数据
$newRequest = new Request();
// 替换应用的请求对象
$newRequest->setBodyParams(self::$param);
Yii::$app->set('request', $newRequest);
// Yii::$app->request->setParams(self::$param);
// Yii::$app->request->setBodyParams(self::$param);
}
$this->response = $this->response ?: Yii::$app->getResponse();
$this->overrideDefaultSettings($action);
$requestCorsHeaders = $this->extractHeaders();
$responseCorsHeaders = $this->prepareHeaders($requestCorsHeaders);
$this->addCorsHeaders($this->response, $responseCorsHeaders);
if ($this->request->isOptions && $this->request->headers->has('Access-Control-Request-Method')) {
// it is CORS preflight request, respond with 200 OK without further processing
$this->response->setStatusCode(200);
Yii::$app->end();
return false;
}
return true;
}
/**
* Override settings for specific action.
* @param \yii\base\Action $action the action settings to override
*/
public function overrideDefaultSettings($action)
{
if (isset($this->actions[$action->id])) {
$actionParams = $this->actions[$action->id];
$actionParamsKeys = array_keys($actionParams);
foreach ($this->cors as $headerField => $headerValue) {
if (in_array($headerField, $actionParamsKeys)) {
$this->cors[$headerField] = $actionParams[$headerField];
}
}
}
}
/**
* Extract CORS headers from the request.
* @return array CORS headers to handle
*/
public function extractHeaders()
{
$headers = [];
foreach (array_keys($this->cors) as $headerField) {
$serverField = $this->headerizeToPhp($headerField);
$headerData = isset($_SERVER[$serverField]) ? $_SERVER[$serverField] : null;
if ($headerData !== null) {
$headers[$headerField] = $headerData;
}
}
return $headers;
}
/**
* For each CORS headers create the specific response.
* @param array $requestHeaders CORS headers we have detected
* @return array CORS headers ready to be sent
*/
public function prepareHeaders($requestHeaders)
{
$responseHeaders = [];
// handle Origin
if (isset($requestHeaders['Origin'], $this->cors['Origin'])) {
if (in_array($requestHeaders['Origin'], $this->cors['Origin'], true)) {
$responseHeaders['Access-Control-Allow-Origin'] = $requestHeaders['Origin'];
}
if (in_array('*', $this->cors['Origin'], true)) {
// Per CORS standard (https://fetch.spec.whatwg.org), wildcard origins shouldn't be used together with credentials
if (isset($this->cors['Access-Control-Allow-Credentials']) && $this->cors['Access-Control-Allow-Credentials']) {
if (YII_DEBUG) {
throw new InvalidConfigException("Allowing credentials for wildcard origins is insecure. Please specify more restrictive origins or set 'credentials' to false in your CORS configuration.");
} else {
Yii::error("Allowing credentials for wildcard origins is insecure. Please specify more restrictive origins or set 'credentials' to false in your CORS configuration.", __METHOD__);
}
} else {
$responseHeaders['Access-Control-Allow-Origin'] = '*';
}
}
}
$this->prepareAllowHeaders('Headers', $requestHeaders, $responseHeaders);
if (isset($requestHeaders['Access-Control-Request-Method'])) {
$responseHeaders['Access-Control-Allow-Methods'] = implode(', ', $this->cors['Access-Control-Request-Method']);
}
if (isset($this->cors['Access-Control-Allow-Credentials'])) {
$responseHeaders['Access-Control-Allow-Credentials'] = $this->cors['Access-Control-Allow-Credentials'] ? 'true' : 'false';
}
if (isset($this->cors['Access-Control-Max-Age']) && $this->request->getIsOptions()) {
$responseHeaders['Access-Control-Max-Age'] = $this->cors['Access-Control-Max-Age'];
}
if (isset($this->cors['Access-Control-Expose-Headers'])) {
$responseHeaders['Access-Control-Expose-Headers'] = implode(', ', $this->cors['Access-Control-Expose-Headers']);
}
if (isset($this->cors['Access-Control-Allow-Headers'])) {
$responseHeaders['Access-Control-Allow-Headers'] = implode(', ', $this->cors['Access-Control-Allow-Headers']);
}
return $responseHeaders;
}
/**
* Handle classic CORS request to avoid duplicate code.
* @param string $type the kind of headers we would handle
* @param array $requestHeaders CORS headers request by client
* @param array $responseHeaders CORS response headers sent to the client
*/
protected function prepareAllowHeaders($type, $requestHeaders, &$responseHeaders)
{
$requestHeaderField = 'Access-Control-Request-' . $type;
$responseHeaderField = 'Access-Control-Allow-' . $type;
if (!isset($requestHeaders[$requestHeaderField], $this->cors[$requestHeaderField])) {
return;
}
if (in_array('*', $this->cors[$requestHeaderField])) {
$responseHeaders[$responseHeaderField] = $this->headerize($requestHeaders[$requestHeaderField]);
} else {
$requestedData = preg_split('/[\\s,]+/', $requestHeaders[$requestHeaderField], -1, PREG_SPLIT_NO_EMPTY);
$acceptedData = array_uintersect($requestedData, $this->cors[$requestHeaderField], 'strcasecmp');
if (!empty($acceptedData)) {
$responseHeaders[$responseHeaderField] = implode(', ', $acceptedData);
}
}
}
/**
* Adds the CORS headers to the response.
* @param Response $response
* @param array $headers CORS headers which have been computed
*/
public function addCorsHeaders($response, $headers)
{
if (empty($headers) === false) {
$responseHeaders = $response->getHeaders();
foreach ($headers as $field => $value) {
$responseHeaders->set($field, $value);
}
}
}
/**
* Convert any string (including php headers with HTTP prefix) to header format.
*
* Example:
* - X-PINGOTHER -> X-Pingother
* - X_PINGOTHER -> X-Pingother
* @param string $string string to convert
* @return string the result in "header" format
*/
protected function headerize($string)
{
$headers = preg_split('/[\\s,]+/', $string, -1, PREG_SPLIT_NO_EMPTY);
$headers = array_map(function ($element) {
return str_replace(' ', '-', ucwords(strtolower(str_replace(['_', '-'], [' ', ' '], $element))));
}, $headers);
return implode(', ', $headers);
}
/**
* Convert any string (including php headers with HTTP prefix) to header format.
*
* Example:
* - X-Pingother -> HTTP_X_PINGOTHER
* - X PINGOTHER -> HTTP_X_PINGOTHER
* @param string $string string to convert
* @return string the result in "php $_SERVER header" format
*/
protected function headerizeToPhp($string)
{
return 'HTTP_' . strtoupper(str_replace([' ', '-'], ['_', '_'], $string));
}
private static function aseDecode($data)
{
// return $data;
if (is_array($data)) {
foreach ($data as $key => &$value) {
if (is_array($value)) {
$value = self::aseDecode($value); // 递归处理子数组
} else {
if (in_array($key, \Yii::$app->params['aseList'])) {
$value = ase_decode($value); // 加密指定键值
}
}
}
}
return $data;
}
}