微信於9月份推出企業號后引起了業界不小的反響,許多企業都在思索企業號將如何影響企業的運營,從本文開始,我將詳細闡述微信企業號開發的相關知識,而本文將着重介紹如何實現更高安全機制的二次驗證。
申請企業體驗號:
企業號顧名思義就是企業來申請的號,申請時就像申請服務號一樣,需要提供各種組織證明文件,對廣大開發者來說很難操作,好在騰訊公司也像服務號一樣開通了體驗號申請,留意企業體驗號的有效期間非常短,只有90天(服務號測試賬號有1年有效期),且如果企業體驗號長期不使用還會收到騰訊公司的提前失效提醒郵件。企業體驗號的申請鏈接如下,開發者只需要按照騰訊公司的引導完成注冊步驟,立刻就能獲得體驗號:
http://qydev.weixin.qq.com/try?t=experience
通訊錄添加成員:
與公眾號不同的是,因為是面向企業內部,所以騰訊允許企業主動添加粉絲,具體操作是進入到通訊錄后點擊+按鈕添加新成員,留意作為唯一識別個人信息,微信號、手機號或者郵箱必須至少有一個,直接搜集微信號通常比較困難,一般可以使用企業HR數據庫里的手機號和郵箱等信息,具體操作上除了手工添加還可以通過Excel模板導入以及通過騰訊企業號微信API來添加,關於API添加用戶稍后章節介紹。
輸入完成以后,可以將企業微信號的二維碼發送給員工,員工掃描后會自動出現系統默認的企業號小助手,小助手會自動引導員工通過郵箱或手機驗證碼來完成員工身份綁定的過程,此為一次驗證,企業自行確保通訊錄員工數據的正確性,后續依賴於騰訊公司來進行員工驗證,驗證通過后通訊錄狀態列的問號會消失,表明一次驗證通過:
啟用二次驗證:
一次驗證通常能夠滿足大多數企業的要求,但對於員工信息以及權限管理比較嚴格的公司來說,一次驗證還不足夠放心,希望能夠員工通過輸入公司內部的用戶名和密碼再進行一次驗證,此為二次驗證,二次驗證的啟用位於企業號首頁設置處,到設置畫面后滾動畫面找到二次驗證,點擊右側的選擇鈕啟用二次驗證:
此時會彈出如下窗口需要輸入企業的二次驗證頁面地址:
為此我們可以參考企業號官方接口文檔http://qydev.weixin.qq.com/wiki/index.php?title=%E5%85%B3%E6%B3%A8%E4%B8%8E%E5%8F%96%E6%B6%88%E5%85%B3%E6%B3%A8在Force.com平台開發相應頁面。
開發二次驗證用頁面:
同樣,頁面分成兩個部分,一部分是顯示部分,用來輸入用戶名和密碼,頁面示意圖如下,用戶輸入用戶名user以及密碼123點擊綁定按鈕既可以完成綁定:
頁面名稱是EmployeeAuth,頁面代碼如下,有些屬於apex代碼特有的標簽,無需做深入理解,重要是在第13行按鈕的action屬性指定了bind方法,當點擊按鈕的時候將調用控制器類EmployeeAuthController的bind方法:
1 <apex:page standardstylesheets="false" showHeader="false" sidebar="false" controller="EmployeeAuthController"> 2 <font size="50"> 3 <h1>Please input your user name and password</h1> 4 </font> 5 <font size="30"> 6 UserName: user<br /> 7 Password: 123<br /><br /> 8 <hr/> 9 <apex:form > 10 UserName: <apex:inputText size="100" style="height:100px" value="{!strUsername}" id="strUsername"/><br /><br /> 11 Password: <apex:inputText size="100" style="height:100px" value="{!strPassword}" id="strPassword"/><br /><br /> 12 <center> 13 <apex:commandButton value="Bind" style="width:600px; height:100px;font-size:50px" action="{!bind}" id="bind" /> 14 </center> 15 </apex:form> 16 {!msg} 17 </font> 18 </apex:page>
在解讀EmployeeAuthController控制器類的代碼前我們首先看看微信二次認證的步驟。
二次驗證的步驟與機理:
1. 首先,當微信一次驗證(或郵箱或手機號碼等認證)完成后,微信會發送如下圖所示的消息給到用戶:
2. 頁面跳轉:
當用戶點擊這個圖文的時候實際上打開了一個位於open.weixin.qq.com網站下面的網頁,這個頁面會做一些處理后跳轉到前面在二次驗證里設置的URL也就是我們正在開發的這個頁面,在跳轉的時候還會再我們設置的URL后面加上參數code=CODE&state=STATE,例如在本例里二次驗證配置的URL是http://johnson0001-developer-edition.ap1.force.com/EmployeeAuth,那么從騰訊openweixin.qq.com跳轉后實際打開的URL是http://johnson0001-developer-edition.ap1.force.com/EmployeeAuth?code=CODE&state=STATE 。這里的state參數是干嘛的騰訊公司並沒有說明目前看也並不重要。重要的是code參數,利用這個參數可以調用騰訊的oauth2接口換取員工的userid,留意userid是一個很重要的概念,在企業號里沒有微信OpenId一說,只有userid用來唯一標識用戶,這個userid實際上就是我們在維護通訊錄時的賬號字段值:
3. 通過code調用騰訊oauth2接口換取員工userid
關於這個接口的說明參見騰訊文檔http://qydev.weixin.qq.com/wiki/index.php?title=%E6%A0%B9%E6%8D%AEcode%E8%8E%B7%E5%8F%96%E6%88%90%E5%91%98%E4%BF%A1%E6%81%AF,也可以參加下方說明,這里需要特別說明的是access token和agentid:
做過微信公共號開發或者看過前面介紹相關開發文章的讀者應該不會陌生,當主動調用騰訊的api時都需要access token已確保訪問的正當性,獲得access token相應的也有一個專門的接口,具體的介紹可以參見騰訊公司文檔http://qydev.weixin.qq.com/wiki/index.php?title=%E4%B8%BB%E5%8A%A8%E8%B0%83%E7%94%A8,簡單點說獲得access token實際就是通過以下接口:
https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=id&corpsecret=secrect
這個接口里Corpid好找,打開設置就能找到,如下圖:
不過corpsecret就沒那么好找,實際是需要系統管理員在后台創建管理組,創建管理組后就可以擁有相應的Secret,而這個Secret所擁有的訪問權限就是系統管理員創建的管理組所擁有的權限,騰訊文章http://qydev.weixin.qq.com/wiki/index.php?title=Secret也有提到:
再回過頭來說agentid騰訊文檔里提到指的是“跳轉鏈接時所在的企業應用ID”,在本例里其實指的就是發送“身份驗證”圖文消息的那個應用也就是“企業小助手”的應用ID,當然在不同的用戶場景里可能會是不同的應用在調用換取userid接口,如何查看“企業小助手”的應用ID呢?進到應用中心,第一個就是企業小助手,點擊進入就可以看到如下圖所示的企業應用ID了:
4. 二次驗證
拿到userid后實際就可以進行二次驗證了,二次驗證的方式有很多種,例如如果公司已經建立起良好的通訊錄管理機制(userid等和企業人力資源數據庫同步,入職離職員工均能和企業號通訊錄同步),拿到userid后只要判斷這個userid是一位在職員工就可以自動判斷為二次驗證通過,或者再保險點如本例演示的,要求員工輸入公司的員工用戶名和密碼進行驗證。留意,輸入用戶名和密碼驗證的頁面也就是我們前面提到的二次驗證頁面是屬於企業擁有也是企業開發的,這樣就確保了企業對安全的控制,具體操作上,用戶輸入用戶名和密碼后企業可以調用已有的接口進行驗證,如果驗證成功則將員工的userid等信息保存在業務系統數據庫中一遍后續操作。
5.通知騰訊關注成功
現在最后一步等企業在自己的網頁里完成了用戶驗證后只剩下通知騰訊該用戶已經驗證成功讓相應員工關注成功,此時應該調用如下接口,可以看到接口需要的第二個參數即是我們前面換回來的userid:
https://qyapi.weixin.qq.com/cgi-bin/user/authsucc?access_token=ACCESS_TOKEN&userid=USERID
此接口的詳細說明如下:
二次驗證的代碼實現:
按照前面的思路,我們首先獲取從騰訊跳轉過來的code,並通過code換取用戶的userid,換取的這個過程在頁面加載中完成,為此主要代碼應放在類構造器里。下面的代碼里設置了五個變量,其中strPassword和strUsername和用戶在頁面里輸入的用戶名和密碼相對應,userID用來存儲換回來的userid信息,msg用來調試幫助在頁面里顯示中間信息,accessToken則用來存儲access token:
1 public class EmployeeAuthController { 2 3 public String strPassword { get; set; } 4 public String strUsername { get; set; } 5 public String msg { get; set; } 6 public String userID { get; set; } 7 public String accessToken { get; set; } 8 9 public EmployeeAuthController (){ 10 accessToken = obtainAccessToken(); 11 String code = ApexPages.currentPage().getParameters().get('code'); 12 //Obtain user ID 13 Http h = new Http(); 14 HttpRequest req = new HttpRequest(); 15 req.setMethod('GET'); 16 req.setHeader('Accept-Encoding','gzip,deflate'); 17 req.setHeader('Content-Type','text/xml;charset=UTF-8'); 18 req.setHeader('User-Agent','Jakarta Commons-HttpClient/3.1'); 19 req.setEndpoint('https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=' + accessToken + '&code=' + code + '&agentid=0'); 20 String bodyRes = ''; 21 try{ 22 HttpResponse res = h.send(req); 23 bodyRes = res.getBody(); 24 } 25 catch(System.CalloutException e) { 26 System.debug('Callout error: '+ e); 27 ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL, e.getMessage())); 28 } 29 msg = bodyRes ; 30 //String operation to obtain userID: 31 JSONParser parser = JSON.createParser(bodyRes); 32 while(parser.nextToken() != null){ 33 if((parser.getCurrentToken() == JSONToken.FIELD_NAME)){ 34 String fieldName = parser.getText(); 35 parser.nextToken(); 36 if(fieldName == 'UserId'){ 37 userID = parser.getText(); 38 } 39 } 40 } 41 msg = userID; 42 } 43 44 }
上述代碼第9行調用obtainAccessToken方法獲取accessToken,后續會介紹該方法的詳情,accessToken兩個小時內會失效,所以這里采取實時獲取的方式,當然可以設計的再巧妙些以省卻每次實時獲取accessToken的網絡開銷。第10行獲得了從騰訊跳轉過來時帶的code參數,從第11行通過HttpRequest方法來調用換取接口獲得userid,留意第18行指定了agentid為0,這是因為驗證消息是從企業小助手應用發起的,而企業小助手應用id是0。第29行開始解析返回來的JSON數據獲取userid。
下面是obtainAccessToken方法,方法內容也比較直接,主要通過調用gettoken接口來獲取accessToken,並通過JSONParser類來解析返回的JSON數據以獲得accessToken:
1 private String obtainAccessToken(){ 2 String token; 3 Http h = new Http(); 4 HttpRequest req = new HttpRequest(); 5 req.setMethod('GET'); 6 req.setHeader('Accept-Encoding','gzip,deflate'); 7 req.setHeader('Content-Type','text/xml;charset=UTF-8'); 8 req.setHeader('User-Agent','Jakarta Commons-HttpClient/3.1'); 9 req.setEndpoint('https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=wx548178d7f347f582&corpsecret=9pwWy0AVoT6V65hnwZLYdi4jnLLx65ofBRb_Ds0mAozysQoywDaqbqYCqglm2vhr'); 10 String bodyRes = ''; 11 try{ 12 HttpResponse res = h.send(req); 13 bodyRes = res.getBody(); 14 } 15 catch(System.CalloutException e) { 16 System.debug('Callout error: '+ e); 17 ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL, e.getMessage())); 18 } 19 msg = bodyRes; 20 JSONParser parser = JSON.createParser(bodyRes); 21 while(parser.nextToken() != null){ 22 if((parser.getCurrentToken() == JSONToken.FIELD_NAME)){ 23 String fieldName = parser.getText(); 24 parser.nextToken(); 25 if(fieldName == 'access_token'){ 26 token= parser.getText(); 27 } 28 } 29 } 30 msg = token; 31 return token; 32 }
接下來最重要的方法是bind方法,該方法將負責用戶身份驗證以及通知騰訊用戶關注成功,可以看到下面代碼里第2行到第6行只做了很簡單的用戶名密碼校驗,真實場景里可以根據企業的具體認證機制進行替換,從第9行開始也即企業內部用戶認證通過后開始調用authsucc接口通知騰訊用戶關注成功。
1 public PageReference bind() { 2 if(!strUsername.equals('user')){ 3 msg = 'Please input correct user name'; 4 } 5 else if(!strPassword.equals('123')){ 6 msg = 'Please input correct password'; 7 } 8 else{ 9 msg = 'Bind successfully!'; 10 //Notify tencent to add user 11 Http h = new Http(); 12 HttpRequest req = new HttpRequest(); 13 req.setMethod('GET'); 14 req.setHeader('Accept-Encoding','gzip,deflate'); 15 req.setHeader('Content-Type','text/xml;charset=UTF-8'); 16 req.setHeader('User-Agent','Jakarta Commons-HttpClient/3.1'); 17 req.setEndpoint('https://qyapi.weixin.qq.com/cgi-bin/user/authsucc?access_token=' + accessToken + '&userid=' + userID); 18 String bodyRes = ''; 19 try{ 20 HttpResponse res = h.send(req); 21 bodyRes = res.getBody(); 22 } 23 catch(System.CalloutException e) { 24 System.debug('Callout error: '+ e); 25 ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL, e.getMessage())); 26 } 27 msg = bodyRes ; 28 } 29 } 30













