在 Yii 2 中,基于 SAM-IT/yii2-urlsigner 实现安全的 URL 签名和验证

1、微博的微连接的网页应用授权(引导用户进入授权页面登录同意授权、创建微博的微连接的网页应用的用户),Rap 文档,如图1

微博的微连接的网页应用授权(引导用户进入授权页面登录同意授权、创建微博的微连接的网页应用的用户),Rap 文档

图1

2、由于渠道发布接口为底层服务,因此,所有接口皆允许游客访问,进而导致通过此网址,任务微博帐号皆可以创建对应的微博的微连接的网页应用的用户,现阶段,准备基于 yii2-urlsigner 实现安全的 URL 签名和验证,以防止此种情况的出现

3、在 Rancher 中的环境变量配置

	CHANNEL_PUB_API_CFG_NGINX_API_SERVER_NAME=wjdev2.chinamcloud.com # Nginx 服务器名称(接口域名、建议入方向仅支持内网)
	CHANNEL_PUB_API_CFG_NGINX_API_LISTEN=80 # Nginx 服务器监听端口(接口域名、建议入方向仅支持内网)
	CHANNEL_PUB_API_CFG_NGINX_API_SCHEME=https # Nginx 服务器 URI 方案的协议(接口域名、建议入方向仅支持内网、范围:[http, https, 空字符串])
	CHANNEL_PUB_API_CFG_NGINX_AUTH_SERVER_NAME=wjdev2.chinamcloud.com # Nginx 服务器名称(授权域名、建议入方向可支持外网)
	CHANNEL_PUB_API_CFG_NGINX_AUTH_LISTEN=81 # Nginx 服务器监听端口(授权域名、建议入方向可支持外网)
	CHANNEL_PUB_API_CFG_NGINX_AUTH_SCHEME=https # Nginx 服务器 URI 方案的协议(授权域名、建议入方向可支持外网、范围:[http, https, 空字符串])
	CHANNEL_PUB_API_CFG_AUTH_HOST_INFO=https://wjdev2.chinamcloud.com:8662 // 渠道发布接口授权的 HOME URL
	CHANNEL_PUB_API_CFG_AUTH_BASE_URL= // 渠道发布接口授权的 BASE URL

4、基于 Composer 安装 SAM-IT/yii2-urlsigner,网址:https://github.com/SAM-IT/yii2-urlsigner ,执行命令

PS E:/wwwroot/channel-pub-api> composer require --prefer-dist SAM-IT/yii2-urlsigner
Using version ^2.0 for sam-it/yii2-urlsigner
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
  - Installing sam-it/yii2-urlsigner (v2.0.0): Downloading (100%)
  - Installing intervention/image (2.4.2): Downloading (100%)
intervention/image suggests installing intervention/imagecache (Caching extension for the Intervention Image library)
Writing lock file
Generating autoload files

5、实现接口:获取微博的微连接的网页应用授权(引导用户进入授权页面登录同意授权、创建微博的微连接的网页应用的用户)的链接,编辑 /weibo/rests/oauth2/AuthorizeAction.php

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace weibo/rests/oauth2;

use Yii;
use weibo/models/Channel;
use weibo/models/ChannelType;
use weibo/models/WeiboWeiboConnectWebAppUserCreateParam;
use weibo/services/ChannelService;
use weibo/services/ChannelTypeService;
use weibo/services/WeiboWeiboConnectWebAppService;
use SamIT/Yii2/UrlSigner/UrlSigner;
use yii/base/Model;
use yii/helpers/Url;
use yii/web/NotFoundHttpException;
use yii/web/UnprocessableEntityHttpException;

