用户账户接入
目录 |
简介
接入百度开发者中心的应用如果有需要用户登录后才能使用的功能,除非应用本身具有一定特殊性,原则上都必须支持用百度用户账号登录,在此前提下,也允许支持用其他账号体系登录。另外,如果应用内需要调用百度开放的Open API接口,一般也需要首先引导用户用百度账号登录授权才行。
目前我们已经推出基于业界最先进的OAuth2.0协议的 百度OAuth2.0服务,它适用于各种类型的应用,包括站内web应用(即目前百度应用开放平台所接入的各种类型应用),也包括第三方web/wap网站、桌面/手机客户端程序、各种浏览器插件等站外应用。
通过百度OAuth2.0服务引导用户登录授权应用后,应用还需要通过 百度Open API V2.0服务提供的各种Open API接口来实现获取用户基本信息、调用百度开放的相关接口等操作。
具体操作流程
我们通过实例来说明第三方应用如何使用百度用户账号。
本示例无法访问,开发要想在自己的浏览器上看到真实的效果,可以按照下述步骤自己部署一个,比如在百度的BAE上,或新浪的SAE上。点击这里可以下载本示例的BAE版源码包,点击这里可以下载本示例的SAE版源码包。
开发前准备
- 首先注册一个应用,得到这个应用的API Key和Secret Key,他们将是后面做OAuth2.0交互时所需要的client_id和client_secret,其显示位置如下图所示:

