没有不值得去解决的问题,也没有不值得去学习的技术!

Secure URL Signing and Verification in Yii 2 With SAM-IT/yii2-urlsigner

Test URL verification by removing the URL signature: expires=1547801689&hmac=17h9ZhIxaDLRWmINHOZ-Syh9X-x7VCj9enotVRjbE_A; the results match expectations

1. Weibo’s web application authorization for micro-connections (guiding users to the authorization page to log in and consent, creating a user for the Weibo micro-connection web application), Rap documentation, as shown in Figure 1

Weibo's web application authorization for micro-connections (guiding users to the authorization page to log in and consent, creating a user for the Weibo micro-connection web application), Rap documentation
Figure 1

2. Because the channel publishing interface is a underlying service, all interfaces allow guest access. Consequently, through this URL, any Weibo account can create a corresponding user for the Weibo micro-connection web application. At this stage, I am preparing to implement secure URL signing and verification based on yii2-urlsigner to prevent this situation from occurring.

3. Environment variable configuration in Rancher

Plaintext
	CHANNEL_PUB_API_CFG_NGINX_API_SERVER_NAME=wjdev2.chinamcloud.com # Nginx server name (API domain, recommended inbound direction supports intranet only)
	CHANNEL_PUB_API_CFG_NGINX_API_LISTEN=80 # Nginx server listening port (API domain, recommended inbound direction supports intranet only)
	CHANNEL_PUB_API_CFG_NGINX_API_SCHEME=https # Nginx server URI scheme protocol (API domain, recommended inbound direction supports intranet only, range: [http, https, empty string])
	CHANNEL_PUB_API_CFG_NGINX_AUTH_SERVER_NAME=wjdev2.chinamcloud.com # Nginx server name (authorization domain, recommended inbound direction can support external network)
	CHANNEL_PUB_API_CFG_NGINX_AUTH_LISTEN=81 # Nginx server listening port (authorization domain, recommended inbound direction can support external network)
	CHANNEL_PUB_API_CFG_NGINX_AUTH_SCHEME=https # Nginx server URI scheme protocol (authorization domain, recommended inbound direction can support external network, range: [http, https, empty string])
	CHANNEL_PUB_API_CFG_AUTH_HOST_INFO=https://wjdev2.chinamcloud.com:8662 // HOME URL authorized by the channel publishing API
	CHANNEL_PUB_API_CFG_AUTH_BASE_URL= // BASE URL authorized by the channel publishing API

4. Install SAM-IT/yii2-urlsigner via Composer, URL: https://github.com/SAM-IT/yii2-urlsigner , execute the command

Plaintext
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. Implement the interface: get the link for Weibo’s web application authorization for micro-connections (guiding users to the authorization page to log in and consent, creating a user for the Weibo micro-connection web application), edit \weibo\rests\oauth2\AuthorizeAction.php

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. The interface response is as follows:

JSON
{
    "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. Edit \frontend\controllers\WeiboOauth2Controller.php, which is the Weibo micro-connection web application authorization (guiding users to the authorization page to log in and consent, creating a user for the Weibo micro-connection web application)

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. Test URL verification by modifying/deleting/adding parameters; the results match expectations, as shown in Figure 2

Test URL verification by modifying/deleting/adding parameters; the results match expectations
Figure 2

The obtained link:

Plaintext
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

Modify parameter: permission=1

Plaintext
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

Modify parameter: expires=1547801680

Plaintext
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

Modify parameter: hmac=17h9ZhIxaDLRWmINHOZ-Syh9X-x7VCj9enotVRjbE_0

Plaintext
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

Delete parameter: status=1

Plaintext
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

Delete parameter: expires=1547801689

Plaintext
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

Add parameter: p=1

Plaintext
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. Test URL verification by removing the URL signature: expires=1547801689&hmac=17h9ZhIxaDLRWmINHOZ-Syh9X-x7VCj9enotVRjbE_A; the results match expectations, as shown in Figure 3

Test URL verification by removing the URL signature: expires=1547801689&hmac=17h9ZhIxaDLRWmINHOZ-Syh9X-x7VCj9enotVRjbE_A; the results match expectations
Figure 3

Delete parameter: expires=1547801689&hmac=17h9ZhIxaDLRWmINHOZ-Syh9X-x7VCj9enotVRjbE_A

Plaintext
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. Test URL verification after a 1-minute timeout; the results match expectations, as shown in Figure 4

Test URL verification after a 1-minute timeout; the results match expectations
Figure 4

After a 1-minute timeout:

Plaintext
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. Test URL verification on the link redirected back from Weibo to check whether adding extra parameters is allowed: Yes, code=7c104c4d67fa8b246ca8081248486be1; the results match expectations, as shown in Figure 5

Test URL verification on the link redirected back from Weibo to check whether adding extra parameters is allowed: Yes, code=7c104c4d67fa8b246ca8081248486be1; the results match expectations
Figure 5

The obtained link:

Plaintext
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

Modify parameter: permission=1

Plaintext
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

Delete parameter: status=1

Plaintext
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 signing and verification have been implemented. The current issue is that the same signed URL can be opened multiple times within 1 minute, which still poses a security risk. It is necessary to ensure that a single signature is used only once and becomes invalid after use. After $urlSigner->signParams(), I store the hmac in Redis with an expiration time of 60 seconds, as shown in Figure 6

URL signing and verification have been implemented. The current issue is that the same signed URL can be opened multiple times within 1 minute, which still poses a security risk. It is necessary to ensure that a single signature is used only once and becomes invalid after use. After $urlSigner->signParams(), store the hmac in Redis with an expiration time of 60 seconds
Figure 6
PHP
        // 是否允许添加额外参数:否,将到期时间设置为 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);

PHP
        // 是否允许添加额外参数:是,将到期时间设置为 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. Create the filter common\filters\HmacFilter, which inherits from SamIT\Yii2\UrlSigner\HmacFilter and executes after the hmacFilter. If the Redis hmac exists, delete the Redis hmac and return true; if the Redis hmac does not exist, return false. Create a new \common\filters\HmacFilter.php

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. Edit \frontend\controllers\WeiboOauth2Controller.php, which is the Weibo micro-connection web application authorization (guiding users to the authorization page to log in and consent, creating a user for the Weibo micro-connection web application)

PHP
use common\filters\HmacFilter;

15. Test URL verification by modifying parameters; the results match expectations
For the obtained link, the first verification passes, and the second verification fails:

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

Second verification failed
Forbidden (#403)
This security code in this URL invalid

Modify parameter: permission=1

Plaintext
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

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系