Salesforce Force.com Site 集成微信公众平台 (八) OAuth 2.0

OAuth 是一个开放协议,用户允许第三方应用以安全且标准的方式获取该用户在某一网站上存储的私密资源,而无须将用户名和密码提供给第三方应用。接下来将介绍 OAuth 协议在微信里的具体实现。

OAuth 2.0 协议介绍

OAuth 2.0 是 OAuth 协议的下一版本,但不向后兼容 OAuth 1.0。 OAuth 2.0 关注客户端开发者的简易性,同时为 Web 应用,桌面应用和手机,起居室设备提供专门的认证流程。OAuth 2.0 允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务中的数据。每一个令牌授权一个特定的网站(例如,视频编辑网站) 在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth 允许用户授权第三方网站访问他们存储在另外服务上的信息,而不需要分享他们的访问许可或他们数据的所有内容。

微信公众平台 OAuth 2.0 授权

1. 用户关注微信公众账号

2. 微信公众账号提供用户请求授权页面 URL

3. 用户点击授权页面 URL,将向服务器发起请求

4. 服务器询问用户是否同意授权给微信公众账号(scope 为 snsapibase 时无此步骤)

5. 用户同意(scope 为 snsapi
base 时无此步骤)

6. 服务器将 CODE 通过回调传给微信公众账号

7. 微信公众账号获得 CODE

8. 微信公众账号通过 CODE 向服务器请求 Access Token

9. 服务器返回 Access Token 和 OpenID 给微信公众账号

10. 微信公众账号通过 Access Token 向服务器请求用户信息(scope 为 snsapibase 时无此步骤)

11. 服务器将用户信息回送给微信公众账号(scope 为 snsapi
base 时无此步骤)

微信网页 OAuth2.0 授权

如果用户在微信中( Web 微信除外)访问公众号的第三方网页,公众号开发者可以通过此接口获取当前用户基本信息(包括昵称、性别、城市、国家)。利用用户信息,可以实现体验优化、用户来源统计、帐号绑定、用户身份鉴权等功能。

需要注意的是,获取用户基本信息接口是在用户和公众号产生消息交互时,才能根据用户 OpenID 获取用户基本信息,而网页授权的方式获取用户基本信息,则无需消息交互,只是用户进入到公众号的网页,就可弹出请求用户授权的界面,用户授权后,就可获得其基本信息(此过程甚至不需要用户已经关注公众号)。

Salesforce 中应用微信 OAuth

了解了微信 OAhth 的方式后,下面我们将通过一个具体的例子来展示开发的详细过程。

配置授权回调域名

在微信公众号请求用户网页授权之前,开发者需要先到公众平台网站配置授权回调名,需要注意的是这里的域名不要加 http:// 或者 https://。另外,授权回调域名配置规范为全域名,比如需要网页授权的域名为:www.qq.com,配置以后此域名下的所有页面例如http://www.qq.com/music.html, http://www.qq.com/login.html 都可以进行 OAuth 2.0 鉴权。但 http://pay.qq.comhttp://music.qq.com 无法进行 OAuth 2.0 鉴权。

在体验接口权限表中,找到 网页服务 -> 网页账号 -> 网页授权获取用户基本信息,点击修改,在弹出窗口里输入域名并点击确定按钮保存。域名是之前在 Salesforce 中创建的 Force.com Site。如下图所示:

用户同意授权,获取 Code

此步骤是前面介绍到 OAuth 2.0 认证过程中的第二步客户端向服务提供方请求一个临时令牌,这里的 Code 即是临时令牌,为此可以请求微信的 OAuth 2.0 接口以获取该 Code,该接口的参数里需要指定一个回调页面 URL,为此我们需要创建一个 Visualforce Page: TestOAuth2.page

接下来我们创建一个对应 TestOAuth2 page 的 Controller, 代码如下:

public class TestOAuth2Controller  
{
    public TestOAuth2Controller()
    {
        Code = ApexPages.currentPage().getParameters().get('code'); 
        if(String.isBlank(code))
        {
            Code = 'No Code'; 
        } 
    }

    public String Code { get; set; }
}

更新 TestOAuth2 代码

<apex:page standardStylesheets="false" showHeader="false" sidebar="false" controller="TestOAuth2Controller">  
    {!Code}
</apex:page>  

接下来我们需要配置该页面能够通过公网进行访问,进入 Setup –> Develop –> Sites,点击 Site 对应的 Site Label 进入详细配置页面后找到 Site Visualforce Page section,点击右侧的 Edit 按钮:

找到左侧列表中的 TestOAuth2 页面添加到右侧 Enabled Visualforce Pages 中,点击 Save

此时即可通过 http://testprint2014-developer-edition.ap1.force.com/TestOAuth2 公网地址来访问前面创造的页面了。接下来我们可以利用微信平台的 OAuth 2.0 认证接口组拼 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,弹出授权页面:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx74f79a5aa5fd241b&redirect_uri=http://testprint2014-developer-edition.ap1.force.com/TestOAuth2&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect  

演示效果如下

另外特别需要说明的是,code 作为换取 access_token 的临时票据,每次用户授权带上的 code 都不一样,code 只能使用一次,5分钟未被使用自动过期。

通过 Code 换取网页授权 access_token

首先请注意,这里通过 code 换取的网页授权 accesstoken, 与基础支持中的 accesstoken 不同。公众号可通过下述接口来获取网页授权 accesstoken。如果网页授权的作用域为 snsapibase,则本步骤中获取到网页授权 accesstoken 的同时,也获取到了 openid,snsapibase 式的网页授权流程即到此为止。获取 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 数据包如下:

{
   "access_token":"ACCESS_TOKEN",
   "expires_in":7200,
   "refresh_token":"REFRESH_TOKEN",
   "openid":"OPENID",
   "scope":"SCOPE"
}

参数的具体说明:

错误时微信会返回JSON数据包如下(示例为Code无效错误):

{"errcode":40029,"errmsg":"invalid code"}

最后 TestOAuth2Controller 代码:

public class TestOAuth2Controller  
{
    public TestOAuth2Controller()
    {
        Code = ApexPages.currentPage().getParameters().get('code'); 
        if(String.isBlank(code))
        {
            Code = 'No Code'; 
        }
        else
        {
            getAcessToken();
        }
    }

    public String Code { get; set; }
    public String Result { get; set; }

    private void getAcessToken()
    {
        Http h = new Http(); 
        HttpRequest req = new HttpRequest(); 
        req.setMethod('GET');
        req.setHeader('Content-Type','text/xml;charset=UTF-8'); 
        req.setEndpoint('https://api.weixin.qq.com/sns/oauth2/access_token?' + 
           'appid=wx74f79a5aa5fd241b&' + 
           'secret=00bd9bcda817c4a344a7287511c1aeee&' + 
           'code=' + Code + '&grant_type=authorization_code');

         HttpResponse res = h.send(req); 
         Result = res.getBody();
    }
}

修改 TestOAuth2 page 代码:

<apex:page standardstylesheets="false" showHeader="false" sidebar="false" controller="TestOAuth2Controller">  
    Code: {!code}<br />
    Result: {!Result}
</apex:page>  

在微信中访问 page:

结语

采用 scope 为 snsapi_userinfo,在获取到 access_token 后,即使用户没有关注我们的公众帐号,也可以通过 access_token 得到用户的详细信息: