Force.com微信开发系列(七)OAuth2.0网页授权
OAuth是一个开放协议,允许用户让第三方应用以安全且标准的方式获取该用户在某一网站上存储的私密资源(如用户个人信息、照片、视频、联系人列表),而无须将用户名和密码提供给第三方应用。本文将详细介绍OAuth协议以及在微信里的具体实现。
OAuth2.0协议介绍
OAuth2.0是OAuth协议的下一版本,但不向后兼容OAuth 1.0。 OAuth 2.0关注客户端开发者的简易性,同时为Web应用,桌面应用和手机,和起居室设备提供专门的认证流程。 OAuth2.0允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的网站(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要分享他们的访问许可或他们数据的所有内容。
OAuth2.0认证和授权的具体过程:
在Oauth2.0认证和授权的过程中涉及的三方包括:
1. 服务提供方,用户使用服务提供方来存储受保护的资源,如照片,视频,联系人列表。
2. 用户,存放在服务提供方的受保护的资源的拥有者。
3. 客户端,要访问服务提供方资源的第三方应用,通常是网站,如提供照片打印服务的网站。在认证过程之前,客户端要向服务提供者申请客户端标识。
使用OAuth进行认证和授权的过程如下所示:
1. 用户访问客户端的网站,想操作用户存放在服务提供方的资源;
2. 客户端向服务提供方请求一个临时令牌;
3. 服务提供方验证客户端的身份后,授予一个临时令牌;
4. 客户端获得临时令牌后,将用户引导至服务提供方的授权页面请求用户授权。在这个过程中将临时令牌和客户端的回调连接发送给服务提供方;
5. 用户在服务提供方的网页上输入用户名和密码,然后授权该客户端访问所请求的资源;
6. 授权成功后,服务提供方引导用户返回客户端的网页;
7. 客户端根据临时令牌从服务提供方那里获取访问令牌;
8. 服务提供方根据临时令牌和用户的授权情况授予客户端访问令牌;
9. 客户端使用获取的访问令牌访问存放在服务提供方上的受保护的资源。
微信网页OAuth2.0授权:
如果用户在微信中(Web微信除外)访问公众号的第三方网页,公众号开发者可以通过此接口获取当前用户基本信息(包括昵称、性别、城市、国家)。利用用户信息,可以实现体验优化、用户来源统计、帐号绑定、用户身份鉴权等功能。
需要注意的是,获取用户基本信息接口(稍后博文会介绍到)是在用户和公众号产生消息交互时,才能根据用户OpenID获取用户基本信息,而网页授权的方式获取用户基本信息,则无需消息交互,只是用户进入到公众号的网页,就可弹出请求用户授权的界面,用户授权后,就可获得其基本信息(此过程甚至不需要用户已经关注公众号。)
下面我们将通过一个具体的例子来展示开发的详细过程。
配置授权回调域名:
在微信公众号请求用户网页授权之前,开发者需要先到公众平台网站的我的服务页中配置授权回调名,需要注意的是这里的域名不要加http://或者https://。另外,授权回调域名配置规范为全域名,比如需要网页授权的域名为:www.qq.com,配置以后此域名下的所有页面例如http://www.qq.com/music.html, http://www.qq.com/login.html都可以进行OAuth2.0鉴权。但http://pay.qq.com, http://music.qq.com无法进行OAuth2.0鉴权。
为此进入到服务页(使用正式的服务号或认证后的订阅号后通过我的服务找到,如果是测试账号直接在首页即可找到)后找到OAuth2.0网页授权,点击右侧的修改链接:
在弹出窗口里输入域名后点击确定按钮保存:
用户同意授权,获取Code:
此步骤相当于前面介绍到的OAuth2.0认证过程的第二步“客户端向服务提供方请求一个临时令牌”,这里的Code即是临时令牌,为此可以请求微信的OAuth2.0接口以获取该Code,该接口的参数里需要指定一个回调页面URL,为此我们需要创建一个Apex Page,为此登陆Force.com后找到域名,通常从中国访问的域名是https://ap1.salesforce.com, 在浏览器地址栏中输入https://ap1.salesforce.com/apex/oauth2test, 此时Force.com将提示页面不存在,点击“Create Page oauth2test”链接创建页面:
创建好后的页面如下,如果启用了开发者模式,则页面分为上下两个部分,上部分是显示效果,下面是源代码编辑窗口,默认Force.com会将自身的顶部导航、左侧导航以及CSS样式应用到新创建的页面:
我们通过在第一行里加入以下代码告诉Force.com不要使用默认的CSS样式,不要显示顶部以及左侧的导航栏,同时最后一个controller属性指定了apex页面对应的控制器类,类似于aspx页面对应的aspx.cs类,鼠标保持在编辑栏窗口,按住Ctrl + S组合键即可保存编辑后的代码:
<apex:page standardstylesheets="false" showHeader="false" sidebar="false" controller="oauth2testcontroller">
此时,应为对应的oauth2testcontroller类并不存在,浏览器会报以下错误:
这里点击第二个链接“Create Apex class ‘public class oauth2testcontroller’”自动创建控制器类。留意这两个链接的唯一区别在于”with sharing”关键字,这个关键字指定了当前类对各个对对象(相当于数据表)、字段等的访问权限与当前登录用户同,如果不指定,Apex页面将拥有对所有对象、字段的访问权限。创建好后在下方代码编辑栏中将多出一个控制器类Tab:
在该类中添加代码如下:
1 public class oauth2testcontroller { 2 public String code {get; set;} 3 public oauth2testcontroller(){ 4 code = ApexPages.currentPage().getParameters().get('code'); 5 if(String.isBlank(code)){ 6 code = 'No Code'; 7 } 8 } 9 }
这段代码中第2行定义了一个公开属性code,第4行通过ApexPages对象获得URL中的code参数,并接着判断是否code值是否为空,如果为空则提示No Code。下面我们会看到微信授权成功回调此URL时会将code参数添加到URL中。
接下来略微修改前台页面,在页面中显示得到的code值:
1 <apex:page standardstylesheets="false" showHeader="false" sidebar="false" controller="oauth2testcontroller"> 2 {!code} 3 </apex:page>
{!对象名}是Force.com Visualforce page里用来显示对象值的语法,接下来我们需要配置该页面能够通过公网进行访问,为此登陆Force.com后,进入Setup –> Develop –> Sites,点击站点对应的Site Label标签如下图:
进入详细配置页面后找到“Site Visualforce Page”,点击右侧的Edit按钮:
找到左侧列表中的oauth2test页面添加到右侧,并保存修改:
此时即可通过http://johnson0001-developer-edition.ap1.force.com/oauth2test公网地址来访问前面创造的页面了。接下来我们可以利用微信平台的OAuth2认证接口组拼URL并引导用户通过微信来访问,该接口的格式如下:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
若提示“该链接无法访问”,请检查参数是否填写错误,是否拥有scope参数对应的授权作用域权限,其中每个参数的详细说明如下:
在我们的例子里URL如下,其中scope我们指定为snsapi_userinfo,弹出授权页面:
兴许是测试账号的关系,虽然微信接口文档里提到在制定scope为snsapi_userinfo的情况下会弹出如下图左所示的授权页面,但反复尝试(乃至删除并重新关注账号)中也没有看到该页面,不过重点是我们得到了临时令牌,如下图右所示。右图实际是http://johnson0001-developer-edition.ap1.force.com/oauth2test页面,用户同意授权后跳转到(或者我遇到的实际情况是直接跳转)到redirect_uri/?CODE&state=STATE。若用户禁止授权,则重定向后不会带上code参数,仅会带上state参数redirect_uri?state=STATE。
另外特别需要说明的是,code作为换取access_token的临时票据,每次用户授权带上的code都不一样,code只能使用一次,5分钟未被使用自动过期。
通过Code换取网页授权access_token:
首先请注意,这里通过code换取的网页授权access_token,与基础支持中的access_token不同。公众号可通过下述接口来获取网页授权access_token。如果网页授权的作用域为snsapi_base,则本步骤中获取到网页授权access_token的同时,也获取到了 openid,snsapi_base式的网页授权流程即到此为止。 获取code后,可以通过以下接口获取access_token:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
这里的CODE即为通过前面方式获得的临时令牌(票据),参数的具体说明如下:
将URL直接输入到浏览器地址栏即可得到返回数据,当然真实场景里更多通过后台代码来请求,正确返回时的JSON数据包如下:
1 { 2 "access_token":"ACCESS_TOKEN", 3 "expires_in":7200, 4 "refresh_token":"REFRESH_TOKEN", 5 "openid":"OPENID", 6 "scope":"SCOPE" 7 }
参数的具体说明如下:
错误时微信会返回JSON数据包如下(示例为Code无效错误):
{"errcode":40029,"errmsg":"invalid code"}
在本例中获得的access_token实例如下:
{"access_token":"OezXcEiiBSKSxW0eoylIeMEUA_AZuBDY8AO0IIw270MMsvemqLvgx1HqemeXIZfzXW2d6yHCPy9cA1yHZ1jHCkwlH5Ct5Jfa1jOQm88M9LzU_O8BCKMNhN7yLlHJfOFLuf4lLTNGOOsoWYxQzYVNGw","expires_in":7200,"refresh_token":"OezXcEiiBSKSxW0eoylIeMEUA_AZuBDY8AO0IIw270MMsvemqLvgx1HqemeXIZfz_Vj5pJZlv2V5wK9EzWmxQmM07cqIAwMXOdqzlQs-NY4hiyENP4WhO4Twpko-3iY_pAPZRnGGmAVt3DirZaWIyg","openid":"ou-37t936RNZEcW0mI75RN2pdxkc","scope":"snsapi_userinfo"}
可以看到上面access_token的默认失效时间是7200秒,也就是2小时,当access_token超时后,可以通过refresh_token进行刷新,refresh_token拥有较长的有效期(7天、30天、60天、90天),当refresh_token失效后,需要用户重新授权,简化理解起见,我们在本文的最后再介绍相关技术。
拉取用户信息(需Scope为snasapi_userinfo):
通过access_token获取用户信息的接口如下,使用GET方法:
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
参数具体说明如下:
本例的URL如下:
输入浏览器访问即可得到相应的用户信息:
1 {"openid":"ou-37t936RNZEcW0mI75RN2pdxkc","nickname":"王浩","sex":1,"language":"zh_CN","city":"松江","province":"上海","country":"中国","headimgurl":"http:\/\/wx.qlogo.cn\/mmopen\/lqsZNvDqcXe8nBKHBPsp9YHuZXPtkzOD1uq3r3xxDicuDLKGlicNd1b371ODnn9xNBB9y9ChBSfL7tuX6m9FS8koY9Ex1iaJRDI\/0","privilege":[]}
刷新access_token:
可以通过前面在“通过Code换取网页授权access_token”小节中获得的refresh_token来调用刷新Token接口获取更新的access_token,微信在API文档里介绍refresh_token拥有较长的有效期(7天、30天、60天、90天),但实际微信的refresh_token的有效期是多长没有具体说明,如果有具体经验的朋友欢迎分享。微信刷新access_token的接口如下:
https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN
接口的具体参数定义如下:
正确时返回的JSON数据包如下:
1 { 2 "access_token":"ACCESS_TOKEN", 3 "expires_in":7200, 4 "refresh_token":"REFRESH_TOKEN", 5 "openid":"OPENID", 6 "scope":"SCOPE" 7 }
数据包的具体定义如下:
错误时微信会返回JSON数据包如下(示例为Code无效错误):
{"errcode":40029,"errmsg":"invalid code"}