1. 程式人生 > >微信開發一--網頁授權

微信開發一--網頁授權

功能:

主要用於在使用者通過手機端微信訪問第三方H5頁面時獲取使用者的身份資訊(openId,暱稱,頭像,所在地等。。)可用來實現微信登入、微信賬號繫結、使用者身份鑑權等功能。
 

一、開發前的準備:

1、需要有一個公眾號,拿到AppID和AppSecret;
注意:AppID和AppSecret在以前的版本中是可以直接顯示的,但改版之後AppID能夠直接看到而AppSecret則無法得知。所以這個AppSecret得保管好了,要不然只能重置了(重置可能會影響你的線上業務和以前程式的正常執行,重置成功後會在頁面中顯示你一定要儲存好了,這個頁面關了之後就無法得知了)
在這裡插入圖片描述


在這裡插入圖片描述
2、將你要部署下面程式碼程式電腦的ip(在瀏覽器百度搜索"ip"即可知道你自己的ip)新增到白名單中,否則無法獲取到access_token。
3、進入公眾號開發者中心頁配置授權回撥域名。具體位置:設定-公眾號設定-功能設定-網頁授權域名
注意:這裡僅需填寫全域名(如 www.qq.comwww.baidu.com),勿加 http:// 等協議頭及具體的地址欄位
這個域名需要是一個備案過的域名。這個條件比較難辦,幸好熱心的網友qydev為我們無私地提供了一個備案過的域名,我們可以通過使用Ngrok來虛擬一個域名對映到本地開發環境,簡直是web開發神器啊。。
qydev版Ngrok使用說明及下載地址:
https://blog.csdn.net/qq_26101151/article/details/53114496?locationNum=4&fps=1

啟動命令:ngrok -config=ngrok.cfg -subdomain xiaoqiang 8080
本文以xiaoqiang.tunnel.qydev.com域名為例
在這裡插入圖片描述
在這裡插入圖片描述
番外:我也是服了,前幾天還好使,今天就不行了。過了幾天又好使了,這麼不穩定啊。。。
在這裡插入圖片描述
注意:網上有好多都是通過使用Ngrok來虛擬一個域名對映到本地開發環境,可是Ngrok有好多版本,只有上面的這個qydev版的lovebread.tunnel.qydev.com域名能成功。

(1)官方版本失敗
在這裡插入圖片描述
在這裡插入圖片描述
(2)Sunny版本,配置了個免費的域名http://suliuu.free.idcfengye.com
在這裡插入圖片描述
在這裡插入圖片描述
這兩個版本的Ngrok簡單使用可參考:https://blog.csdn.net/liu_005/article/details/79557818和https://blog.csdn.net/qq_33404395/article/details/80788233

