實現微信開啟網頁時,獲取當前微信使用者資訊(Java)(基於snsapi_userinfo方式)
/**
* 1 第一步:使用者同意授權,獲取code
* 2 第二步:通過code換取網頁授權access_token
* 3 第三步:重新整理access_token(如果需要)
* 4 第四步:拉取使用者資訊(需scope為 snsapi_userinfo)
*/
第一部分:微信授權獲取基本資訊的介紹
我們首先來看看官方的文件怎麼說:
如果使用者在微信客戶端中訪問第三方網頁,公眾號可以通過微信網頁授權機制,來獲取使用者基本資訊,進而實現業務邏輯。
關於網頁授權回撥域名的說明
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),引導關注者開啟如下頁面
引數說明
引數 | 是否必須 | 說明 |
---|---|---|
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. 通過網頁授權獲取的使用者資訊
使用者資訊類:SNSUserInfo類
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; } }
View Code
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; } }
View Code
3. 網頁授權資訊 WeixinOauth2Token類
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; } }
View Code
4. 微信使用者的基本資訊WeixinUserInfo類
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; } }
View Code
5. 封裝AdvancedUtil來實現以下方法 。
但是如何獲取到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; }
View Code
獲取使用者資訊:
/** * 通過網頁授權獲取使用者資訊 * * @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; }
View Code
6. 封裝https請求類 CommonUtil 類。
上面我們用到了一個支援傳送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; } }
View Code
/** * 傳送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; }
View Code
二、寫授權類:
注意替換成自己的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); } }
View Code
三、授權後,顯示資訊的頁面
<%@ 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>
View Code
四、寫請求的路徑
<?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>
View Code
五、替換官方的連結成我們的方法路徑:
官方的請求連結:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
需要修改的地方:
(1)替換自己的AppID
(2)將redirect_url換成自己的授權請求連結URL。注意這個連線需要經過UTF-8編碼。
(3)需要修改scope。需要彈出頁面則要修改為snsapi_userinfo 。
scope引數的解釋:
1、以snsapi_base為scope發起的網頁授權,是用來獲取進入頁面的使用者的openid的,並且是靜默授權並自動跳轉到回撥頁的。使用者感知的就是直接進入了回撥頁(往往是業務頁面)
2、以snsapi_userinfo為scope發起的網頁授權,是用來獲取使用者的基本資訊的。但這種授權需要使用者手動同意,並且由於使用者同意過,所以無須關注,就可在授權後獲取該使用者的基本資訊。
/** * 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; }
View Code
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)); } }
View Code
也可以直接線上url編碼: http://tool.chinaz.com/Tools/URLEncode.aspx
六、複製上面替換好的連結,然後丟進瀏覽器,然後用微信來掃一掃。會出現以下的效果:
說明,恭喜你,我們獲取到了使用者的基本資訊。