基於Springboot的微信公眾號接入、通過網頁授權機制獲取使用者資訊
因為基於Springboot,所以有些地方需要用Spring的方式來解決,本文預設你已經搭建好Maven環境,我們將通過花生殼做內網穿透,接入公眾號並通過網頁授權機制獲取使用者基本資訊
· 獲得一個測試號,通過花生殼將內網對映在外
· 到微信公眾平臺(測試號)配置介面資訊,接入微信公眾號
· 通過網頁授權機制獲取使用者資訊
一、花生殼是一套動態域名解析服務客戶端軟體,方便、穩定,今天用它來做內網穿透
因為微信訊息傳送流程是從使用者到微信伺服器,再到你的伺服器,接收訊息後將響應訊息發到微信伺服器,其再發給使用者。這裡將自己的電腦作為一臺伺服器對映在外,方便測試
1、你需要一個測試號,它具有所有介面的使用許可權,登入即可使用:
2、註冊花生殼,做內網穿透:點我
之後進入網頁,點選新增對映,進入新增頁面:
- 注意 :微信只接受80埠,所以對映型別選擇網站80埠
我本機訪問地址為127.0.0.1:80,對映後得到外網訪問地qiangqiangchen.55555.io
啟動伺服器(我用的tomcat),即可訪問
至此,你已經獲得一個所有人都能訪問的地址了
二、到微信公眾平臺(測試號)配置介面資訊,接入微信公眾號
如圖,這裡的URL即我們剛剛對映到外網的地址,Token為隨意填寫,提交資訊後,微信伺服器將傳送GET請求到你填寫的伺服器地址URL上,GET請求攜帶四個引數,我們需要將其中三個引數做字典排序、SHA-1加密,然後拿它與另一個引數作對比,相同則證明該資訊來自於微信伺服器,即校驗通過
具體實現:新建校驗類WxPubController
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class WxPubController {
//此處TOKEN即我們剛剛所填的token
private String TOKEN = "good";
/**
* 接收並校驗四個請求引數
* @param signature
* @param timestamp
* @param nonce
* @param echostr
* @return echostr
*/
@RequestMapping(value = "/",method=RequestMethod.GET)
public String checkName(@RequestParam(name="signature")String signature,
@RequestParam(name="timestamp")String timestamp,
@RequestParam(name="nonce")String nonce,
@RequestParam(name="echostr")String echostr){
System.out.println("-----------------------開始校驗------------------------");
//排序
String sortString = sort(TOKEN, timestamp, nonce);
//加密
String myString = sha1(sortString);
//校驗
if (myString != null && myString != "" && myString.equals(signature)) {
System.out.println("簽名校驗通過");
//如果檢驗成功原樣返回echostr,微信伺服器接收到此輸出,才會確認檢驗完成。
return echostr;
} else {
System.out.println("簽名校驗失敗");
return "";
}
}
/**
* 排序方法
*/
public String sort(String token, String timestamp, String nonce) {
String[] strArray = {token, timestamp, nonce};
Arrays.sort(strArray);
StringBuilder sb = new StringBuilder();
for (String str : strArray) {
sb.append(str);
}
return sb.toString();
}
/**
* 將字串進行sha1加密
*
* @param str 需要加密的字串
* @return 加密後的內容
*/
public String sha1(String str) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(str.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
// 位元組陣列轉換為 十六進位制 數
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
}
至此,我們已經接入公眾號,接下來就可以實現具體業務了
三、 通過網頁授權機制獲取使用者資訊
網頁授權機制,開發者文件寫的很詳細,一共五個步驟:
1 :使用者同意授權,獲取code
2 :通過code換取網頁授權access_token
3 :重新整理access_token(如果需要)
4 :拉取使用者資訊(需scope為 snsapi_userinfo)
5 附:檢驗授權憑證(access_token)是否有效
在這裡,我們只需要三個步驟就可以了,即:
1 :使用者同意授權,獲取code
2 :通過code換取網頁授權access_token
3 :拉取使用者資訊(需scope為 snsapi_userinfo)
接下來,我們來實現它:
1、使用者同意授權,獲取code
1.1>>>新建UserInfoUtil,得到一個URL,這個URL是讓使用者去訪問的,當用戶進入該URL後,如果使用者同意授權,微信伺服器收到請求,頁面將跳轉至 redirect_uri/?code=CODE&state=STATE,也就是你的外網訪問網址,攜帶兩個引數,一個為code,一個為state
public class UserInfoUtil {
//獲取code的請求地址
public static String Get_Code = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=STAT#wechat_redirect";
//替換字串
public static String getCode(String APPID, String REDIRECT_URI,String SCOPE) {
return String.format(Get_Code,APPID,REDIRECT_URI,SCOPE);
}
public static void main(String[] args) {
String REDIRECT_URI = "http://qiangqiangchen.55555.io/vote.do";
String SCOPE = "snsapi_userinfo";
//appId
String appId = "wx69b8accef39ebb40";
String getCodeUrl = getCode(appId, REDIRECT_URI, SCOPE);
System.out.println("getCodeUrl:"+getCodeUrl);
}
值得注意的是,我這裡的scope=snsapi_userinfo,微信文件說的很明確,它還有另一個可選引數snsapi_base,那麼他們有什麼區別呢?
區別就是當scope=snsapi_userinfo的時候,彈出詢問授權頁面,使用者同意,才可進一步獲取使用者基本資訊,而後者,不會出現詢問頁面,但只能獲取使用者openid(使用者的唯一標識)
好了,接下來,我們需要讓使用者去訪問這個URL,那麼,該怎麼實現呢?
分兩步:
首先,需要到自己的微信公眾號,到介面許可權表裡找到網頁賬號,並修改之,如圖:
說明一下,這裡的域名即我們通過花生殼映射出去的全域名,比如你的域名是aaaaa.cn,那麼aaaaa.cn/bbbb/cccc也是可以彈出許可權詢問頁面的
其次,我們需要引導使用者通過getCodeUrl來進入我們的首頁,所以,我們要將此URL加到微信選單上去
這裡所說選單,指的是公眾號最下邊的那一列———
至於怎麼加進去,可以用程式碼方式,也可以直接設定,但目前測試號沒有選單設定功能,如果需要程式碼設定,請點選百度這裡不做介紹
1.2>>>獲取code
這裡說明一下:code作為換取access_token的票據,每次使用者授權帶上的code將不一樣,code只能使用一次,5分鐘未被使用自動過期。
微信伺服器收到使用者請求,
@Controller
public class IndexController extends BaseController{
@RequestMapping("/vote.do")
public ModelAndView listVote(@RequestParam(name="code",required=false)String code,
@RequestParam(name="state")String state) {
System.out.println("-----------------------------收到請求,請求資料為:"+code+"-----------------------"+state);
//……………………業務程式碼,此處省略
return new ModelAndView("mypages/index", model);
}
}
2、通過code換取網頁授權access_token
2.1>>>新建UserInfoUtil
public class UserInfoUtil {
//獲取code的請求地址
public static String Get_Code = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=STAT#wechat_redirect";
//替換字串
public static String getCode(String APPID, String REDIRECT_URI,String SCOPE) {
return String.format(Get_Code,APPID,REDIRECT_URI,SCOPE);
}
//獲取Web_access_tokenhttps的請求地址
public static String Web_access_tokenhttps = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
//替換字串
public static String getWebAccess(String APPID, String SECRET,String CODE) {
return String.format(Web_access_tokenhttps, APPID, SECRET,CODE);
}
}
2.2>>>因為需要用https的方式請求微信伺服器,我們還需要一個以https方式傳送請求的工具類:
package com.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class HttpsUtil {
/**
* 以https方式傳送請求並將請求響應內容以String方式返回
*
* @param path 請求路徑
* @param method 請求方法
* @param body 請求資料體
* @return 請求響應內容轉換成字串資訊
*/
public static String httpsRequestToString(String path, String method, String body) {
if (path == null || method == null) {
return null;
}
String response = null;
InputStream inputStream = null;
InputStreamReader inputStreamReader = null;
BufferedReader bufferedReader = null;
HttpsURLConnection conn = null;
try {
//建立SSLConrext物件,並使用我們指定的信任管理器初始化
TrustManager[] tm = {new JEEWeiXinX509TrustManager()};
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
//從上述物件中的到SSLSocketFactory
SSLSocketFactory ssf = sslContext.getSocketFactory();
System.out.println(path);
URL url = new URL(path);
conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(ssf);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
//設定請求方式(git|post)
conn.setRequestMethod(method);
//有資料提交時
if (null != body) {
OutputStream outputStream = conn.getOutputStream();
outputStream.write(body.getBytes("UTF-8"));
outputStream.close();
}
//將返回的輸入流轉換成字串
inputStream = conn.getInputStream();
inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
response = buffer.toString();
} catch (Exception e) {
} finally {
if (conn != null) {
conn.disconnect();
}
try {
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
} catch (IOException execption) {
}
}
return response;
}
}
class JEEWeiXinX509TrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
2.3>>>接下來,換取access_token,需要說明一下,這裡的access_token和基礎access_token並不相同,此處指的是專門在網頁中使用的access_token,我們暫且稱之為web_access_token
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.entity.oDCon;
import com.entity.vOpt;
import com.entity.vPro;
import com.entity.voDet;
import com.github.pagehelper.PageInfo;
import com.service.VoteOpService;
import com.util.Const;
import com.util.HttpsUtil;
import com.util.UserInfoUtil;
@Controller
public class IndexController extends BaseController{
@RequestMapping("/vote.do")
public ModelAndView listVote(@RequestParam(name="code",required=false)String code,
@RequestParam(name="state")String state) {
System.out.println("-----------------------------收到請求,請求資料為:"+code+"-----------------------"+state);
//通過code換取網頁授權web_access_token
if(code != null || !(code.equals(""))){
String APPID = Const.appId;
String SECRET = Const.appSecret;
String CODE = code;
String WebAccessToken = "";
String openId = "";
//String nickName,sex,openid = "";
String REDIRECT_URI = "http://qiangqiangchen.55555.io/vote.do";
String SCOPE = "snsapi_userinfo";
String getCodeUrl = UserInfoUtil.getCode(APPID, REDIRECT_URI, SCOPE);
System.out.println("---------------getCodeUrl--------------"+getCodeUrl);
//替換字串,獲得請求URL
String token = UserInfoUtil.getWebAccess(APPID, SECRET, CODE);
System.out.println("----------------------------token為:"+token);
//通過https方式請求獲得web_access_token
String response = HttpsUtil.httpsRequestToString(token, "GET", null);
JSONObject jsonObject = JSON.parseObject(response);
System.out.println("jsonObject------"+jsonObject);
if (null != jsonObject) {
try {
WebAccessToken = jsonObject.getString("access_token");
openId = jsonObject.getString("openid");
System.out.println("獲取access_token成功-------------------------"+WebAccessToken+"----------------"+openId);
} catch (JSONException e) {
WebAccessToken = null;// 獲取code失敗
System.out.println("獲取WebAccessToken失敗");
}
}
}
//此處業務程式碼省略 ^_^
return new ModelAndView("mypages/index", model);
}
}
至此,我們拿到WebAccessToken、openId,就算完成這一步驟了。
3 :拉取使用者資訊
3.1>>>在UserInfoUtil中加入:
//拉取使用者資訊的請求地址
public static String User_Message = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN";
//替換字串
public static String getUserMessage(String access_token, String openid) {
return String.format(User_Message, access_token,openid);
}
3.2>>>在IndexController中加入拉取使用者資訊的程式碼
@Controller
public class IndexController extends BaseController{
//required=false的意思就是可以不傳此引數
@RequestMapping("/vote.do")
public ModelAndView listVote(@RequestParam(name="code",required=false)String code,
@RequestParam(name="state")String state) {
System.out.println("-----------------------------收到請求,請求資料為:"+code+"-----------------------"+state);
//通過code換取網頁授權web_access_token
if(code != null || !(code.equals(""))){
String APPID = Const.appId;
String SECRET = Const.appSecret;
String CODE = code;
String WebAccessToken = "";
String openId = "";
String nickName,sex,openid = "";
String REDIRECT_URI = "http://qiangqiangchen.55555.io/vote.do";
String SCOPE = "snsapi_userinfo";
String getCodeUrl = UserInfoUtil.getCode(APPID, REDIRECT_URI, SCOPE);
System.out.println("---------------getCodeUrl--------------"+getCodeUrl);
//替換字串,獲得請求URL
String token = UserInfoUtil.getWebAccess(APPID, SECRET, CODE);
System.out.println("----------------------------token為:"+token);
//通過https方式請求獲得web_access_token
String response = HttpsUtil.httpsRequestToString(token, "GET", null);
JSONObject jsonObject = JSON.parseObject(response);
System.out.println("jsonObject------"+jsonObject);
if (null != jsonObject) {
try {
WebAccessToken = jsonObject.getString("access_token");
openId = jsonObject.getString("openid");
System.out.println("獲取access_token成功-------------------------"+WebAccessToken+"----------------"+openId);
//-----------------------拉取使用者資訊...替換字串,獲得請求URL
String userMessage = UserInfoUtil.getUserMessage(WebAccessToken, openId);
System.out.println(" userMessage==="+ userMessage);
//通過https方式請求獲得使用者資訊響應
String userMessageResponse = HttpsUtil.httpsRequestToString(userMessage, "GET", null);
JSONObject userMessageJsonObject = JSON.parseObject(userMessageResponse);
System.out.println("userMessagejsonObject------"+userMessageJsonObject);
if (userMessageJsonObject != null) {
try {
//使用者暱稱
nickName = userMessageJsonObject.getString("nickname");
//使用者性別
sex = userMessageJsonObject.getString("sex");
sex = (sex.equals("1")) ? "男":"女";
//使用者唯一標識
openid = userMessageJsonObject.getString("openid");
System.out.println("使用者暱稱------------------------"+nickName);
System.out.println("使用者性別------------------------"+sex);
System.out.println("使用者的唯一標識-------------------"+openid);
} catch (JSONException e) {
System.out.println("獲取userName失敗");
}
}
} catch (JSONException e) {
WebAccessToken = null;// 獲取code失敗
System.out.println("獲取WebAccessToken失敗");
}
}
}
return new ModelAndView("mypages/index", model);
}
}
當啟動tomcat後,在手機上開啟微信公眾號,點選公眾號選單,訪問頁面,獲得使用者基本資訊的json資料
至此,最初目的已經實現,謝謝你的耐心閱讀,大神多批評~本文部分程式碼參考大神孤傲蒼狼所寫: 微信開發學習總結