1. 程式人生 > >基於Springboot的微信公眾號接入、通過網頁授權機制獲取使用者資訊

基於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資料

使用者基本資訊

至此,最初目的已經實現,謝謝你的耐心閱讀,大神多批評~本文部分程式碼參考大神孤傲蒼狼所寫: 微信開發學習總結