注意:
1.改版之後還得把它規定的一個txt檔案放到你域名根目錄下,所以說這步先別管,先進行下面的內容(在eclipse中把整個專案搭建好後再把這個txt檔案下載後放到你的專案中去,否則這步根本通過不了)
在這裡插入圖片描述
2.把上面說的TXT檔案放到下圖的位置,如果你訪問到http://xiaoqiang.tunnel.qydev.com/MP_verify_xxxxxxxxxxxxxxxx.txt則說明你放對了
在這裡插入圖片描述
4.在mysql庫中建相應的表並插入資料:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `account` char(10) DEFAULT NULL,
  `openid` char(50) DEFAULT NULL,
  `password` char(10) DEFAULT NULL,
  `nickname` char(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk;

INSERT INTO user VALUES(1,'xiaoqiang','','123456','小強簽名設計');

5.如果嫌手機上測試麻煩,可以使用微信官方提供的web開發者工具直接在瀏覽器中進行除錯。
前提是需要在微信公眾號中繫結開發者賬號:登入公眾號-人員設定-繫結運營者微訊號
使用說明及下載地址:https://mp.weixin.qq.com/wiki?action=doc&id=mp1455784140&t=0.7272727088156665&token=&lang=zh_CN#6
 

二、專案構建:

1.專案結構:
在eclipse中右鍵新建“Dynamic Web Project”
在這裡插入圖片描述
在這裡插入圖片描述
本文所需jar包下載地址:https://download.csdn.net/download/m0_37739193/10854643

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">
  <display-name>WxAuth</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  
  <servlet>
      <servlet-name>wxCallBack</servlet-name>
      <servlet-class>com.xingshang.servlet.CallBackSerclet</servlet-class>
      <init-param>
          <param-name>dbUrl</param-name>
          <param-value>jdbc:mysql://127.0.0.1:3306/xiaoqiang</param-value>
      </init-param>
      <init-param>
          <param-name>driverClassName</param-name>
          <param-value>com.mysql.jdbc.Driver</param-value>
      </init-param>
      <init-param>
          <param-name>userName</param-name>
          <param-value>root</param-value>
      </init-param>
      <init-param>
          <param-name>passWord</param-name>
          <param-value>123456</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
  </servlet>
  
  <servlet-mapping>
      <servlet-name>wxCallBack</servlet-name>
      <url-pattern>/wxCallBack</url-pattern>
  </servlet-mapping>
</web-app>

AuthUtil:

package com.xingshang.util;

import java.io.IOException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;

import net.sf.json.JSONObject;

public class AuthUtil {
    
    public static final String APPID = "xxxxxxxxxxxxxxxxxx";
    public static final String APPSECRET = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

    public static JSONObject doGetJson(String url) throws ClientProtocolException, IOException{
        JSONObject jsonObject = null;
        //首先初始化HttpClient物件
//        DefaultHttpClient client = new DefaultHttpClient();
        HttpClient client = HttpClientBuilder.create().build();//獲取DefaultHttpClient請求,上面註釋的那行已經不建議使用
        //通過get方式進行提交
        HttpGet httpGet = new HttpGet(url);
        //通過HTTPclient的execute方法進行傳送請求
        HttpResponse response = client.execute(httpGet);
        //從response裡面拿自己想要的結果
        HttpEntity entity = response.getEntity();
        if(entity != null){
            String result = EntityUtils.toString(entity,"UTF-8");
            jsonObject = JSONObject.fromObject(result);
        }
        //把連結釋放掉
        httpGet.releaseConnection();
        return jsonObject;
    }
}

Java實現:
1、引導使用者進入授權頁面同意授權,獲取code
這一步其實就是將需要授權的頁面url拼接到微信的認證請求接口裡面,比如需要使用者在訪問頁面時進行授權認證
其中的scope引數有兩個值:
snsapi_base:只能獲取到使用者openid。好處是靜預設證,無需使用者手動點選認證按鈕,感覺上像是直接進入網站一樣。
snsapi_userinfo:可以獲取到openid、暱稱、頭像、所在地等資訊。需要使用者手動點選認證按鈕。並且,即使在未關注的情況下,只要使用者授權,也能獲取其資訊
LoginServlet:

package com.xingshang.servlet;

import java.io.IOException;
import java.net.URLEncoder;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.xingshang.util.AuthUtil;

/**
 * 入口地址
 * @author Administrator
 */
@WebServlet("/wxLogin")
public class LoginServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
        //第一步:引導使用者進入授權頁面同意授權,獲取code
        
        //回撥地址
//        String backUrl = "http://xiaoqiang.tunnel.qydev.com/WxAuth/callBack";    //第1種情況使用
        String backUrl = "http://xiaoqiang.tunnel.qydev.com/WxAuth/wxCallBack";  //第2種情況使用,這裡是web.xml中的路徑
        
        //授權頁面地址
        String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+AuthUtil.APPID
                + "&redirect_uri="+URLEncoder.encode(backUrl, "UTF-8") //URLEncoder.encode(backUrl)已不建議使用。這裡推薦一個encode轉義的網頁工具https://www.jianshu.com/p/f0966f28ddac
                + "&response_type=code"
                + "&scope=snsapi_userinfo"
                + "&state=STATE#wechat_redirect";
        
        //重定向到授權頁面
        response.sendRedirect(url);
    }
}

2、通過第一步獲取的code換取網頁授權access_token(與基礎支援中的access_token不同)
(1)網頁授權的access_token在每次獲取openID時一起更新,在介面呼叫頻次限制中為“無上限”。
(2)基礎access_token一般限制為2000次/日,需要自己儲存起來並定時更新。
這一步需要在控制器中獲取微信回傳給我們的code,通過這個code來請求access_token,通過access_token和openid獲取使用者基本資訊。

CallBackSerclet:

package com.xingshang.servlet;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.xingshang.util.AuthUtil;

import net.sf.json.JSONObject;

