接入概述
接入微信公衆平臺開發,開發者需要按照如下步驟完成:
1、填寫服務器配置
2、驗證服務器地址的有效性
3、依據接口文檔實現業務邏輯
下面詳細介紹這3個步驟。
第一步:填寫服務器配置
登錄微信公衆平臺官網後,在公衆平臺官網的開發-基本設置頁面,勾選協議成爲開發者,點擊“修改配置”按鈕,填寫服務器地址(URL)、Token和EncodingAESKey,其中URL是開發者用來接收微信消息和事件的接口URL。Token可由開發者可以任意填寫,用作生成簽名(該Token會和接口URL中包含的Token進行比對,從而驗證安全性)。EncodingAESKey由開發者手動填寫或隨機生成,將用作消息體加解密密鑰。
同時,開發者可選擇消息加解密方式:明文模式、兼容模式和安全模式。模式的選擇與服務器配置在提交後都會立即生效,請開發者謹慎填寫及選擇。加解密方式的默認狀態爲明文模式,選擇兼容模式和安全模式需要提前配置好相關加解密代碼,詳情請參考消息體簽名及加解密部分的文檔 。
第二步:驗證消息的確來自微信服務器
開發者提交信息後,微信服務器將發送GET請求到填寫的服務器地址URL上,GET請求攜帶參數如下表所示:
參數 | 描述 |
---|---|
signature | 微信加密簽名,signature結合了開發者填寫的token參數和請求中的timestamp參數、nonce參數。 |
timestamp | 時間戳 |
nonce | 隨機數 |
echostr | 隨機字符串 |
開發者通過檢驗signature對請求進行校驗(下面有校驗方式)。若確認此次GET請求來自微信服務器,請原樣返回echostr參數內容,則接入生效,成爲開發者成功,否則接入失敗。加密/校驗流程如下:
1)將token、timestamp、nonce三個參數進行字典序排序 2)將三個參數字符串拼接成一個字符串進行sha1加密 3)開發者獲得加密後的字符串可與signature對比,標識該請求來源於微信
這是一個servlet文件,用來處理微信發過來的認證請求;
package myservlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class MyServlet
*/
public class MyServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public MyServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter pw = response.getWriter();
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
boolean isSuccess = CheckUtil.check(signature, timestamp, nonce);
if(isSuccess) {
pw.print(echostr);
pw.write("接入成功!");
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
驗證方法
public class CheckUtil {
public static final String token ="##";
public static boolean check(String signature,String timestamp,String nonce ) {
String arrs[] = {token,timestamp,nonce};
Arrays.sort(arrs);//字典排序
//拼接字符串
StringBuffer sb = new StringBuffer();
for(String str :arrs) {
sb.append(str);
}
String signaturesha1 = Sha1Util.encode(sb.toString());
return signaturesha1.equals(signature);
}
}
sha1加密
package myservlet;
import java.security.MessageDigest;
public final class Sha1Util {
private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
'e', 'f' };
/**
*
* @Title: getFormattedText
* @Description: TODO(這裏用一句話描述這個方法的作用)
* @param @param bytes
* @param @return 參數
* @return String 返回類型
* @throws
*/
private static String getFormattedText(byte[] bytes) {
int len = bytes.length;
StringBuilder buf = new StringBuilder(len * 2);
// 把密文轉換成十六進制的字符串形式
for (int j = 0; j < len; j++) {
buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
}
return buf.toString();
}
/**
*
* @Title: encode
* @Description: TODO(這裏用一句話描述這個方法的作用)
* @param @param str
* @param @return 參數
* @return String 返回類型
* @throws
*/
public static String encode(String str) {
if (str == null) {
return null;
}
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
messageDigest.update(str.getBytes());
return getFormattedText(messageDigest.digest());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<servlet>
<!--servlet名稱,可以自定義-->
<servlet-name>MyServlet</servlet-name>
<!-- servlet類名: 包名+簡單類名-->
<servlet-class>myservlet.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<!--servlet名稱,應與上面的名稱保持一致,因爲是通過下面的servlet訪問名稱來定位到上面的servlet名稱,再通過上面的名稱定位到servlet類的位置-->
<servlet-name>MyServlet</servlet-name>
<!-- servlet的訪問名稱: /名稱 -->
<url-pattern>/helloMyServlet.do</url-pattern>
</servlet-mapping>
</web-app>
原理
- 將token,timestamp,nonce進行字典排序,拼接成一個字符串。
- 將字符串進行sha1加密。
- 將加密後的字符創和signature比較。true則提交成功,請原樣返回echostr參數內容;false則爲失敗。
獲取Access token
access_token是公衆號的全局唯一接口調用憑據,公衆號調用各接口時都需使用access_token。開發者需要進行妥善保存。access_token的存儲至少要保留512個字符空間。access_token的有效期目前爲2個小時,需定時刷新,重複獲取將導致上次獲取的access_token失效。
公衆平臺的API調用所需的access_token的使用及生成方式說明:
1、建議公衆號開發者使用中控服務器統一獲取和刷新access_token,其他業務邏輯服務器所使用的access_token均來自於該中控服務器,不應該各自去刷新,否則容易造成衝突,導致access_token覆蓋而影響業務;
2、目前access_token的有效期通過返回的expire_in來傳達,目前是7200秒之內的值。中控服務器需要根據這個有效時間提前去刷新新access_token。在刷新過程中,中控服務器可對外繼續輸出的老access_token,此時公衆平臺後臺會保證在5分鐘內,新老access_token都可用,這保證了第三方業務的平滑過渡;
3、access_token的有效時間可能會在未來有調整,所以中控服務器不僅需要內部定時主動刷新,還需要提供被動刷新access_token的接口,這樣便於業務服務器在API調用獲知access_token已超時的情況下,可以觸發access_token的刷新流程。
4、對於可能存在風險的調用,在開發者進行獲取 access_token調用時進入風險調用確認流程,需要用戶管理員確認後纔可以成功獲取。具體流程爲:
開發者通過某IP發起調用->平臺返回錯誤碼[89503]並同時下發模板消息給公衆號管理員->公衆號管理員確認該IP可以調用->開發者使用該IP再次發起調用->調用成功。
如公衆號管理員第一次拒絕該IP調用,用戶在1個小時內將無法使用該IP再次發起調用,如公衆號管理員多次拒絕該IP調用,該IP將可能長期無法發起調用。平臺建議開發者在發起調用前主動與管理員溝通確認調用需求,或請求管理員開啓IP白名單功能並將該IP加入IP白名單列表。
公衆號和小程序均可以使用AppID和AppSecret調用本接口來獲取access_token。AppID和AppSecret可在“微信公衆平臺-開發-基本配置”頁中獲得(需要已經成爲開發者,且帳號沒有異常狀態)。**調用接口時,請登錄“微信公衆平臺-開發-基本配置”提前將服務器IP地址添加到IP白名單中,點擊查看設置方法,否則將無法調用成功。**小程序無需配置IP白名單。
接口調用請求說明
https請求方式: GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
參數說明
參數 | 是否必須 | 說明 |
---|---|---|
grant_type | 是 | 獲取access_token填寫client_credential |
appid | 是 | 第三方用戶唯一憑證 |
secret | 是 | 第三方用戶唯一憑證密鑰,即appsecret |
返回說明
正常情況下,微信會返回下述JSON數據包給公衆號:
{"access_token":"ACCESS_TOKEN","expires_in":7200}
參數說明
參數 | 說明 |
---|---|
access_token | 獲取到的憑證 |
expires_in | 憑證有效時間,單位:秒 |
錯誤時微信會返回錯誤碼等信息,JSON數據包示例如下(該示例爲AppID無效錯誤):
{"errcode":40013,"errmsg":"invalid appid"}