如题,在接入百度统计接口数据时发现,使用百度账号接口的 百度账号登录 流程报错:错误代码:redirect_uri_mismatch,错误信息:Invali redirect uri,详细描述:授权回调地址错误。具体内容请开发者参考“redirect uri”验证方式

按照百度的帮助文档去定位错误原因,然后通过 百度账号接口说明 中的 百度开发者中心控制台 登录到项目工程列表,里面是之前创建的一些项目,用到了百度的 API 而申请的。按照指示,点击安全设置进入到以下的页面:

百度管理控制台 - 安全设置

授权回调页 中添加了回调地址(主要是为了接受百度授权账号登录成功后跳转携带的 code 参数),然后去尝试再次请求,依然报错。就这样折腾了半天各种方法都尝试之后,想着去搜索一下这个问题,看是否有解决方案。直接搜索 redirect_uri_mismatch 出来的都是新浪微博授权登录失败之类的问题。换成 百度统计 授权回调页设置 之后,终于找到了一个匹配度很高的方法:百度回调地址问题 - cnblogs

里面加粗强调了一件事:修改 redirect url 地址(回调地址必须填写为 bdconnect://success, 不然会报错)。在安全设置中的回调地址依然填写之前的,修改的是 百度账号登录 以及之后所有接口参数中用到的 redirect_uri 参数。

尝试修改之后再次访问,发现页面没有报错了,跳转到了百度账号授权登录页面。登录状态下直接点击授权登录,然后页面加载了一下就停了。通过浏览器控制台发现,授权登录成功后,页面跳转到 bdconnect://success?code=xxx了。code 是携带了,但没有走我的回调地址,也就拿不到这个 code。回头一想,我回调地址也没传给他呀,用我的回调地址,则授权登录无法进行,用这个 bdconnect://success 可以授权成功,但我拿不到 code。看到这个解决方案还是 2014 年写的,这么长时间了百度还是没有更新这个 bug,真 TM 坑。

最后取了一个折中的方法,通过页面授权登录,控制台请求 response 中拿到 code,然后回调地址手动输入 code 参数使其加入到缓存中,从而跳过获取授权码的这个过程。后续的 access_token 和 refresh_token 可以循环跑起来,就不需要授权码了。

以下是我封装的入口文件,参考了获取微信 access_token 信息的模板(本身基于 destoon 框架):

统计初始化文件 init.inc.php

require DT_ROOT.'/xxx/config.inc.php';
$session = new dsession();
class tongji {
    var $code;
    var $access_token;
    var $refresh_token;
    var $current_url;

    function __construct() {
        global $DT_TIME;
        $this->time = $DT_TIME;
//        $this->current_url = DT_PATH . 'xxx/index.php';
        $this->current_url = 'bdconnect://success';
    }

    function tongji() {
        $this->__construct();
    }

    function http_get($url) {
        $rec = dcurl($url);
        $arr = json_decode($rec, true);
        return $arr ? $arr : array();
    }

    function http_post($url, $par) {
        $rec = dcurl($url, $par);
        $arr = json_decode($rec, true);
        return $arr ? $arr : array();
    }

    function get_code() {
        global $dc;
        $tongji_code = $dc->get('tongji_code');
        if($tongji_code) {
            return $tongji_code;
        }
        // 4. 百度账号登录
        $url = 'http://openapi.baidu.com/oauth/2.0/authorize?response_type=code&client_id='.TONGJI_APIKEY.'&redirect_uri='.$this->current_url.'&scope=basic&display=popup';
        dheader($url);
        return '';
    }

    function get_token() {
        global $dc;
        // code 获取存在问题,现直接通过外部输入 code
        $this->code = $this->get_code();
        if (!$this->code) {
            exit();
        }
        // 5. 通过身份验证(code)获取 ACCESS_TOKEN
        $url = 'http://openapi.baidu.com/oauth/2.0/token?grant_type=authorization_code&code='.$this->code.'&client_id='.TONGJI_APIKEY.'&client_secret='.TONGJI_SECRETKEY.'&redirect_uri='.$this->current_url;
        $arr = $this->http_get($url);
        $dc->set('tongji_access_token', $arr['access_token'], 2592000);
        $dc->set('tongji_refresh_token', $arr['refresh_token'], 311040000);
        return isset($arr['access_token']) ? $arr['access_token'] : '';
    }

    function refresh_token() {
        global $dc;
        $this->refresh_token = $dc->get('tongji_refresh_token');
        if (!$this->refresh_token) {
            $this->get_token();
        }
        $url = 'http://openapi.baidu.com/oauth/2.0/token?grant_type=refresh_token&refresh_token='.$this->refresh_token.'&client_id='.TONGJI_APIKEY.'&client_secret='.TONGJI_SECRETKEY;
        $arr = $this->http_get($url);
        $dc->set('tongji_access_token', $arr['access_token'], 2592000);
        $dc->set('tongji_refresh_token', $arr['refresh_token'], 311040000);
        return isset($arr['access_token']) ? $arr['access_token'] : '';
    }

    function getSiteList() {
        $url = 'https://openapi.baidu.com/rest/2.0/tongji/config/getSiteList?access_token='.$this->access_token;
        return $this->http_get($url);
    }
}

$tj = new tongji;
$tj->access_token = $dc->get('tongji_access_token');
if(!$tj->access_token) {
    // 直接更新 access_token
    $tj->access_token = $tj->refresh_token();
}

配置文件 config.inc.php

define('TONGJI_ID', 'xx');
define('TONGJI_APIKEY', 'xx');
define('TONGJI_SECRETKEY', 'xx');

回调地址 index.php

// 百度统计接口
require '../../common.inc.php';
require DT_ROOT.'/api/baidu/init.inc.php';

/**
 * API 文档地址
 * https://tongji.baidu.com/api/manual/Chapter2/openapi.html
 * 省略前面申请百度账号和创建工程获取API Key 与Secret Key 的过程
 * 从 4. 百度账号登录 开始
 */
if ($code) {
    // 存在 code 表示登录成功后跳转回调,当前地址即为回调 URL
    // 5. 通过身份验证(code)获取 ACCESS_TOKEN
    $dc->set('tongji_code', $code, 311040000);
    dheader($tj->current_url);
}

echo $tj->access_token;

然后拿到公司百度统计的账号,想着登录进去再创建新的工程,拿到 API Key 和 Secret Key。结果进去发现,公司用的是百度商业账号,接口也跟百度账号完成不一样,瞬间懵逼了。这意味着我之前都是在浪费时间,现在对接接口还得从头来过。

好在百度商业账号的接口非常简单好接,不需要 API Key 和 Secret Key,用户名和密码现成的,token 在百度统计后台的管理菜单下,左侧 其他设置 中的 数据导出服务 中获取。点击开通服务,token 就会显示在页面上了,百度账号就没有这个东西了。

百度商业账号开通数据导出服务

总结:下次对接接口的时候,一定要确定自己对接的是与目标功能匹配的接口,否则再多的努力都是白费功夫。