微信公众号扫码登录(一)—— 获取微信公众号二维码
引言
這幾天在研究微信登錄,今天解決了獲取微信二維碼問題;在這里總結一下
關于微信登錄想說的話
第一次接觸微信登錄,開始就弄混了登錄方式;簡單來說,微信掃碼登錄分為兩種,一種是微信公眾平臺,一種是微信開放平臺,兩者的開發文檔也不一樣,開始就是一直用的微信公眾號的參數卻使用的是微信開放平臺提供的接口,找了半天問題;
總結一下:
微信公眾號(公眾平臺) 和 微信開放平臺 是兩碼事。
- 微信公眾平臺是掃碼通過微信公眾號授權登錄的,個人用戶可以申請訂閱號,但是沒有一些接口調用權限,企業用戶可以申請服務號,有許多接口權限;但是個人用戶可以通過測試號獲取一些權限進行學習測試;
- 微信開放平臺是微信為了接入更多第三方應用而開放的接口,對于web應用,可以申請web應用,從而獲取權限,但是只能是企業用戶才能申請;(個人學習很不方便)
兩者開發文檔是不同的,所以看網上教程一定要看清楚是公眾平臺還是開放平臺,不要跟錯教程了;最好就是看官方文檔,這樣就能避免踩坑;(我開始就是看不下去文檔,因為微信是php示例代碼,所以一直看網上教程,就被各種各樣的教程弄暈了;最后還是乖乖看文檔去了)
下面就進入正題
準備環境
首先申請了一個公眾號,然后從開發者工具中進入測試號:
然后就是這個界面:
appID和appsecret都是操作所需參數;
接口配置下面介紹;
接口配置
接口配置后面會用到,所以需要先配置一下,其實看官方文檔就能看懂,但是示例代碼是php,所以這里我來演示一下我的操作;
官方文檔:傳送門
官方文檔意思就是你得有一個域名,代碼還得跑在該域名下的服務器上,然后你自己需要再代碼中實現一個接口獲取微信發來的信息進行處理;
如果你和我一樣是個學生,開發都在本地127.0.0.1,或者沒有域名,那該怎么辦?因為微信不能直接調用本地ip,這就需要用到內網穿透;
簡單來說就是微信想要向你填入的url發送請求數據,但是它不能直接向本地127.0.0.1發送,我們可以通過內網穿透獲取一個域名,讓該域名映射到本地127.0.0.1,然后微信向該域名發送數據,這樣就把數據發送到了本地;(個人理解)
內網穿透
我通過ngrok進行的內網穿透,就花2塊錢實名了一下,然后有一個免費的隧道可以使用:
然后進行配置,它就會給你分一個域名:
接下來下載ngrok客戶端,啟動隧道:
點擊.bat文件,按要求輸入隧道id:
鏈接成功,這時時就意味著訪問本地127.0.0.1和訪問生成的域名的效果是一樣的;
訪問流程就是:
文檔:
微信向自己填的url發送請求;
實現接入代碼
然后就是代碼配置,就是官網文檔的第二步,官方檢驗是一個php代碼,下面是java代碼:
controller接口:
/*** 接入微信接口*/ @GetMapping("/callback") @ResponseBody public String checkSign (HttpServletRequest request) throws Exception {log.info("===========>checkSign");// 獲取微信請求參數String signature = request.getParameter ("signature");String timestamp = request.getParameter ("timestamp");String nonce = request.getParameter ("nonce");String echostr = request.getParameter ("echostr");log.info("開始校驗此次消息是否來自微信服務器,param->signature:{},\ntimestamp:{},\nnonce:{},\nechostr:{}",signature, timestamp, nonce, echostr);if (CheckWXTokenUtils.checkSignature(signature, timestamp, nonce)) {return echostr;}return ""; }校驗工具類(直接cv)
import lombok.extern.log4j.Log4j2;import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays;/*** 和微信建立鏈接參數校驗*/ @Log4j2 public class CheckWXTokenUtils {private static final String TOKEN = "xxxxxx"; // 自定義的token/*** 校驗微信服務器Token簽名** @param signature 微信加密簽名* @param timestamp 時間戳* @param nonce 隨機數* @return boolean*/public static boolean checkSignature(String signature, String timestamp, String nonce) {String[] arr = {TOKEN, timestamp, nonce};Arrays.sort(arr);StringBuilder stringBuilder = new StringBuilder();for (String param : arr) {stringBuilder.append(param);}String hexString = SHA1(stringBuilder.toString());return signature.equals(hexString);}private static String SHA1(String str) {MessageDigest md;try {md = MessageDigest.getInstance("SHA-1");byte[] digest = md.digest(str.getBytes());return toHexString(digest);} catch (NoSuchAlgorithmException e) {log.info("校驗令牌Token出現錯誤:{}", e.getMessage());}return "";}/*** 字節數組轉化為十六進制** @param digest 字節數組* @return String*/private static String toHexString(byte[] digest) {StringBuilder hexString = new StringBuilder();for (byte b : digest) {String shaHex = Integer.toHexString(b & 0xff);if (shaHex.length() < 2) {hexString.append(0);}hexString.append(shaHex);}return hexString.toString();} }幾個校驗參數官方文檔也說了,自己對比著看就行了;
再次放入官方文檔地址:接入概述
配置就是:
url是內網穿透域名+自己實現的接口
token也是自己寫的;
然后在網頁服務中:
點擊修改:(還是內網穿透域名)
然后就配置好了;
下面就是正式開始二維碼生成了;
生成二維碼
先放出文檔:
生成帶參數的二維碼
獲取Access token
獲取ticket
我們先看第一個文檔,大致意思就是先發送一個獲取二維碼ticket的post請求,獲取ticket:
整理一下:
url: post https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN參數: param參數:access_token json參數(兩個必要的,其他可以自己看文檔加): {"expire_seconds": 604800, "action_name": "QR_SCENE"}結果: {"ticket":"gQH47joAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL2taZ2Z3TVRtNzJXV1Brb3ZhYmJJAAIEZ23sUwMEmm 3sUw==","expire_seconds":60,"url":"http://weixin.qq.com/q/kZgfwMTm72WWPkovabbI"}那么就很簡單了,就是調用就接口獲取ticket,但是可以發現所需要的參數中的access_token我們并沒有,所以調用該接口前需要先獲取,同樣也有官方文檔,上面已經放出來了;
獲取access_token
同樣整理一下:
url: get https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET參數: grant_type、appid、secret結果: {"access_token":"ACCESS_TOKEN","expires_in":7200}其中grant_type就寫client_credential就行了,固定的;
appid和secret就是測試號的:
所以很輕易就可以獲取到access_token;
獲取到access_token就可以獲取ticket,到這里這兩步就完成了;
接下來就是獲取二維碼了:
獲取二維碼
這就不過多介紹了,就加一個ticket參數即可;
測試流程
下面我使用postman測試一遍流程:
1,首先獲取access_token:
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=&secret=
2,然后獲取ticket:
https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=ACCESS_TOKEN
3,最后獲取二維碼:
https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET
直接瀏覽器訪問該鏈接,即可看到該二維碼
整個流程就走通了;
下面就是代碼實現該流程;
代碼實現
這里思考一個問題,后端內部如何自己發送請求獲取響應?
平時都是前端向后端發送請求,后端響應;
因為我們需要先發送請求獲取access_token,再獲取ticket,最后響應給前端的就是一個二維碼url,所以后端需要自己發請求并獲取響應結果;
這里可以使用httpclient,具體細節可以查資料,這里不過多介紹;
下面是實現代碼:
httpclient依賴:
<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.13</version> </dependency>httpclient工具類(直接cv):
import org.apache.http.HttpEntity; import org.apache.http.NameValuePair; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.utils.URIBuilder; import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.ssl.SSLContexts; import org.apache.http.util.EntityUtils;import javax.net.ssl.SSLContext; import java.io.File; import java.io.FileInputStream; import java.nio.charset.StandardCharsets; import java.security.KeyStore; import java.util.ArrayList; import java.util.List; import java.util.Map;/*** HttpClient工具類*/ public class HttpClientUtils {private static final CloseableHttpClient httpClient;// 采用靜態代碼塊,初始化超時時間配置,再根據配置生成默認httpClient對象static {RequestConfig config = RequestConfig.custom().setConnectTimeout(30000).setSocketTimeout(15000).build();httpClient = HttpClientBuilder.create().setDefaultRequestConfig(config).build();}/*** 發送 HTTP GET請求,不帶請求參數和請求頭* @param url 請求地址* @return* @throws Exception*/public static String doGet(String url) throws Exception {HttpGet httpGet = new HttpGet(url);return doHttp(httpGet);}/*** 發送 HTTP GET,請求帶參數,不帶請求頭* @param url 請求地址* @param params 請求參數* @return* @throws Exception*/public static String doGet(String url, Map<String, Object> params) throws Exception {// 轉換請求參數List<NameValuePair> pairs = covertParamsToList(params);// 裝載請求地址和參數URIBuilder ub = new URIBuilder();ub.setPath(url);ub.setParameters(pairs);HttpGet httpGet = new HttpGet(ub.build());return doHttp(httpGet);}/*** 發送 HTTP GET請求,帶請求參數和請求頭* @param url 請求地址* @param headers 請求頭* @param params 請求參數* @return* @throws Exception*/public static String doGet(String url, Map<String, Object> headers, Map<String, Object> params) throws Exception {// 轉換請求參數List<NameValuePair> pairs = covertParamsToList(params);// 裝載請求地址和參數URIBuilder ub = new URIBuilder();ub.setPath(url);ub.setParameters(pairs);HttpGet httpGet = new HttpGet(ub.build());// 設置請求頭for (Map.Entry<String, Object> param : headers.entrySet()) {httpGet.addHeader(param.getKey(), String.valueOf(param.getValue()));}return doHttp(httpGet);}/*** 發送 HTTP POST請求,不帶請求參數和請求頭** @param url 請求地址* @return* @throws Exception*/public static String doPost(String url) throws Exception {HttpPost httpPost = new HttpPost(url);return doHttp(httpPost);}/*** 發送 HTTP POST請求,帶請求參數,不帶請求頭** @param url 請求地址* @param params 請求參數* @return* @throws Exception*/public static String doPost(String url, Map<String, Object> params) throws Exception {// 轉換請求參數List<NameValuePair> pairs = covertParamsToList(params);HttpPost httpPost = new HttpPost(url);// 設置請求參數httpPost.setEntity(new UrlEncodedFormEntity(pairs, StandardCharsets.UTF_8.name()));return doHttp(httpPost);}/*** 發送 HTTP POST請求,帶請求參數和請求頭** @param url 地址* @param headers 請求頭* @param params 參數* @return* @throws Exception*/public static String doPost(String url, Map<String, Object> headers, Map<String, Object> params) throws Exception {// 轉換請求參數List<NameValuePair> pairs = covertParamsToList(params);HttpPost httpPost = new HttpPost(url);// 設置請求參數httpPost.setEntity(new UrlEncodedFormEntity(pairs, StandardCharsets.UTF_8.name()));// 設置請求頭for (Map.Entry<String, Object> param : headers.entrySet()) {httpPost.addHeader(param.getKey(), String.valueOf(param.getValue()));}return doHttp(httpPost);}/*** 發送 HTTP POST請求,請求參數是JSON格式,數據編碼是UTF-8** @param url 請求地址* @param param 請求參數* @return* @throws Exception*/public static String doPostJson(String url, String param) throws Exception {HttpPost httpPost = new HttpPost(url);// 設置請求頭httpPost.addHeader("Content-Type", "application/json; charset=UTF-8");// 設置請求參數httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name()));return doHttp(httpPost);}/*** 發送 HTTP POST請求,請求參數是XML格式,數據編碼是UTF-8** @param url 請求地址* @param param 請求參數* @return* @throws Exception*/public static String doPostXml(String url, String param) throws Exception {HttpPost httpPost = new HttpPost(url);// 設置請求頭httpPost.addHeader("Content-Type", "application/xml; charset=UTF-8");// 設置請求參數httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name()));return doHttp(httpPost);}/*** 發送 HTTPS POST請求,使用指定的證書文件及密碼,不帶請求頭信息<** @param url 請求地址* @param param 請求參數* @param path 證書全路徑* @param password 證書密碼* @return* @throws Exception* @throws Exception*/public static String doHttpsPost(String url, String param, String path, String password) throws Exception {HttpPost httpPost = new HttpPost(url);// 設置請求參數httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name()));return doHttps(httpPost, path, password);}/*** 發送 HTTPS POST請求,使用指定的證書文件及密碼,請求頭為“application/xml;charset=UTF-8”** @param url 請求地址* @param param 請求參數* @param path 證書全路徑* @param password 證書密碼* @return* @throws Exception* @throws Exception*/public static String doHttpsPostXml(String url, String param, String path, String password) throws Exception {HttpPost httpPost = new HttpPost(url);// 設置請求頭httpPost.addHeader("Content-Type", "application/xml; charset=UTF-8");// 設置請求參數httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name()));return doHttps(httpPost, path, password);}/*** 發送 HTTPS 請求,使用指定的證書文件及密碼** @param request* @param path 證書全路徑* @param password 證書密碼* @return* @throws Exception* @throws Exception*/private static String doHttps(HttpRequestBase request, String path, String password) throws Exception {// 獲取HTTPS SSL證書SSLConnectionSocketFactory csf = getHttpsFactory(path, password);// 通過連接池獲取連接對象CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();return doRequest(httpClient, request);}/*** 獲取HTTPS SSL連接工廠,使用指定的證書文件及密碼** @param path 證書全路徑* @param password 證書密碼* @return* @throws Exception* @throws Exception*/private static SSLConnectionSocketFactory getHttpsFactory(String path, String password) throws Exception {// 初始化證書,指定證書類型為“PKCS12”KeyStore keyStore = KeyStore.getInstance("PKCS12");// 讀取指定路徑的證書FileInputStream input = new FileInputStream(new File(path));try {// 裝載讀取到的證書,并指定證書密碼keyStore.load(input, password.toCharArray());} finally {input.close();}// 獲取HTTPS SSL證書連接上下文SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keyStore, password.toCharArray()).build();// 獲取HTTPS連接工廠,指定TSL版本SSLConnectionSocketFactory sslCsf = new SSLConnectionSocketFactory(sslContext, new String[]{"SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.2"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());return sslCsf;}/*** 發送 HTTP 請求** @param request* @return* @throws Exception*/private static String doHttp(HttpRequestBase request) throws Exception {// 通過連接池獲取連接對象return doRequest(httpClient, request);}/*** 處理Http/Https請求,并返回請求結果,默認請求編碼方式:UTF-8* @param httpClient* @param request* @return*/private static String doRequest(CloseableHttpClient httpClient, HttpRequestBase request) throws Exception {String result = null;try (CloseableHttpResponse response = httpClient.execute(request)) {// 獲取請求結果int statusCode = response.getStatusLine().getStatusCode();if (statusCode != 200) {request.abort();throw new RuntimeException("HttpClient error status code: " + statusCode);}// 解析請求結果HttpEntity entity = response.getEntity();// 轉換結果result = EntityUtils.toString(entity, StandardCharsets.UTF_8.name());// 關閉IO流EntityUtils.consume(entity);}return result;}/*** 轉換請求參數,將Map鍵值對拼接成QueryString字符串** @param params* @return*/public static String covertMapToQueryStr(Map<String, Object> params) {List<NameValuePair> pairs = covertParamsToList(params);return URLEncodedUtils.format(pairs, StandardCharsets.UTF_8.name());}/*** 轉換請求參數** @param params* @return*/public static List<NameValuePair> covertParamsToList(Map<String, Object> params) {List<NameValuePair> pairs = new ArrayList<>();for (Map.Entry<String, Object> param : params.entrySet()) {pairs.add(new BasicNameValuePair(param.getKey(), String.valueOf(param.getValue())));}return pairs;} }然后controller實現一個接口,前端調用該接口獲取二維碼url:
// 獲取二維碼 @GetMapping("/qr/login/param") @ResponseBody public BaseResponse<String> getWxQRCodeParam() {String QRUrl = null;try {// 第一步:發送請求獲取access_tokenString getAccessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" +"&appid=" + WXConstant.APP_ID +"&secret=" + WXConstant.APP_SECRET;String accessTokenRes = HttpClientUtils.doGet(getAccessTokenUrl);log.info("accessTokenRes=>" + accessTokenRes);String accessToken = (String) JSON.parseObject(accessTokenRes).get("access_token"); // 獲取到access_token// 第二步:通過access_token和一些參數發送post請求獲取二維碼TicketString getTicketUrl = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + accessToken;// 封裝參數Map<String, Object> ticketInfo = new HashMap<>();ticketInfo.put("expire_seconds", 604800); // 二維碼超時時間ticketInfo.put("action_name", "QR_SCENE");String ticketJsonInfo = JSON.toJSON(ticketInfo).toString();String ticketRes = HttpClientUtils.doPostJson(getTicketUrl, ticketJsonInfo);log.info("ticketRes=>" + ticketRes);String ticket = (String) JSON.parseObject(ticketRes).get("ticket");// 第三步:通過ticket獲取二維碼urlString encodeTicket = URLEncoder.encode(ticket, "utf-8"); // 編碼ticketString getQRUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + encodeTicket;QRUrl = getQRUrl; // 二維碼url} catch (Exception e) {e.printStackTrace();}return ResultUtils.success(QRUrl); }注:該接口只注重了功能實現,具體安全性并未考慮;
可以調用該接口測試一下:
那么前端只需要獲取到這個url并展示出來就行了:
可以測試一下:
可以看到每次都能獲取到不同二維碼;
到這里二維碼生成就完成了,可以掃碼測試一下:
掃碼候就是公眾號界面了,接下來就是一些授權操作了,下集在講;
總結
總的來說只要細心看文檔還不算是特別難理解的,所以一定要多看文檔!
總的來說流程就是那三步:
因為文章是本人理解記錄的,可能會有錯誤,有錯誤問題歡迎交流!
總結
以上是生活随笔為你收集整理的微信公众号扫码登录(一)—— 获取微信公众号二维码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 工具链接
- 下一篇: 如何利用脚本方法清除文本框中汉字,从而保