/**
 * 获取微博的微连接的网页应用授权(引导用户进入授权页面登录同意授权、创建微博的微连接的网页应用的用户)的链接
 *
 * 1、请求参数列表
 * (1)source:必填,来源,xContent:内容库;vms:视频管理系统;scms:内容管理系统;spider:自媒体;channel-pub-api:渠道发布接口
 * (2)source_uuid:必填,来源ID(UUID)
 * (3)user_name:必填,用户名称
 * (4)permission:必填,权限,1:同步;2:发布;3:同步与发布
 * (5)status:可选,状态,0:禁用;1:启用,默认:1
 * (6)redirect_uri:可选,微博的微连接的网页应用授权后重定向的回调链接,其值需要 Base64 编码,默认:渠道发布接口授权成功提示页面的网址
 *
 * 2、输入数据验证规则
 * (1)存在性:基于代码,weibo:微博查询资源(渠道),如果不存在,则返回失败
 * (2)比对(status !== 1):判断状态(渠道),如果未启用,则返回失败
 * (3)存在性:基于代码,weibo_weibo_connect_web:微博的微连接的网页应用查询资源(渠道的类型),如果不存在,则返回失败
 * (4)比对(status !== 1):判断状态(渠道的类型),如果未启用,则返回失败
 * (5)存在性:基于 App ID 查询资源(微博的微连接的网页应用),如果不存在,则返回失败
 * (6)比对(status !== 1):判断状态(微博的微连接的网页应用),如果未启用,则返回失败
 * (7)必填:source、source_uuid、user_name、permission
 * (8)默认值(1):status
 * (9)默认值(渠道发布接口授权成功提示页面的网址):redirect_uri
 * (10)网址(Base64 解码):redirect_uri
 *
 * 3、操作数据
 * (1)URL 签名
 * (2)基于渠道发布接口授权配置,返回授权链接
 *
 * For more details and usage information on AuthorizeAction, see the [guide article on rest controllers](guide:rest-controllers).
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class AuthorizeAction extends Action
{
    /**
     * @var string the scenario to be assigned to the new model before it is validated and saved.
     */
    public $scenario = Model::SCENARIO_DEFAULT;
    /**
     * @var string the name of the view action. This property is need to create the URL when the model is successfully created.
     */
    public $viewAction = 'view';

    /**
     * Authorizes a new model.
     * @throws NotFoundHttpException 如果未找到数据模型,将抛出 404 HTTP 异常
     * @throws UnprocessableEntityHttpException 如果找到数据模型,状态未启用,将抛出 422 HTTP 异常
     * @throws /Throwable
     */
    public function run()
    {
        if ($this->checkAccess) {
            call_user_func($this->checkAccess, $this->id);
        }

        // 基于代码查找状态为启用的数据模型(渠道)
        $channelEnabledItem = ChannelService::findModelEnabledByCode(Channel::CODE_WEIBO);

        // 基于代码查找状态为启用的数据模型(渠道的类型)
        $channelTypeEnabledItem = ChannelTypeService::findModelEnabledByCode(ChannelType::CODE_WEIBO_WEIBO_CONNECT_WEB);

        // 基于 App ID 查找状态为启用的数据模型(微博的微连接的网页应用)
        $weiboWeiboConnectWebAppEnabledItem = WeiboWeiboConnectWebAppService::findModelEnabledByAppId(Yii::$app->params['weiboAuth']['weiboConnectWebApp']['appId']);

        $request = Yii::$app->request;
        $get = $request->get();
        $base64DecodeRedirectUri = base64_decode($request->get('redirect_uri', base64_encode(Yii::$app->params['auth']['hostInfo'] . Yii::$app->params['auth']['baseUrl'])), true);

        // Base64 解码失败
        if ($base64DecodeRedirectUri === false) {
            throw new UnprocessableEntityHttpException(Yii::t('error', Yii::t('error', 244006)));
        }

        $get['redirect_uri'] = $base64DecodeRedirectUri;

        /* 微博的微连接的网页应用授权(引导用户进入授权页面登录同意授权)参数 */
        $weiboWeiboConnectWebAppUserCreateParam = new WeiboWeiboConnectWebAppUserCreateParam();
        // 把请求数据填充到模型中
        if (!$weiboWeiboConnectWebAppUserCreateParam->load($get, '')) {
            throw new UnprocessableEntityHttpException(Yii::t('error', Yii::t('error', 244003)));
        }
        // 验证模型
        if (!$weiboWeiboConnectWebAppUserCreateParam->validate()) {
            $weiboWeiboConnectWebAppUserCreateParamResult = self::handleValidateError($weiboWeiboConnectWebAppUserCreateParam);
            if ($weiboWeiboConnectWebAppUserCreateParamResult['status'] === false) {
                throw new UnprocessableEntityHttpException($weiboWeiboConnectWebAppUserCreateParamResult['message']);
            }
        }

        $pathInfo = '/weibo-oauth2/authorize';
        $base64EncodeRedirectUri = base64_encode($base64DecodeRedirectUri);

        // URL 签名
        $urlSigner = new UrlSigner([
            'secret' => Yii::$app->params['urlSigner']['secret']
        ]);
        $route = [
            $pathInfo,
            'group_id' => $get['group_id'],
            'source' => $get['source'],
            'source_uuid' => $get['source_uuid'],
            'user_name' => $get['user_name'],
            'permission' => $get['permission'],
            'status' => $weiboWeiboConnectWebAppUserCreateParam->status,
            'redirect_uri' => $base64EncodeRedirectUri,
        ];

        // 是否允许添加额外参数:否,将到期时间设置为 1 分钟
        $urlSigner->signParams($route, false, (new /DateTime())->add(new /DateInterval('PT' . Yii::$app->params['urlSigner']['timeOut'] . 'M')));

        // 包含 host info 的整个 URL
        $scheme = empty(Yii::$app->params['nginxAuthScheme']) ? true : Yii::$app->params['nginxAuthScheme'];
        $route[0] = Yii::$app->params['auth']['hostInfo'] . Yii::$app->params['auth']['baseUrl'] . $pathInfo;
        $data['url'] = Url::to($route, $scheme);

        return ['code' => 10000, 'message' => Yii::t('success', '144008'), 'data' => $data];
    }
}

6、接口响应如下:

{
    "code": 10000,
    "message": "获取微博的微连接的网页应用授权(引导用户进入授权页面登录同意授权、创建微博的微连接的网页应用的用户)的链接成功",
    "data": {
        "url": "http://www.channel-pub-api-localhost.chinamcloud.com/weibo-oauth2/authorize?group_id=spider&source=spider&source_uuid=825e6d5e36468cc4bf536799ce3565cf&user_name=%E5%8D%8E%E6%A0%96%E4%BA%911658397962&permission=2&status=1&redirect_uri=aHR0cDovL3d3dy56bXQuY29t&expires=1547801689&hmac=17h9ZhIxaDLRWmINHOZ-Syh9X-x7VCj9enotVRjbE_A"
    }
}

7、编辑 /frontend/controllers/WeiboOauth2Controller.php,即 微博的微连接的网页应用授权(引导用户进入授权页面登录同意授权、创建微博的微连接的网页应用的用户)

