我們首先來看看官方的文檔怎麼說:如果用戶在微信客戶端中訪問第三方網頁,公眾號可以通過微信網頁授權機制,來獲取用戶基本信息,進而實現業務邏輯。關於網頁授權回調功能變數名稱的說明1、在微信公眾號請求用戶網頁授權之前,開發者需要先到公眾平臺官網中的開發者中心頁配置授權回調功能變數名稱。請註意,這裡填寫的是功能變數名稱(是一個字元...
我們首先來看看官方的文檔怎麼說:
如果用戶在微信客戶端中訪問第三方網頁,公眾號可以通過微信網頁授權機制,來獲取用戶基本信息,進而實現業務邏輯。
關於網頁授權回調功能變數名稱的說明
1、在微信公眾號請求用戶網頁授權之前,開發者需要先到公眾平臺官網中的開發者中心頁配置授權回調功能變數名稱。請註意,這裡填寫的是功能變數名稱(是一個字元串),而不是URL,因此請勿加http://等協議頭; 2、授權回調功能變數名稱配置規範為全功能變數名稱,比如需要網頁授權的功能變數名稱為:www.qq.com,配置以後此功能變數名稱下麵的頁面http://www.qq.com/music.html 、 http://www.qq.com/login.html 都可以進行OAuth2.0鑒權。但http://pay.qq.com 、 http://music.qq.com 、 http://qq.com無法進行OAuth2.0鑒權 3、如果公眾號登錄授權給了第三方開發者來進行管理,則不必做任何設置,由第三方代替公眾號實現網頁授權即可
關於網頁授權的兩種scope的區別說明
1、以snsapi_base為scope發起的網頁授權,是用來獲取進入頁面的用戶的openid的,並且是靜默授權並自動跳轉到回調頁的。用戶感知的就是直接進入了回調頁(往往是業務頁面) 2、以snsapi_userinfo為scope發起的網頁授權,是用來獲取用戶的基本信息的。但這種授權需要用戶手動同意,並且由於用戶同意過,所以無須關註,就可在授權後獲取該用戶的基本信息。 3、用戶管理類介面中的“獲取用戶基本信息介面”,是在用戶和公眾號產生消息交互或關註後事件推送後,才能根據用戶OpenID來獲取用戶基本信息。這個介面,包括其他微信介面,都是需要該用戶(即openid)關註了公眾號後,才能調用成功的。
關於網頁授權access_token和普通access_token的區別
1、微信網頁授權是通過OAuth2.0機制實現的,在用戶授權給公眾號後,公眾號可以獲取到一個網頁授權特有的介面調用憑證(網頁授權access_token),通過網頁授權access_token可以進行授權後介面調用,如獲取用戶基本信息; 2、其他微信介面,需要通過基礎支持中的“獲取access_token”介面來獲取到的普通access_token調用。
關於UnionID機制
1、請註意,網頁授權獲取用戶基本信息也遵循UnionID機制。即如果開發者有在多個公眾號,或在公眾號、移動應用之間統一用戶帳號的需求,需要前往微信開放平臺(open.weixin.qq.com)綁定公眾號後,才可利用UnionID機制來滿足上述需求。 2、UnionID機制的作用說明:如果開發者擁有多個移動應用、網站應用和公眾帳號,可通過獲取用戶基本信息中的unionid來區分用戶的唯一性,因為同一用戶,對同一個微信開放平臺下的不同應用(移動應用、網站應用和公眾帳號),unionid是相同的。
關於特殊場景下的靜默授權
1、上面已經提到,對於以snsapi_base為scope的網頁授權,就靜默授權的,用戶無感知; 2、對於已關註公眾號的用戶,如果用戶從公眾號的會話或者自定義菜單進入本公眾號的網頁授權頁,即使是scope為snsapi_userinfo,也是靜默授權,用戶無感知。
具體而言,網頁授權流程分為四步:
1、引導用戶進入授權頁面同意授權,獲取code 2、通過code換取網頁授權access_token(與基礎支持中的access_token不同) 3、如果需要,開發者可以刷新網頁授權access_token,避免過期 4、通過網頁授權access_token和openid獲取用戶基本信息(支持UnionID機制)
下麵,我們來按照這個步驟來實現這個功能:
第一步:用戶同意授權,獲取code
在確保微信公眾賬號擁有授權作用域(scope參數)的許可權的前提下(服務號獲得高級介面後,預設擁有scope參數中的snsapi_base和snsapi_userinfo),引導關註者打開如下頁面
參考鏈接(請在微信客戶端中打開此鏈接體驗)
Scope為snsapi_base
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx520c15f417810387&redirect_uri=https%3A%2F%2Fchong.qq.com%2Fphp%2Findex.php%3Fd%3D%26c%3DwxAdapter%26m%3DmobileDeal%26showwxpaytitle%3D1%26vb2ctag%3D4_2030_5_1194_60&response_type=code&scope=snsapi_base&state=123#wechat_redirect
Scope為snsapi_userinfo
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxf0e81c3bee622d60&redirect_uri=http%3A%2F%2Fnba.bluewebgame.com%2Foauth_response.php&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
參數說明
參數 | 是否必須 | 說明 |
---|---|---|
appid | 是 | 公眾號的唯一標識 |
redirect_uri | 是 | 授權後重定向的回調鏈接地址,請使用urlencode對鏈接進行處理 |
response_type | 是 | 返回類型,請填寫code |
scope | 是 | 應用授權作用域,snsapi_base (不彈出授權頁面,直接跳轉,只能獲取用戶openid),snsapi_userinfo (彈出授權頁面,可通過openid拿到昵稱、性別、所在地。並且,即使在未關註的情況下,只要用戶授權,也能獲取其信息) |
state | 否 | 重定向後會帶上state參數,開發者可以填寫a-zA-Z0-9的參數值,最多128位元組 |
#wechat_redirect | 是 | 無論直接打開還是做頁面302重定向時候,必須帶此參數 |
下圖為scope等於snsapi_userinfo時的授權頁面:
用戶同意授權後
如果用戶同意授權,頁面將跳轉至 redirect_uri/?code=CODE&state=STATE。若用戶禁止授權,則重定向後不會帶上code參數,僅會帶上state參數redirect_uri?state=STATE
code說明 : code作為換取access_token的票據,每次用戶授權帶上的code將不一樣,code只能使用一次,5分鐘未被使用自動過期。
溫馨提醒:以下的省略了搭建環境和導入jar的過程,以下的方法提供參考。如果需要的話,需要看下前面的系列文章。
我們首先創建一些需要用到的pojo :
1. 通過網頁授權獲取的用戶信息
package com.souvc.weixin.pojo; import java.util.List; /** * 類名: SNSUserInfo </br> * 描述: 通過網頁授權獲取的用戶信息 </br> * 開發人員: souvc </br> * 創建時間: 2015-11-27 </br> * 發佈版本:V1.0 </br> */ public class SNSUserInfo { // 用戶標識 private String openId; // 用戶昵稱 private String nickname; // 性別(1是男性,2是女性,0是未知) private int sex; // 國家 private String country; // 省份 private String province; // 城市 private String city; // 用戶頭像鏈接 private String headImgUrl; // 用戶特權信息 private List<String> privilegeList; public String getOpenId() { return openId; } public void setOpenId(String openId) { this.openId = openId; } public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } public int getSex() { return sex; } public void setSex(int sex) { this.sex = sex; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getHeadImgUrl() { return headImgUrl; } public void setHeadImgUrl(String headImgUrl) { this.headImgUrl = headImgUrl; } public List<String> getPrivilegeList() { return privilegeList; } public void setPrivilegeList(List<String> privilegeList) { this.privilegeList = privilegeList; } }
2. 憑證實體類
package com.souvc.weixin.pojo; /** * 類名: Token </br> * 描述: 憑證 </br> * 開發人員: souvc </br> * 創建時間: 2015-11-27 </br> * 發佈版本:V1.0 </br> */ public class Token { // 介面訪問憑證 private String accessToken; // 憑證有效期,單位:秒 private int expiresIn; public String getAccessToken() { return accessToken; } public void setAccessToken(String accessToken) { this.accessToken = accessToken; } public int getExpiresIn() { return expiresIn; } public void setExpiresIn(int expiresIn) { this.expiresIn = expiresIn; } }
3. 網頁授權信息 類
package com.souvc.weixin.pojo; /** * 類名: WeixinOauth2Token </br> * 描述: 網頁授權信息 </br> * 開發人員: souvc </br> * 創建時間: 2015-11-27 </br> * 發佈版本:V1.0 </br> */ public class WeixinOauth2Token { // 網頁授權介面調用憑證 private String accessToken; // 憑證有效時長 private int expiresIn; // 用於刷新憑證 private String refreshToken; // 用戶標識 private String openId; // 用戶授權作用域 private String scope; public String getAccessToken() { return accessToken; } public void setAccessToken(String accessToken) { this.accessToken = accessToken; } public int getExpiresIn() { return expiresIn; } public void setExpiresIn(int expiresIn) { this.expiresIn = expiresIn; } public String getRefreshToken() { return refreshToken; } public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; } public String getOpenId() { return openId; } public void setOpenId(String openId) { this.openId = openId; } public String getScope() { return scope; } public void setScope(String scope) { this.scope = scope; } }
4. 微信用戶的基本信息
package com.souvc.weixin.pojo; /** * 類名: WeixinUserInfo </br> * 描述: 微信用戶的基本信息 </br> * 開發人員: souvc </br> * 創建時間: 2015-11-27 </br> * 發佈版本:V1.0 </br> */ public class WeixinUserInfo { // 用戶的標識 private String openId; // 關註狀態(1是關註,0是未關註),未關註時獲取不到其餘信息 private int subscribe; // 用戶關註時間,為時間戳。如果用戶曾多次關註,則取最後關註時間 private String subscribeTime; // 昵稱 private String nickname; // 用戶的性別(1是男性,2是女性,0是未知) private int sex; // 用戶所在國家 private String country; // 用戶所在省份 private String province; // 用戶所在城市 private String city; // 用戶的語言,簡體中文為zh_CN private String language; // 用戶頭像 private String headImgUrl; public String getOpenId() { return openId; } public void setOpenId(String openId) { this.openId = openId; } public int getSubscribe() { return subscribe; } public void setSubscribe(int subscribe) { this.subscribe = subscribe; } public String getSubscribeTime() { return subscribeTime; } public void setSubscribeTime(String subscribeTime) { this.subscribeTime = subscribeTime; } public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } public int getSex() { return sex; } public void setSex(int sex) { this.sex = sex; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getLanguage() { return language; } public void setLanguage(String language) { this.language = language; } public String getHeadImgUrl() { return headImgUrl; } public void setHeadImgUrl(String headImgUrl) { this.headImgUrl = headImgUrl; } }
但是如何獲取到token值呢?
/** * 獲取網頁授權憑證 * * @param appId 公眾賬號的唯一標識 * @param appSecret 公眾賬號的密鑰 * @param code * @return WeixinAouth2Token */ public static WeixinOauth2Token getOauth2AccessToken(String appId, String appSecret, String code) { WeixinOauth2Token wat = null; // 拼接請求地址 String requestUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code"; requestUrl = requestUrl.replace("APPID", appId); requestUrl = requestUrl.replace("SECRET", appSecret); requestUrl = requestUrl.replace("CODE", code); // 獲取網頁授權憑證 JSONObject jsonObject = CommonUtil.httpsRequest(requestUrl, "GET", null); if (null != jsonObject) { try { wat = new WeixinOauth2Token(); wat.setAccessToken(jsonObject.getString("access_token")); wat.setExpiresIn(jsonObject.getInt("expires_in")); wat.setRefreshToken(jsonObject.getString("refresh_token")); wat.setOpenId(jsonObject.getString("openid")); wat.setScope(jsonObject.getString("scope")); } catch (Exception e) { wat = null; int errorCode = jsonObject.getInt("errcode"); String errorMsg = jsonObject.getString("errmsg"); log.error("獲取網頁授權憑證失敗 errcode:{} errmsg:{}", errorCode, errorMsg); } } return wat; }
獲取用戶信息:
/** * 通過網頁授權獲取用戶信息 * * @param accessToken 網頁授權介面調用憑證 * @param openId 用戶標識 * @return SNSUserInfo */ @SuppressWarnings( { "deprecation", "unchecked" }) public static SNSUserInfo getSNSUserInfo(String accessToken, String openId) { SNSUserInfo snsUserInfo = null; // 拼接請求地址 String requestUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID"; requestUrl = requestUrl.replace("ACCESS_TOKEN", accessToken).replace("OPENID", openId); // 通過網頁授權獲取用戶信息 JSONObject jsonObject = CommonUtil.httpsRequest(requestUrl, "GET", null); if (null != jsonObject) { try { snsUserInfo = new SNSUserInfo(); // 用戶的標識 snsUserInfo.setOpenId(jsonObject.getString("openid")); // 昵稱 snsUserInfo.setNickname(jsonObject.getString("nickname")); // 性別(1是男性,2是女性,0是未知) snsUserInfo.setSex(jsonObject.getInt("sex")); // 用戶所在國家 snsUserInfo.setCountry(jsonObject.getString("country")); // 用戶所在省份 snsUserInfo.setProvince(jsonObject.getString("province")); // 用戶所在城市 snsUserInfo.setCity(jsonObject.getString("city")); // 用戶頭像 snsUserInfo.setHeadImgUrl(jsonObject.getString("headimgurl")); // 用戶特權信息 snsUserInfo.setPrivilegeList(JSONArray.toList(jsonObject.getJSONArray("privilege"), List.class)); } catch (Exception e) { snsUserInfo = null; int errorCode = jsonObject.getInt("errcode"); String errorMsg = jsonObject.getString("errmsg"); log.error("獲取用戶信息失敗 errcode:{} errmsg:{}", errorCode, errorMsg); } } return snsUserInfo; }
上面我們用到了一個支持發送https請求的工具:
package com.souvc.weixin.util; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.X509TrustManager; /** * 類名: MyX509TrustManager </br> * 描述:信任管理器 </br> * 開發人員: souvc </br> * 創建時間: 2015-11-27 </br> * 發佈版本:V1.0 </br> */ public class MyX509TrustManager implements X509TrustManager { // 檢查客戶端證書 public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } // 檢查伺服器端證書 public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } // 返回受信任的X509證書數組 public X509Certificate[] getAcceptedIssuers() { return null; } }
/** * 發送https請求 * * @param requestUrl 請求地址 * @param requestMethod 請求方式(GET、POST) * @param outputStr 提交的數據 * @return JSONObject(通過JSONObject.get(key)的方式獲取json對象的屬性值) */ public static JSONObject httpsRequest(String requestUrl, String requestMethod, String outputStr) { JSONObject jsonObject = null; try { // 創建SSLContext對象,並使用我們指定的信任管理器初始化 TrustManager[] tm = { new MyX509TrustManager() }; SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); // 從上述SSLContext對象中得到SSLSocketFactory對象 SSLSocketFactory ssf = sslContext.getSocketFactory(); URL url = new URL(requestUrl); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setSSLSocketFactory(ssf); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); // 設置請求方式(GET/POST) conn.setRequestMethod(requestMethod); // 當outputStr不為null時向輸出流寫數據 if (null != outputStr) { OutputStream outputStream = conn.getOutputStream(); // 註意編碼格式 outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); } // 從輸入流讀取返回內容 InputStream inputStream = conn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; StringBuffer buffer = new StringBuffer(); while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } // 釋放資源 bufferedReader.close(); inputStreamReader.close(); inputStream.close(); inputStream = null; conn.disconnect(); jsonObject = JSONObject.fromObject(buffer.toString()); } catch (ConnectException ce) { log.error("連接超時:{}", ce); } catch (Exception e) { log.error("https請求異常:{}", e); } return jsonObject; }
二、寫授權類:
註意替換成自己的appid 和 密鑰
package com.souvc.weixin.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.souvc.weixin.pojo.SNSUserInfo; import com.souvc.weixin.pojo.WeixinOauth2Token; import com.souvc.weixin.util.AdvancedUtil; /** * 類名: OAuthServlet </br> * 描述: 授權後的回調請求處理 </br> * 開發人員: souvc </br> * 創建時間: 2015-11-27 </br> * 發佈版本:V1.0 </br> */ public class OAuthServlet extends HttpServlet { private static final long serialVersionUID = -1847238807216447030L; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8"); // 用戶同意授權後,能獲取到code String code = request.getParameter("code"); String state = request.getParameter("state"); // 用戶同意授權 if (!"authdeny".equals(code)) { // 獲取網頁授權access_token WeixinOauth2Token weixinOauth2Token = AdvancedUtil.getOauth2AccessToken("wxe34a90ac7bxxcab85c", "1207d566090a8344xxxd6224c02c", code); // 網頁授權介面訪問憑證 String accessToken = weixinOauth2Token.getAccessToken(); // 用戶標識 String openId = weixinOauth2Token.getOpenId(); // 獲取用戶信息 SNSUserInfo snsUserInfo = AdvancedUtil.getSNSUserInfo(accessToken, openId); // 設置要傳遞的參數 request.setAttribute("snsUserInfo", snsUserInfo); request.setAttribute("state", state); } // 跳轉到index.jsp request.getRequestDispatcher("index.jsp").forward(request, response); } }
三、授權後,顯示信息的頁面
<%@ page language="java" pageEncoding="utf-8"%> <%@ page import="com.souvc.weixin.pojo.SNSUserInfo,java.lang.*"%> <html> <head> <title>OAuth2.0網頁授權</title> <meta name="viewport" content="width=device-width,user-scalable=0"> <style type="text/css"> *{margin:0; padding:0} table{border:1px dashed #B9B9DD;font-size:12pt} td{border:1px dashed #B9B9DD;word-break:break-all; word-wrap:break-word;} </style> </head> <body> <% // 獲取由OAuthServlet中傳入的參數 SNSUserInfo user = (SNSUserInfo)request.getAttribute("snsUserInfo"); String state=request.getAttribute("state").toString(); if(null != user) { %> <table width="100%" cellspacing="0" cellpadding="0"> <tr><td width="20%">屬性</td><td width="80%">值</td></tr> <tr><td>OpenID</td><td><%=user.getOpenId()%></td></tr> <tr><td>昵稱</td><td><%=user.getNickname()%></td></tr> <tr><td>性別</td><td><%=user.getSex()%></td></tr> <tr><td>國家</td><td><%=user.getCountry()%></td></tr> <tr><td>省份</td><td><%=user.getProvince()%></td></tr> <tr><td>城市</td><td><%=user.getCity()%></td></tr> <tr><td>頭像</td><td><%=user.getHeadImgUrl()%></td></tr> <tr><td>特權</td><td><%=user.getPrivilegeList()%></td></tr> <tr><td>state:</td><td><%=state%></td></tr> </table> <% } else out.print("用戶不同意授權,未獲取到用戶信息!"); %> </body> </html>
四、寫請求的路徑
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- <servlet> <servlet-name>coreServlet</servlet-name> <servlet-class>com.souvc.weixin.servlet.CoreServlet</servlet-class> </servlet>
-->
<!-- /coreServlet用於指定該Servlet的訪問路徑 <servlet-mapping> <servlet-name>coreServlet</servlet-name> <url-pattern>/coreServlet</url-pattern> </servlet-mapping>
-->
<servlet> <servlet-name>oauthServlet</servlet-name> <servlet-class>com.souvc.weixin.servlet.OAuthServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>oauthServlet</servlet-name> <url-pattern>/oauthServlet</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
五、替換官方的鏈接成我們的方法路徑:
Scope為snsapi_userinfo
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxf0e81c3bee622d60&redirect_uri=http%3A%2F%2Fnba.bluewebgame.com%2Foauth_response.php&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
我這裡是:把
http%3A%2F%2Fnba.bluewebgame.com%2Foauth_response.php 換成自己的請求鏈接。
註意這個鏈接需要經過utf-8的編碼:
方法是:
/** * URL編碼(utf-8) * * @param source * @return */ public static String urlEncodeUTF8(String source) { String result = source; try { result = java.net.URLEncoder.encode(source, "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return result; }
package com.souvc.weixin.util; public class TestURL { /** * 方法名:main</br> * 詳述:生成URL編碼 </br> * 開發人員:souvc </br> * 創建時間:2016-1-4 </br> * @param args 說明返回值含義 * @throws 說明發生此異常的條件 */ public static void main(String[] args) { String source="http://chiyan.duapp.com/oauthServlet"; System.out.println(CommonUtil.urlEncodeUTF8(source)); } }
也可以直接線上url編碼: http://tool.chinaz.com/Tools/URLEncode.aspx
六、複製上面替換好的鏈接,然後丟進瀏覽器,然後用微信來掃一掃。會出現以下的效果:
說明,恭喜你,我們獲取到了用戶的基本信息。