在 Yii 2 中,基于 SAM-IT/yii2-urlsigner 实现安全的 URL 签名和验证
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
获取到的链接:
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
删除参数: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
超时 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
获取到的链接:
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
// 是否允许添加额外参数:否,将到期时间设置为 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
近期评论