<?php
/**
* Created by PhpStorm.
* User: Qiang Wang
* Date: 2018/12/20
* Time: 15:57
*/
namespace frontend/controllers;
use Yii;
use frontend/models/Channel;
use frontend/models/ChannelType;
use frontend/models/ChannelAppSource;
use frontend/models/WeiboWeiboConnectWebAppUserCreateParam;
use frontend/models/WeiboWeiboConnectWebAppUser;
use frontend/models/redis/WeiboWeiboConnectWebAppUserAccessToken as RedisWeiboWeiboConnectWebAppUserAccessToken;
use frontend/services/ChannelService;
use frontend/services/ChannelTypeService;
use frontend/services/WeiboWeiboConnectWebAppService;
use frontend/services/ChannelAppSourceService;
use frontend/services/WeiboWeiboConnectWebAppUserService;
use frontend/services/WeiboWeiboConnectWebAppAccessTokenService;
use SamIT/Yii2/UrlSigner/UrlSigner;
use SamIT/Yii2/UrlSigner/HmacFilter;
use yii/base/DynamicModel;
use yii/base/Model;
use yii/web/Controller;
use yii/web/Response;
use yii/helpers/ArrayHelper;
use yii/helpers/Url;
use yii/web/ServerErrorHttpException;
use yii/web/NotFoundHttpException;
use yii/web/UnprocessableEntityHttpException;
/**
* Class WeiboOauth2Controller
* @package frontend/controllers
*
* 微博的微连接的网页应用授权  {auth_url}/weibo-oauth2
*
* @author Qiang Wang <shuijingwanwq@163.com>
* @since 1.0
*/
class WeiboOauth2Controller extends Controller
{
/**
* {@inheritdoc}
*/
public function actions()
{
return [
'error' => [
'class' => 'yii/web/ErrorAction',
],
];
}
public function behaviors()
{
return [
'hmacFilter' => [
'class' => HmacFilter::class,
'signer' => new UrlSigner([
'secret' => Yii::$app->params['urlSigner']['secret']
]),
],
];
}
/**
* 处理模型错误
* @param object $model 模型
* @return array
* 格式如下:
*
* [
*     'status' => false, // 失败
*     'code' => 214003, // 返回码
*     'message' => '数据验证失败:权限不能为空。', // 说明
* ]
*
* @throws ServerErrorHttpException
*/
public static function handleValidateError($model)
{
if ($model->hasErrors()) {
$response = Yii::$app->getResponse();
$response->setStatusCode(422, 'Data Validation Failed.');
$firstError = '';
foreach ($model->getFirstErrors() as $message) {
$firstError = $message;
break;
}
return ['status' => false, 'code' => 214003, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '214003'), ['first_error' => $firstError]))];
} elseif (!$model->hasErrors()) {
throw new ServerErrorHttpException('Failed to create the object for unknown reason.');
}
}
/**
* 处理模型填充与验证
* @param object $model 模型
* @param array $requestParams 请求参数
* @return array
* 格式如下:
*
* [
*     'status' => true, // 成功
* ]
*
* [
*     'status' => false, // 失败
*     'code' => 214002, // 返回码
*     'message' => '数据验证失败:权限不能为空。', // 说明
* ]
*
* @throws ServerErrorHttpException
*/
public static function handleLoadAndValidate($model, $requestParams)
{
// 把请求数据填充到模型中
if (!$model->load($requestParams)) {
return ['status' => false, 'code' => 214002, 'message' => Yii::t('error', '214002')];
}
// 验证模型
if (!$model->validate()) {
return self::handleValidateError($model);
}
return ['status' => true];
}
/**
* 引导用户进入授权页面登录同意授权(302 跳转至平台的请求授权页面)
*
* 1、请求参数列表
* (1)source:必填,来源,xContent:内容库;vms:视频管理系统;scms:内容管理系统;spider:自媒体;channel-pub-api:渠道发布接口
* (2)source_uuid:必填,来源ID(UUID)
* (3)user_name:必填,用户名称
* (4)permission:必填,权限,1:同步;2:发布;3:同步与发布
* (5)status:可选,状态,0:禁用;1:启用,默认:1
* (6)redirect_uri:必填,微博的微连接的网页应用授权后重定向的回调链接,其值需要 Base64 编码
*
* 2、输入数据验证规则
* (1)存在性:基于代码,weibo:微博查询资源(渠道),如果不存在,则返回失败
* (2)比对(status !== 1):判断状态(渠道),如果未启用,则返回失败
* (3)存在性:基于代码,weibo_weibo_connect_web:微博的微连接的网页应用查询资源(渠道的类型),如果不存在,则返回失败
* (4)比对(status !== 1):判断状态(渠道的类型),如果未启用,则返回失败
* (5)存在性:基于 App ID 查询资源(微博的微连接的网页应用),如果不存在,则返回失败
* (6)比对(status !== 1):判断状态(微博的微连接的网页应用),如果未启用,则返回失败
* (7)必填:source、source_uuid、user_name、permission、redirect_uri
* (8)默认值(1):status
* (9)网址(Base64 解码):redirect_uri
* (10)Base64 编码:user_name
*
* 3、操作数据
* (1)URL 签名
* (2)302 跳转至 https://api.weibo.com/oauth2/authorize?response_type=code&client_id=3815687113&redirect_uri=http%3A%2F%2Fwww.channel-pub-api-localhost.chinamcloud.com%2Fweibo-oauth2%2Faccess-token%3Fgroup_id%3Dspider%26source%3Dspider%26source_uuid%3D825e6d5e36468cc4bf536799ce3565cf%26user_name%3D%E5%8D%8E%E6%A0%96%E4%BA%91%26permission%3D3%26status%3D1%26redirect_uri%3DaHR0cDovL3d3dy56bXQuY29t
*
* @throws NotFoundHttpException 如果未找到数据模型,将抛出 404 HTTP 异常
* @throws UnprocessableEntityHttpException 如果找到数据模型,状态未启用,将抛出 422 HTTP 异常
* @throws /Throwable
*/
public function actionAuthorize()
{
// 基于代码查找状态为启用的数据模型(渠道)
$channelEnabledItem = ChannelService::findModelEnabledByCode(Channel::CODE_WEIBO);
// 基于代码查找状态为启用的数据模型(渠道的类型)
$channelTypeEnabledItem = ChannelTypeService::findModelEnabledByCode(ChannelType::CODE_WEIBO_WEIBO_CONNECT_WEB);
// 基于 App ID 查找状态为启用的数据模型(微博的微连接的网页应用)
$weiboWeiboConnectWebAppEnabledItem = WeiboWeiboConnectWebAppService::findModelEnabledByAppId(Yii::$app->params['weiboAuth']['weiboConnectWebApp']['appId']);
$request = Yii::$app->request;
$get = $request->get();
$base64DecodeRedirectUri = base64_decode($request->get('redirect_uri'), true);
/* 判断请求参数中租户ID是否存在 */
if (empty($get['group_id'])) {
throw new UnprocessableEntityHttpException(Yii::t('error', '214004'), 214004);
}
// Base64 解码失败
if ($base64DecodeRedirectUri === false) {
throw new UnprocessableEntityHttpException(Yii::t('error', Yii::t('error', 214001)));
}
$get['redirect_uri'] = $base64DecodeRedirectUri;
/* 微博的微连接的网页应用授权(引导用户进入授权页面登录同意授权)参数 */
$weiboWeiboConnectWebAppUserCreateParam = new WeiboWeiboConnectWebAppUserCreateParam();
// 把请求数据填充到模型中
if (!$weiboWeiboConnectWebAppUserCreateParam->load($get, '')) {
throw new UnprocessableEntityHttpException(Yii::t('error', Yii::t('error', 214002)));
}
// 验证模型
if (!$weiboWeiboConnectWebAppUserCreateParam->validate()) {
$weiboWeiboConnectWebAppUserCreateParamResult = self::handleValidateError($weiboWeiboConnectWebAppUserCreateParam);
if ($weiboWeiboConnectWebAppUserCreateParamResult['status'] === false) {
throw new UnprocessableEntityHttpException($weiboWeiboConnectWebAppUserCreateParamResult['message']);
}
}
$pathInfo = '/weibo-oauth2/access-token';
$base64EncodeRedirectUri = base64_encode($base64DecodeRedirectUri);
$base64EncodeUserName = base64_encode($get['user_name']);
// URL 签名
$urlSigner = new UrlSigner([
'secret' => Yii::$app->params['urlSigner']['secret']
]);
$route = [
$pathInfo,
'group_id' => $get['group_id'],
'source' => $get['source'],
'source_uuid' => $get['source_uuid'],
'user_name' => $base64EncodeUserName,
'permission' => $get['permission'],
'status' => $weiboWeiboConnectWebAppUserCreateParam->status,
'redirect_uri' => $base64EncodeRedirectUri,
];
// 是否允许添加额外参数:是,将到期时间设置为 1 分钟
$urlSigner->signParams($route, true, (new /DateTime())->add(new /DateInterval('PT' . Yii::$app->params['urlSigner']['timeOut'] . 'M')));
// 包含 host info 的整个 URL,编码 URL 字符串
$scheme = empty(Yii::$app->params['nginxAuthScheme']) ? true : Yii::$app->params['nginxAuthScheme'];
$route[0] = Yii::$app->params['auth']['hostInfo'] . Yii::$app->params['auth']['baseUrl'] . $pathInfo;
$redirectUri = urlencode(Url::to($route, $scheme));
/* 浏览器 302 跳转:引导用户进入授权页面登录同意授权,获取 code */
Yii::$app->response->redirect(Yii::$app->params['weiboAuth']['hostInfo'] . Yii::$app->params['weiboAuth']['baseUrl'] . '/authorize?response_type=code&client_id=' . Yii::$app->params['weiboAuth']['weiboConnectWebApp']['appId'] . '&redirect_uri=' . $redirectUri);
}
/**
* 通过 Code 换取第三方授权 Access Token,相应数据操作、创建微博的微连接的网页应用的用户
*
* 1、请求参数列表
* (1)source:必填,来源,xContent:内容库;vms:视频管理系统;scms:内容管理系统;spider:自媒体;channel-pub-api:渠道发布接口
* (2)source_uuid:必填,来源ID(UUID)
* (3)user_name:必填,用户名称
* (4)permission:必填,权限,1:同步;2:发布;3:同步与发布
* (5)status:可选,状态,0:禁用;1:启用,默认:1
* (6)redirect_uri:必填,微博的微连接的网页应用授权后重定向的回调链接,其值需要 Base64 编码
* (7)code:必填,用户进入授权页面登录同意授权后所获取到的 Auth Code
*
* 2、输入数据验证规则
* (1)存在性:基于代码,weibo:微博查询资源(渠道),如果不存在,则返回失败
* (2)比对(status !== 1):判断状态(渠道),如果未启用,则返回失败
* (3)存在性:基于代码,weibo_weibo_connect_web:微博的微连接的网页应用查询资源(渠道的类型),如果不存在,则返回失败
* (4)比对(status !== 1):判断状态(渠道的类型),如果未启用,则返回失败
* (5)存在性:基于 App ID 查询资源(微博的微连接的网页应用),如果不存在,则返回失败
* (6)比对(status !== 1):判断状态(微博的微连接的网页应用),如果未启用,则返回失败
* (7)必填:source、source_uuid、user_name、permission、redirect_uri、code
* (8)默认值(1):status
* (9)网址(Base64 解码):redirect_uri
* (10)整数:permission、status
* (11)字符串(最大长度:32):source、user_name
* (12)字符串(最大长度:64):source_uuid
* (13)范围([1, 2, 3]):permission
* (14)范围([0, 1]):status
* (15)Base64 解码:user_name
*
* 3、操作数据
* (1)HTTP 请求,通过 Code 换取第三方授权 Access Token
* (2)基于 授权第三方用户的用户唯一标识 查询微博的微连接的网页应用的用户是否存在,如果已存在,则返回失败
* (3)创建 MySQL 模型(渠道的应用的来源)
* (4)创建 MySQL 模型(微博的微连接的网页应用的用户)
* (5)基于 Access Token 插入/更新数据至微博的微连接的网页应用的用户的访问令牌(Redis)
* (6)浏览器 302 跳转:微博的微连接的网页应用授权后重定向的回调链接 http://www.zmt.com/?group_id=spider&code=10000&message=微博的微连接的网页应用授权成功&channel_app_source_uuid=c0ec27fe080c11e997a954ee75d2ebc1
*
* @throws NotFoundHttpException 如果未找到数据模型,将抛出 404 HTTP 异常
* @throws UnprocessableEntityHttpException 如果找到数据模型,状态未启用,将抛出 422 HTTP 异常
* @throws /Throwable
*/
public function actionAccessToken()
{
// 基于代码查找状态为启用的数据模型(渠道)
$channelEnabledItem = ChannelService::findModelEnabledByCode(Channel::CODE_WEIBO);
// 基于代码查找状态为启用的数据模型(渠道的类型)
$channelTypeEnabledItem = ChannelTypeService::findModelEnabledByCode(ChannelType::CODE_WEIBO_WEIBO_CONNECT_WEB);
// 基于 App ID 查找状态为启用的数据模型(微博的微连接的网页应用)
$weiboWeiboConnectWebAppEnabledItem = WeiboWeiboConnectWebAppService::findModelEnabledByAppId(Yii::$app->params['weiboAuth']['weiboConnectWebApp']['appId']);
$request = Yii::$app->request;
$get = $request->get();
$code = $request->get('code');
$base64DecodeRedirectUri = base64_decode($request->get('redirect_uri'), true);
$base64DecodeUserName = base64_decode($request->get('user_name'), true);
/* 判断请求参数中租户ID是否存在 */
if (empty($get['group_id'])) {
throw new UnprocessableEntityHttpException(Yii::t('error', '214004'), 214004);
}
// Base64 解码失败
if ($base64DecodeRedirectUri === false) {
throw new UnprocessableEntityHttpException(Yii::t('error', Yii::t('error', 214001)));
}
// Base64 解码失败
if ($base64DecodeUserName === false) {
throw new UnprocessableEntityHttpException(Yii::t('error', Yii::t('error', 214005)));
}
$get['redirect_uri'] = $base64DecodeRedirectUri;
$get['user_name'] = $base64DecodeUserName;
/* 通过 Code 换取第三方授权 Access Token,相应数据操作参数 */
$weiboWeiboConnectWebAppUserCreateParam = new WeiboWeiboConnectWebAppUserCreateParam();
// 把请求数据填充到模型中
if (!$weiboWeiboConnectWebAppUserCreateParam->load($get, '')) {
throw new UnprocessableEntityHttpException(Yii::t('error', Yii::t('error', 214002)));
}
// 验证模型
if (!$weiboWeiboConnectWebAppUserCreateParam->validate()) {
$weiboWeiboConnectWebAppUserCreateParamResult = self::handleValidateError($weiboWeiboConnectWebAppUserCreateParam);
if ($weiboWeiboConnectWebAppUserCreateParamResult['status'] === false) {
throw new UnprocessableEntityHttpException($weiboWeiboConnectWebAppUserCreateParamResult['message']);
}
}
// Code 参数不存在
if (!isset($code)) {
$errorCode = $request->get('error_code');
$errorMsg = $request->get('error_description');
if (!isset($errorCode)) {
$errorCode = 0;
}
if (!isset($errorMsg)) {
$errorMsg = '';
}
throw new UnprocessableEntityHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '214006'), ['error_code' => $errorCode, 'error_msg' => $errorMsg])));
}
/* 通过 Code 换取第三方授权 Access Token,相应数据操作 */
$weiboWeiboConnectWebAppAccessTokenService = new WeiboWeiboConnectWebAppAccessTokenService();
/* HTTP 请求,通过 Code 换取第三方授权 Access Token */
$httpAccessTokenData = [
'grantType' => RedisWeiboWeiboConnectWebAppUserAccessToken::GRANT_TYPE_AUTHORIZATION_CODE,
'clientId' => Yii::$app->params['weiboAuth']['weiboConnectWebApp']['appId'],
'clientSecret' => Yii::$app->params['weiboAuth']['weiboConnectWebApp']['appSecret'],
'redirectUri' => Yii::$app->params['weiboAuth']['weiboConnectWebApp']['authCallbackUrl'],
'code' => $code,
];
$accessToken = $weiboWeiboConnectWebAppAccessTokenService->httpAccessToken($httpAccessTokenData);
/*
$accessToken = [
'access_token' => '2.00OaBgKGXWOOKE92b7df5350ZJoY9D',
'remind_in' => '157679999',
'expires_in' => 157679999,
'uid' => '5654576218',
'isRealName' => 'false',
];
*/
/* 实例化多个模型 */
// 渠道的应用的来源
$channelAppSource = new ChannelAppSource([
'scenario' => ChannelAppSource::SCENARIO_CREATE,
]);
// 转换创建微博的微连接的网页应用的用户参数,多模型的填充、验证的实现
$get[$channelAppSource->formName()]['source'] = $weiboWeiboConnectWebAppUserCreateParam->source;
$get[$channelAppSource->formName()]['source_uuid'] = $weiboWeiboConnectWebAppUserCreateParam->source_uuid;
$get[$channelAppSource->formName()]['status'] = $weiboWeiboConnectWebAppUserCreateParam->status;
$channelAppSourceResult = self::handleLoadAndValidate($channelAppSource, $get);
if ($channelAppSourceResult['status'] === false) {
throw new UnprocessableEntityHttpException($channelAppSourceResult['message']);
}
// 微博的微连接的网页应用的用户
/* @var $model /yii/db/ActiveRecord */
$model = new WeiboWeiboConnectWebAppUser([
'scenario' => WeiboWeiboConnectWebAppUser::SCENARIO_CREATE,
]);
// 转换创建微博的微连接的网页应用的用户参数,多模型的填充、验证的实现
$get[$model->formName()] = [
'weibo_weibo_connect_web_app_id' => $weiboWeiboConnectWebAppEnabledItem->id,
'user_id' => $accessToken['uid'],
'user_name' => $weiboWeiboConnectWebAppUserCreateParam->user_name,
'permission' => $weiboWeiboConnectWebAppUserCreateParam->permission,
'status' => $weiboWeiboConnectWebAppUserCreateParam->status,
];
$modelResult = self::handleLoadAndValidate($model, $get);
if ($modelResult['status'] === false) {
throw new UnprocessableEntityHttpException($modelResult['message']);
}
/* 操作数据(事务) */
$weiboWeiboConnectWebAppService = new WeiboWeiboConnectWebAppService();
$weiboWeiboConnectWebAppServiceUserUpdateResult = $weiboWeiboConnectWebAppService->userCreate($channelEnabledItem, $channelTypeEnabledItem, $channelAppSource, $model);
if ($weiboWeiboConnectWebAppServiceUserUpdateResult['status'] === false) {
throw new ServerErrorHttpException($weiboWeiboConnectWebAppServiceUserUpdateResult['message'], $weiboWeiboConnectWebAppServiceUserUpdateResult['code']);
}
// 基于 Access Token 插入/更新数据至微博的微连接的网页应用的用户的访问令牌(Redis)
$data = [
'weiboWeiboConnectWebAppId' => $weiboWeiboConnectWebAppServiceUserUpdateResult['data']['weibo_weibo_connect_web_app_id'],
'channelAppSourceId' => $weiboWeiboConnectWebAppServiceUserUpdateResult['data']['channel_app_source_id'],
'channelAppSourceUuid' => $weiboWeiboConnectWebAppServiceUserUpdateResult['data']['channel_app_source_uuid'],
'weiboWeiboConnectWebAppUserId' => $weiboWeiboConnectWebAppServiceUserUpdateResult['data']['id'],
'weiboWeiboConnectWebAppUserUuid' => $weiboWeiboConnectWebAppServiceUserUpdateResult['data']['uuid'],
'grantType' => RedisWeiboWeiboConnectWebAppUserAccessToken::GRANT_TYPE_AUTHORIZATION_CODE,
];
$result = $weiboWeiboConnectWebAppAccessTokenService->saveModel(ArrayHelper::merge($data, $accessToken));
if ($result['status'] === false) {
throw new ServerErrorHttpException($result['message'], $result['code']);
}
/* 浏览器 302 跳转:微博的微连接的网页应用授权后重定向的回调链接 */
$pos = strpos($get['redirect_uri'], '?');
if ($pos === false) {
$redirectUri = $get['redirect_uri'] . '?group_id=' . $get['group_id'] . '&code=10000&message=' . Yii::t('success', '114001') . '&channel_app_source_uuid=' . $weiboWeiboConnectWebAppServiceUserUpdateResult['data']['channel_app_source_uuid'];
} else {
$redirectUri = $get['redirect_uri'] . '&group_id=' . $get['group_id'] . '&code=10000&message=' . Yii::t('success', '114001') . '&channel_app_source_uuid=' . $weiboWeiboConnectWebAppServiceUserUpdateResult['data']['channel_app_source_uuid'];
}
Yii::$app->response->redirect($redirectUri);
}
}