/**
 * 回撥地址
 * @author Administrator
 */
//@WebServlet("/callBack")
//第1種情況的兩種寫法
//@WebServlet(urlPatterns = "/callBack")
public class CallBackSerclet extends HttpServlet {

    private static final long serialVersionUID = 1L;
    
    private String dbUrl;
    private String driverClassName;
    private String userName;
    private String passWord;
    
    private Connection conn =null;
    private PreparedStatement ps =null;
    private ResultSet rs = null;
    
    //初始化資料庫
    @Override
    public void init(ServletConfig config) throws ServletException {
        
        //載入驅動
        try {
            this.dbUrl = config.getInitParameter("dbUrl");
            this.driverClassName = config.getInitParameter("driverClassName");
            this.userName = config.getInitParameter("userName");
            this.passWord = config.getInitParameter("passWord");
            Class.forName(driverClassName);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
        //第二步:通過code換取網頁授權access_token
        
        //從request裡面獲取code引數(當微信伺服器訪問回撥地址的時候,會把code引數傳遞過來)
        String code = request.getParameter("code");
        System.out.println("code:"+code);
        
        //獲取code後,請求以下連結獲取access_token
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + AuthUtil.APPID
                + "&secret=" + AuthUtil.APPSECRET
                + "&code=" + code
                + "&grant_type=authorization_code";
        
        //通過網路請求方法來請求上面這個介面
        JSONObject jsonObject = AuthUtil.doGetJson(url);
        System.out.println("==========================jsonObject"+jsonObject);
        
        //從返回的JSON資料中取出access_token和openid,拉取使用者資訊時用
        String token =  jsonObject.getString("access_token");
        String openid = jsonObject.getString("openid");
        
        // 第三步:重新整理access_token(如果需要)

        // 第四步:拉取使用者資訊(需scope為snsapi_userinfo)
        String infoUrl ="https://api.weixin.qq.com/sns/userinfo?access_token=" + token
                + "&openid=" + openid
                + "&lang=zh_CN";
        //通過網路請求方法來請求上面這個介面
        JSONObject userInfo = AuthUtil.doGetJson(infoUrl);
        /** userInfo樣例:
         * {"openid":"xiaoqiangxxxxxxx_xxxxxxxxxOs","nickname":"小強簽名設計","sex":1,"language":"zh_CN","city":"張家口","province":"河北","country":"中國",
         * "headimgurl":"http://thirdwx.qlogo.cn/mmopen/vi_32/TGm9MicTQp8icMoA2mFDXIKhsHXfamAVibskR11VwZWu6I2trEb038ufVh6ianSAQz6zDuYEsxicFfElWskVTYmldrA/132",
         * "privilege":[],"unionid":"xiaoxx-xiaoGVcRqiangxiaxiaxT"}
         */
        
        //第1種情況:使用微信使用者資訊直接登入,無需註冊和繫結
//        request.setAttribute("info", userInfo);
        //直接跳轉
//        request.getRequestDispatcher("/index1.jsp").forward(request, response);
        
        
        //第2種情況: 將微信與當前系統的賬號進行繫結(需將第1種情況和@WebServlet("/callBack")註釋掉)
        //第一步,根據當前openid查詢資料庫,看是否該賬號已經進行繫結
        try {
            String nickname = getNickName(openid);
            if(!"".equals(nickname)){
                //已繫結
                request.setAttribute("nickname", nickname);
                request.getRequestDispatcher("/index2.jsp").forward(request, response);
            }else{
                //未繫結
                request.setAttribute("openid", openid);
                request.getRequestDispatcher("/login.jsp").forward(request, response);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //資料庫的查詢
    public String getNickName(String openid) throws SQLException{
        String nickName = "";
        //建立資料庫連結
        conn = DriverManager.getConnection(dbUrl, userName, passWord);
        String sql = "select nickname from user where openid = ?";
        ps = conn.prepareStatement(sql);
        ps.setString(1, openid);
        rs = ps.executeQuery();
        while (rs.next()) {
            nickName = rs.getString("nickname"); //暱稱
        }
        
        //關閉連結
        rs.close();
        ps.close();
        conn.close();
        
        return nickName;
    }
    
    //資料庫的修改(openid的繫結)
    public int updateUser(String account,String password,String openid) throws SQLException{
        
        //建立資料庫連結
        conn = DriverManager.getConnection(dbUrl, userName, passWord);
        String sql = "update user set openid = ? where account = ? and password = ?";
        ps = conn.prepareStatement(sql);
        ps.setString(1, openid);
        ps.setString(2, account);
        ps.setString(3, password);
        int temp = ps.executeUpdate();
        
        //關閉連結
        rs.close();
        ps.close();
        conn.close();
        
        return temp;
    }
    
    //post方法,用來接受登入請求
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
        String account = request.getParameter("account");
        String password = request.getParameter("password");
        String openid = request.getParameter("openid");
        
        try {
            int temp = updateUser(account, password, openid); 
            
            if(temp > 0){
                String nickname = getNickName(openid);
                request.setAttribute("nickname", nickname);
                request.getRequestDispatcher("/index2.jsp").forward(request, response);
                System.out.println("賬號繫結成功");
            }else{
                System.out.println("賬號繫結失敗");
            }
            
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

login.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Insert title here</title>
</head>
<body>
    <form action="/WxAuth/wxCallBack" method="post">
        <input type="text" name="account" />
        <input type="password" name="password" />
        <input type="hidden" name="openid" value="${openid }" />
        <input type="submit" value="提交併繫結" />
    </form>
</body>
</html>

index.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Insert title here</title>
</head>
<body style="font-size: 40px; text-align: center;">
    <a href="/WxAuth/wxLogin">微信公眾授權登入</a>
</body>
</html>

index1.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Insert title here</title>
</head>
<body>
    <div>登陸成功!</div>
    <div>使用者暱稱:${info.nickname}</div>
    <div>使用者頭像:<img style="text-align: top;" width="100" src="${info.headimgurl }"></div>
</body>
</html>

index2.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Insert title here</title>
</head>
<body>
    <div>登陸成功!</div>
    <div>使用者暱稱:${nickname}</div>
</body>
</html>

三、專案執行:

在這裡插入圖片描述
電腦:
在這裡插入圖片描述
手機:
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
注意:不能直接在瀏覽器中開啟,要在微信客戶端開啟連結http://xiaoqiang.tunnel.qydev.com/WxAuth/
 

四、遇到的問題:

1.如果你已經授權過,電腦客戶端比手機客戶端(我試了多個手機包括蘋果安卓都是)多一個頁面(近期你已經授權登陸過XXXXX 自動登入中)。
我分別在用手機和電腦直接開啟這兩個網址,發現手機這兩個地址都是直接跳轉到http://xiaoqiang.tunnel.qydev.com/WxAuth/頁面(你複製該頁面連結可以看到有code引數,如http://xiaoqiang.tunnel.qydev.com/WxAuth/?code=077zL1rm1c26mp0zxIom1YwXqm1zL1r1&state=123),而電腦在第一個地址先彈出“近期你已經授權登陸過XXXXX 自動登入中”頁面在跳轉到http://xiaoqiang.tunnel.qydev.com/WxAuth/頁面,而第二個地址和手機效果一樣都是直接跳轉到了回撥地址。(我懷疑微信官方做了相應的處理,網上也沒有搜到相應的說法)
https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxxxxxxxxxxxxxxxxx&redirect_uri=http%3A%2F%2Fxiaoqiang.tunnel.qydev.com%2FWxAuth%2F&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxxxxxxxxxxxxxxxxx&redirect_uri=http%3A%2F%2Fxiaoqiang.tunnel.qydev.com%2FWxAuth%2F &response_type=code&scope=snsapi_base&state=123#wechat_redirect
2.在使用snsapi_base的時候還必須在redirect_uri後面加上%0A,否則跳轉後的網頁連結還為上面的那個長連結根本沒有code引數(雖然複製連結沒有code引數,但是程式裡卻還是能獲取到code引數不受影響),也不知道是為什麼,垃圾騰訊官方文件啥也沒說,而且使用snsapi_base呼叫介面最終也可以獲得使用者的其他資訊如頭像啊,並不像官方文件說的是能獲得openID。網上有種說法“如果使用者之前進行了snsapi_userinfo授權,那麼在一定時間內進行snsapi_base授權拿到的access_token是可以拿到使用者資訊的,這個時間就不好測試了。”垃圾官方文件啥也沒說,還得讓人猜,真是服了。

參考:
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
https://www.cnblogs.com/sutao/p/8727019.html