- 然后设置应用的搜索结果页加载地址、应用独立页加载地址、应用服务器稳定性监地址、应用所在域名列表。本示例为了简单起见,两个加载地址用的是同一个页面。客户端监控路径有两个,http访问为http://app.baidu.com/static/appstore/monitor.st,当采用https访问时,监控路径为https://openapi.baidu.com/static/appstore/monitor.st。如果应用本身包含多个域名,需要在根域名绑定一栏将应用所在根域名或所有域名填入其中,这些域名下的页面都可以使用百度账号登录,如下图:
.png)
.png)
.png)
- 到此基本准备工作就算完成了,当然你也可以在下一个tab页中上传下应用logo图,它会在应用授权页面被用到。
具体开发说明
第一步:先判断当前用户是否已经登录百度
IFrame应用在百度任何页面中被加载时,百度开发者中心都会通过应用画布页中的iframe给应用传递2个跟当前用户相关的参数:
- bd_user:当前登录用户的user id,如果用户未登录,则该值为0。
- bd_sig:对所有以"bd_"为参数名前缀的参数(目前只有bd_user参数)的MD5签名。
例如,本例中访问 http://app.baidu.com/myiframedemo 时,应用可能会接收到这样的请求:
http://robin928.duapp.com/demo/index.php?bd_user=3640932355&bd_sig=6d17a233b72b05f431f3011513f0fd55
要判断"bd_user"这个参数是否真实可信(http请求是明文传递的,可能会被网络截包并被篡改伪造),可以通过对"bd_user"参数重新计算签名并判断是否与"bd_sig"参数一致来得知。在PHP中,可以通过如下代码来判定:
$secret_key = "B2EtoGjjhm3eYgrIGNq1UWUoZ6PwFu3r"; //应用的Secret Key就是签名密钥 $bd_user = $_REQUEST['bd_user']; $expected_sig = md5("bd_user=$bd_user$secret_key"); if ($expected_sig == $_REQUEST['bd_sig']) { //当前用户的uid就是$bd_user这个值了 } else { //这个请求肯定是某人篡改伪造的了,直接忽略这个请求吧要不? }
如果"bd_user"参数校验失败,则可以认为请求是恶意伪造的,可以直接忽略,结束处理;否则,如果"bd_user"参数为0,则表明当前用户尚未登录百度,此时直接进入第三步骤继续处理即可,否则表明当前登录用户的uid值就是"bd_user"参数值,此时需要进入第二步骤继续判断用户是否也登录到应用内了。
还有一种情况是,用户点击IFrame应用内部的某个链接使得应用页面被加载,由于此时页面请求不是通过应用开放平台的应用画布页的iframe来加载的,所以请求到达应用服务端时,是肯定不会带着"bd_user"和"bd_sig"参数的,此时应用无法判断用户是否已经登录百度,则直接进入第二步骤判断用户是否已经登录应用即可。
第二步:判断当前应用是否已经登录到应用内
判断用户是否已经登录到某个应用内不是以用户是否已经登录百度为依据,而是以应用内是否含有当前会话用户的有效会话信息为依据。用户会话信息包含应用通过OAuth2.0协议获取到的用户授权码信息(如access_token、session_key、session_secret等),以及通过用户授权码调用passport/users/getLoggedInUser接口所获取到的uid、uname等用户基本信息。应用在获取到这些信息后,应该通过Session、Cookie、关系数据库或各种KV存储系统等存储介质将其与当前用户的会话ID一一对应并保存起来,以便下次处理页面请求时使用,减少不必要的网络请求时间。本例中,我们采用的是浏览器Cookie作为会话信息的存储介质。
页面请求处理到达本步骤后,应用先从相应的存储介质上读取当前会话用户的会话信息,如果会话信息不存在,则表明用户尚未登录本应用,则直接进入第三步骤即可;如果会话信息存在,则需要分以下2种情况来判断:
- 如果当前页面请求url中不存在"bd_user"和"bd_sig"参数,则表明用户是在iframe内部做操作,在这种情况下,即使用户在其他页面中已经退出登录,或已经更换账号登录,应用仍然以最后一次登录该应用的用户身份作处理也是没问题的;
- 如果当前页面请求url中存在"bd_user"和"bd_sig"参数,则表明用户刷新了整个百度页面,此时应用内部的用户登录信息必须与应用所在的百度页面上的用户登录信息保持同步,这就需要对比"bd_user"参数是否与读取到的用户会话信息中存储的用户uid是否相等:
- 如果相等,说明当前用户仍然是之前登录到应用内的那个用户,则读取到的用户会话信息缓存就是当前用户的有效会话信息,后续调用Open API接口可以直接利用其中的授权码数据;
- 如果不相等,说明当前用户已经不是之前登录到应用内的那个用户,例如用户已经更换账号登录,则读取到的用户会话信息也已经失效,需要从存储介质上删除,并进入第三步骤,通过OAuth2.0协议和Open API接口重新获取一份有效的用户会话信息,并将其重新存储到存储介质上。
第三步:引导用户登录授权应用并获取Access Token信息
确定用户尚未登录应用后,应用就可以使用OAuth2.0中的使用Authentication Code获取Access Token模式(又称Web Server Flow)引导用户登录授权,并获取到后续访问百度Open API所需的相关用户授权码,基本流程如下所示:
- 引导用户登录授权,获取Authorization Code;
- 使用Authorization Code换取Access Token。
关于这两步的具体流程可以参考百度OAuth2.0服务中关于使用Authentication Code获取Access Token这一篇文档。下面我们将通过本示例应用在实现过程中所用的方法来说明这两个步骤。
引导用户登录授权,获取Authorization Code
在应用内通过浮层对话框加载如下URL:
https://openapi.baidu.com/oauth/2.0/authorize? response_type=code& client_id=CaIoYk2sfz1cBwvv7EGxT7tn& redirect_uri=http%3A%2F%2Frobin928.duapp.com%2Fdemo%2Flogin_callback2.php& display=page
此时如果用户还没有登录百度,则将看到如下图所示的页面:

如果用户之前没有给应用授过权,则输入用户名、密码登录后将看到如下图所示的界面:

在此界面上,如果用户点击允许授权,则授权服务器将为应用授予用户默认权限,并生成一个Authorization Code(假设为a79fefe2a5de23bf2a3b0132f4bb4fff),然后将浮层对话框内的页面重定向到如下URL:
http://robin928.duapp.com/demo/login_callback2.php?code=a79fefe2a5de23bf2a3b0132f4bb4fff
此时login_callback2.php这个页面拿到就可以通过"code"参数拿到Authorization Code了。
使用Authorization Code换取Access Token
拿到Authorization Code后,通过应用服务器端向如下URL地址发起一个POST请求:
https://openapi.baidu.com/oauth/2.0/token
其中,POST请求参数如下所示:
grant_type=authorization_code&code=a79fefe2a5de23bf2a3b0132f4bb4fff&client_id=CaIoYk2sfz1cBwvv7EGxT7tn&client_secret=B2EtoGjjhm3eYgrIGNq1UWUoZ6PwFu3r&redirect_uri=http%3A%2F%2Frobin928.duapp.com%2Flogin_callback2.php
当然你也可以通过GET方法向如下地址发起请求:
https://openapi.baidu.com/oauth/2.0/token? grant_type=authorization_code& code=a79fefe2a5de23bf2a3b0132f4bb4fff& client_id=CaIoYk2sfz1cBwvv7EGxT7tn& client_secret=B2EtoGjjhm3eYgrIGNq1UWUoZ6PwFu3r& redirect_uri=http%3A%2F%2Frobin928.duapp.com%2Fdemo%2Flogin_callback2.php
此时,授权服务器会返回如下格式的JSON文本:
{ "access_token":"1.03ce7530fbbff7ab5696015409e4cc8e.86400.1310024300.2786290847-131640", "expires_in":86400, "session_secret":"cb354cbcd4a1fb779bd7ba44441168c6", "session_key":"9XMeqTFMIeotrGRGxBZdAM9ARSyP\/8q84KOREDP1nLNj4d\/UyMreoG2oXetIZoLpk7RgSunCTmKcGHvOMFGBwjUaCWmx", "scope":"basic" }
第四步:获取用户基本信息
获取到Access Token后,应用可以通过https或http请求发起一个passport/users/getLoggedInUser接口调用,具体调用方法可以参考Open API 2.0 文档。
例如,继续本例的上一个步骤,应用服务器端可以通过向如下URL地址发送一个HTTPS GET请求:
https://openapi.baidu.com/rest/2.0/passport/users/getLoggedInUser?access_token=1.03ce7530fbbff7ab5696015409e4cc8e.86400.1310024300.2786290847-131640
或向如下URL地址发送一个HTTP GET请求:
http://openapi.baidu.com/rest/2.0/passport/users/getLoggedInUser?session_key=9XMeqTFMIeotrGRGxBZdAM9ARSyP%5C%2F8q84KOREDP1nLNj4d%5C%2FUyMreoG2oXetIZoLpk7RgSunCTmKcGHvOMFGBwjUaCWmx×tamp=2011-06-21+17%3A18%3A09&format=json&sign=2f44b3eb1a2fadeb9c529c08765e60d8
此时百度Open API 2.0服务将返回当前登录用户的基本信息,如下JSON文本所示:
{ "uid":3640932355, "uname":"robin928" }
第五步:保存用户会话信息
最后,将第三步获取到的用户授权码信息和第四步获取到的用户基本信息一起作为当前登录用户的有效会话信息保存到会话信息存储介质上。本例中,我们通过Cookie来存储,存储方式是在返回用户页面请求的响应头中带上如下SetCookie头:
SetCookie: bds_CaIoYk2sfz1cBwvv7EGxT7tn=expires_in%3D86400%26access_token%3D1.aa3dd5591bfa01d3cb3294612f4b76b8.86400.1310141154.3640932355-132361%26session_secret%3D3e6c5abd7c695c591dfcda11dfb83e2d%26session_key%3D9XNPrWq%252FvVCV7ZoByANm1QuHRuRcrHRFm%252Fod9bkyHM6k2Ff4hedSGbKK7YFEfoGMr9Oz5rHFJrlpuXiJUJIYiUcVvMLg%26scope%3Dbasic%26uid%3D3640932355%26uname%3Drobin928%26portrait%3D76d9726f62696e393238dc00; expires=Sat, 09-Jul-2011 16:05:54 GMT; path=/
其中,该Cookie项的key的构成方式是"bds_" + 应用API Key,value则是按照query string的格式组织。用户会话信息保持到Cookie中后,则下次应用页面被加载时我们就可以直接从中读取到用户的授权码及个人基本信息,但为保证会话信息的时效性,每次页面请求处理时,都还是需要从第一步开始进行判断的。
在此之后,应用就可以通过会话信息中的access_token或session_key和session_secret来调用其他Open API了。
如何使用官方版SDK实现引导用户登录授权
这里我们通过前面提到的示例应用介绍下如何通过官方版的PHP SDK来实现,具体步骤如下(点击这里可以下载本示例的BAE版源码包,点击这里可以下载本示例的SAE版源码包。):
实例化Baidu类
首先实例化一个Baidu类:
require_once 'Baidu.php'; $baidu = new Baidu( 'CaIoYk2sfz1cBwvv7EGxT7tn', //API Key 'B2EtoGjjhm3eYgrIGNq1UWUoZ6PwFu3r', //App Secret Key new BaiduCookieStore('CaIoYk2sfz1cBwvv7EGxT7tn') //BaiduStore实例,用于存取用户会话信息 );
- 其中,第三个参数是一个BaiduStore实例,这里我们用SDK自带的BaiduCookieStore类实现,用于通过Cookie来存取用户会话信息。如果开发者希望将会话信息存储在其他地方,如数据库中,则需要自己通过继承BaiduStore抽象类来实现一个新的类。
调用getLoggedInUser接口判断用户是否登录
调用Baidu类实例的getLoggedInUser()接口获取当前登录用户的uid和uname属性,如果接口调用返回的是false,则表明当前用户尚未登录到应用内,否则表示用户已登录:
$user = $baidu->getLoggedInUser(); if ($user) { //用户已经登录,通过$user['uid']和$user['uname']可以得到用户的uid和uname } else { //用户尚未登录应用 }
构造登录、退出链接地址
如果用户未登录,则构造引导用户登录的URL:
$loginUrl = $baidu->getLoginUrl( array('response_type' => 'code', 'redirect_uri' => 'http://robin928.duapp.com/demo/login_callback2.php') );
表示完成用户登录、授权操作后页面将跳回到 http://robin928.duapp.com/demo/login_callback2.php 这个地址。
否则,则构造允许用户退出登录的URL:
$logoutUrl = $baidu->getLogoutUrl( array('next' => 'http://robin928.duapp.com/demo/logout_callback2.php') );
表示用户退出登录后,页面将跳回到 http://robin928.duapp.com/demo/logout_callback2.php 这个地址。
引导用户登录、授权
如果用户未登录,则通过浮层对话框加载前面构造的引导用户登录用的URL:
<script> new LightFace.IFrame({height:320, width:560, url: '<?php echo $loginUrl;?>'}).open(); </script>
此时如果用户并未登录百度,则会看到如下界面:

如果用户已经登录百度,但尚未给应用授权,则会看到如下界面:

如果用户已经登录百度,也已经授权过应用,则此时会自动直接跳转到 http://robin928.duapp.com/demo/login_callback2.php 这个页面,在该页面中,通过如下代码即可获取到用户授权码相关信息:
require_once 'Baidu.php'; $baidu = new Baidu( 'CaIoYk2sfz1cBwvv7EGxT7tn', //API Key 'B2EtoGjjhm3eYgrIGNq1UWUoZ6PwFu3r', //App Secret Key new BaiduCookieStore('CaIoYk2sfz1cBwvv7EGxT7tn') //BaiduStore实例,用于存取用户会话信息 ); $access_token = $baidu->getAccessToken();
此时,用户会话信息也会自动存储到相应的存储介质上,本例中就是存储到了用户浏览器Cookie中。
然后,在JS代码中直接reload当前页面的父页面以刷新调用浮层对话框的页面即可:
<script> parent.location.reload(); </script>