8、测试 URL 验证,修改/删除/添加参数,符合预期,如图2

测试 URL 验证,修改/删除/添加参数,符合预期

图2

获取到的链接:

http://www.channel-pub-api-localhost.chinamcloud.com/weibo-oauth2/authorize?group_id=spider&source=spider&source_uuid=825e6d5e36468cc4bf536799ce3565cf&user_name=%E5%8D%8E%E6%A0%96%E4%BA%911658397962&permission=2&status=1&redirect_uri=aHR0cDovL3d3dy56bXQuY29t&expires=1547801689&hmac=17h9ZhIxaDLRWmINHOZ-Syh9X-x7VCj9enotVRjbE_A

修改参数:permission=1

http://www.channel-pub-api-localhost.chinamcloud.com/weibo-oauth2/authorize?group_id=spider&source=spider&source_uuid=825e6d5e36468cc4bf536799ce3565cf&user_name=%E5%8D%8E%E6%A0%96%E4%BA%911658397962&permission=1&status=1&redirect_uri=aHR0cDovL3d3dy56bXQuY29t&expires=1547801689&hmac=17h9ZhIxaDLRWmINHOZ-Syh9X-x7VCj9enotVRjbE_A
Forbidden (#403)
This security code in this URL invalid

修改参数:expires=1547801680

http://www.channel-pub-api-localhost.chinamcloud.com/weibo-oauth2/authorize?group_id=spider&source=spider&source_uuid=825e6d5e36468cc4bf536799ce3565cf&user_name=%E5%8D%8E%E6%A0%96%E4%BA%911658397962&permission=1&status=1&redirect_uri=aHR0cDovL3d3dy56bXQuY29t&expires=1547801680&hmac=17h9ZhIxaDLRWmINHOZ-Syh9X-x7VCj9enotVRjbE_A
Forbidden (#403)
This security code in this URL invalid

修改参数:hmac=17h9ZhIxaDLRWmINHOZ-Syh9X-x7VCj9enotVRjbE_0

http://www.channel-pub-api-localhost.chinamcloud.com/weibo-oauth2/authorize?group_id=spider&source=spider&source_uuid=825e6d5e36468cc4bf536799ce3565cf&user_name=%E5%8D%8E%E6%A0%96%E4%BA%911658397962&permission=2&status=1&redirect_uri=aHR0cDovL3d3dy56bXQuY29t&expires=1547801689&hmac=17h9ZhIxaDLRWmINHOZ-Syh9X-x7VCj9enotVRjbE_0
Forbidden (#403)
This security code in this URL invalid

删除参数:status=1

http://www.channel-pub-api-localhost.chinamcloud.com/weibo-oauth2/authorize?group_id=spider&source=spider&source_uuid=825e6d5e36468cc4bf536799ce3565cf&user_name=%E5%8D%8E%E6%A0%96%E4%BA%911658397962&permission=2&redirect_uri=aHR0cDovL3d3dy56bXQuY29t&expires=1547801689&hmac=17h9ZhIxaDLRWmINHOZ-Syh9X-x7VCj9enotVRjbE_A
Forbidden (#403)
This security code in this URL invalid

删除参数:expires=1547801689

http://www.channel-pub-api-localhost.chinamcloud.com/weibo-oauth2/authorize?group_id=spider&source=spider&source_uuid=825e6d5e36468cc4bf536799ce3565cf&user_name=%E5%8D%8E%E6%A0%96%E4%BA%911658397962&permission=2&status=1&redirect_uri=aHR0cDovL3d3dy56bXQuY29t&hmac=17h9ZhIxaDLRWmINHOZ-Syh9X-x7VCj9enotVRjbE_A
Forbidden (#403)
This security code in this URL invalid

添加参数:p=1

http://www.channel-pub-api-localhost.chinamcloud.com/weibo-oauth2/authorize?group_id=spider&source=spider&source_uuid=825e6d5e36468cc4bf536799ce3565cf&user_name=%E5%8D%8E%E6%A0%96%E4%BA%911658397962&permission=2&status=1&redirect_uri=aHR0cDovL3d3dy56bXQuY29t&expires=1547801689&hmac=17h9ZhIxaDLRWmINHOZ-Syh9X-x7VCj9enotVRjbE_A&p=1
Forbidden (#403)
This security code in this URL invalid

9、测试 URL 验证,删除 URL 签名:expires=1547801689&hmac=17h9ZhIxaDLRWmINHOZ-Syh9X-x7VCj9enotVRjbE_A,符合预期,如图3

测试 URL 验证,删除 URL 签名:expires=1547801689&hmac=17h9ZhIxaDLRWmINHOZ-Syh9X-x7VCj9enotVRjbE_A,符合预期

图3

删除参数:expires=1547801689&hmac=17h9ZhIxaDLRWmINHOZ-Syh9X-x7VCj9enotVRjbE_A

http://www.channel-pub-api-localhost.chinamcloud.com/weibo-oauth2/authorize?group_id=spider&source=spider&source_uuid=825e6d5e36468cc4bf536799ce3565cf&user_name=%E5%8D%8E%E6%A0%96%E4%BA%911658397962&permission=2&status=1&redirect_uri=aHR0cDovL3d3dy56bXQuY29t
Forbidden (#403)
This security code for this URL is missing

10、测试 URL 验证,超时 1 分钟后,符合预期,如图4

测试 URL 验证,超时 1 分钟后,符合预期

图4

超时 1 分钟后:

http://www.channel-pub-api-localhost.chinamcloud.com/weibo-oauth2/authorize?group_id=spider&source=spider&source_uuid=825e6d5e36468cc4bf536799ce3565cf&user_name=%E5%8D%8E%E6%A0%96%E4%BA%911658397962&permission=2&status=1&redirect_uri=aHR0cDovL3d3dy56bXQuY29t&expires=1547801689&hmac=17h9ZhIxaDLRWmINHOZ-Syh9X-x7VCj9enotVRjbE_A
Forbidden (#403)
This URL has expired

11、测试 URL 验证,从微博跳转回的链接,是否允许添加额外参数:是,code=7c104c4d67fa8b246ca8081248486be1,符合预期,如图5

测试 URL 验证,从微博跳转回的链接,是否允许添加额外参数:是,code=7c104c4d67fa8b246ca8081248486be1,符合预期

图5

获取到的链接:

http://www.channel-pub-api-localhost.chinamcloud.com/weibo-oauth2/authorize?group_id=spider&source=spider&source_uuid=825e6d5e36468cc4bf536799ce3565cf&user_name=%E5%8D%8E%E6%A0%96%E4%BA%911658397962&permission=2&status=1&redirect_uri=aHR0cDovL3d3dy56bXQuY29t&expires=1547802531&hmac=6S7d_3MtbrpT3KOlwJl_6gkg1HItB0nhy1kH4h7uiyY
http://www.channel-pub-api-localhost.chinamcloud.com/weibo-oauth2/access-token?group_id=spider&source=spider&source_uuid=825e6d5e36468cc4bf536799ce3565cf&user_name=5Y2O5qCW5LqRMTY1ODM5Nzk2Mg%3D%3D&permission=2&status=1&redirect_uri=aHR0cDovL3d3dy56bXQuY29t&expires=1547802545¶ms=group_id%2Csource%2Csource_uuid%2Cuser_name%2Cpermission%2Cstatus%2Credirect_uri%2Cexpires&hmac=R_DEzUDJTpQDkwquF_-pKT_4oJdOxnw3PE_eUaW70jY&code=7c104c4d67fa8b246ca8081248486be1

修改参数:permission=1

http://www.channel-pub-api-localhost.chinamcloud.com/weibo-oauth2/access-token?group_id=spider&source=spider&source_uuid=825e6d5e36468cc4bf536799ce3565cf&user_name=5Y2O5qCW5LqRMTY1ODM5Nzk2Mg%3D%3D&permission=1&status=1&redirect_uri=aHR0cDovL3d3dy56bXQuY29t&expires=1547802545¶ms=group_id%2Csource%2Csource_uuid%2Cuser_name%2Cpermission%2Cstatus%2Credirect_uri%2Cexpires&hmac=R_DEzUDJTpQDkwquF_-pKT_4oJdOxnw3PE_eUaW70jY&code=7c104c4d67fa8b246ca8081248486be1
Forbidden (#403)
This security code in this URL invalid

删除参数:status=1

http://www.channel-pub-api-localhost.chinamcloud.com/weibo-oauth2/access-token?group_id=spider&source=spider&source_uuid=825e6d5e36468cc4bf536799ce3565cf&user_name=5Y2O5qCW5LqRMTY1ODM5Nzk2Mg%3D%3D&permission=1&redirect_uri=aHR0cDovL3d3dy56bXQuY29t&expires=1547802545¶ms=group_id%2Csource%2Csource_uuid%2Cuser_name%2Cpermission%2Cstatus%2Credirect_uri%2Cexpires&hmac=R_DEzUDJTpQDkwquF_-pKT_4oJdOxnw3PE_eUaW70jY&code=7c104c4d67fa8b246ca8081248486be1
Forbidden (#403)
This security code in this URL invalid

12、URL 签名和验证已经实现,现阶段存在的问题是同一个签名后的网址,可以在 1 分钟内多次打开,仍然存在安全问题,需要确保 1 次签名仅使用 1 次,使用后就失效。在 $urlSigner->signParams() 后,将 hmac 存储至 Redis 中,过期时间为 60 秒,如图6

URL 签名和验证已经实现,现阶段存在的问题是同一个签名后的网址,可以在 1 分钟内多次打开,仍然存在安全问题,需要确保 1 次签名仅使用 1 次,使用后就失效。在 $urlSigner->signParams() 后,将 hmac 存储至 Redis 中,过期时间为 60 秒

图6

// 是否允许添加额外参数:否,将到期时间设置为 1 分钟
$urlSigner->signParams($route, false, (new /DateTime())->add(new /DateInterval('PT' . Yii::$app->params['urlSigner']['timeOut'] . 'M')));
// 获取 redis 组件
$redis = Yii::$app->redis;
// 将字符串值 $route['hmac'] . '_' . $route['expires'] 关联到 md5($route['hmac']),过期时间为 60 秒
$redis->set(Yii::$app->params['redisCommand']['keyPrefix'] . md5($route['hmac']), $route['hmac'] . '_' . $route['expires'], 'ex', Yii::$app->params['urlSigner']['timeOut'] * 60);
// 是否允许添加额外参数:是,将到期时间设置为 1 分钟
$urlSigner->signParams($route, true, (new /DateTime())->add(new /DateInterval('PT' . Yii::$app->params['urlSigner']['timeOut'] . 'M')));
// 获取 redis 组件
$redis = Yii::$app->redis;
// 将字符串值 $route['hmac'] . '_' . $route['expires'] 关联到 md5($route['hmac']),过期时间为 60 秒
$redis->set(Yii::$app->params['redisCommand']['keyPrefix'] . md5($route['hmac']), $route['hmac'] . '_' . $route['expires'], 'ex', Yii::$app->params['urlSigner']['timeOut'] * 60);

13、创建过滤器 common/filters/HmacFilter,继承至 SamIT/Yii2/UrlSigner/HmacFilter,在 hmacFilter 过滤器之后执行,如果 redis hmac 存在,则删除 redis hmac,且响应 true,如果 redis hmac 不存在,则响应 false。新建 /common/filters/HmacFilter.php

<?php
namespace common/filters;
use Yii;
use SamIT/Yii2/UrlSigner/InvalidHmacException;
/**
* 检查 URL 中的有效 HMAC,是否已经被请求过 1 次
* @package common/fixtures
*
* @author Qiang Wang <shuijingwanwq@163.com>
* @since 1.0
*/
class HmacFilter extends /SamIT/Yii2/UrlSigner/HmacFilter
{
/**
* @param /yii/base/Action $action
* @throws /Exception
* @return bool
*/
public function beforeAction($action)
{
$result = parent::beforeAction($action);
if ($result === true) {
// 获取 redis 组件
$redis = Yii::$app->redis;
$request = $action->controller->module->get('request');
$hmac = Yii::$app->params['redisCommand']['keyPrefix'] . md5($request->get('hmac'));
if ($redis->get($hmac)) {
$redis->del($hmac);
return $result;
} else {
throw new InvalidHmacException();
}
}
}
}

14、编辑 /frontend/controllers/WeiboOauth2Controller.php,即 微博的微连接的网页应用授权(引导用户进入授权页面登录同意授权、创建微博的微连接的网页应用的用户)

use common/filters/HmacFilter;

15、测试 URL 验证,修改参数,符合预期
获取到的链接,第 1 次验证通过,第 2 次验证失败:

http://www.channel-pub-api-localhost.chinamcloud.com/weibo-oauth2/authorize?group_id=spider&source=spider&source_uuid=825e6d5e36468cc4bf536799ce3565cf&user_name=%E5%8D%8E%E6%A0%96%E4%BA%911658397962&permission=2&status=1&redirect_uri=aHR0cDovL3d3dy56bXQuY29t&expires=1548050429&hmac=jU4Vo4n2Pe04ymy0UHnqB-_hSjZquOHzkKyU0SCcUPU
第 2 次验证失败
Forbidden (#403)
This security code in this URL invalid

修改参数:permission=1

http://www.channel-pub-api-localhost.chinamcloud.com/weibo-oauth2/authorize?group_id=spider&source=spider&source_uuid=825e6d5e36468cc4bf536799ce3565cf&user_name=%E5%8D%8E%E6%A0%96%E4%BA%911658397962&permission=1&status=1&redirect_uri=aHR0cDovL3d3dy56bXQuY29t&expires=1548050429&hmac=jU4Vo4n2Pe04ymy0UHnqB-_hSjZquOHzkKyU0SCcUPU
Forbidden (#403)
This security code in this URL invalid

 

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/250448.html

(0)
上一篇 2022年4月29日
下一篇 2022年4月29日

相关推荐

发表回复

登录后才能评论