第一部分:微信授權獲取基本信息的介紹
我們首先來看看官方的文檔怎么說:
如果用戶在微信客戶端中訪問第三方網頁,公眾號可以通過微信網頁授權機制,來獲取用戶基本信息,進而實現業務邏輯。
關於網頁授權回調域名的說明
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. 通過網頁授權獲取的用戶信息
用戶信息類:SNSUserInfo類
package com.souvc.weixin.pojo; import java.util.List; /** * 類名: SNSUserInfo </br> * 描述: 通過網頁授權獲取的用戶信息 </br> * 開發人員: fr </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. 憑證實體類
/** * 類名: Token </br> * 描述: 憑證 </br> * 開發人員: fr</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. 網頁授權信息 WeixinOauth2Token類
/** * 類名: WeixinOauth2Token </br> * 描述: 網頁授權信息 </br> * 開發人員: fr</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. 微信用戶的基本信息WeixinUserInfo類
/** * 類名: WeixinUserInfo </br> * 描述: 微信用戶的基本信息 </br> * 開發人員: fr</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; } }
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; }
獲取用戶信息:
/** * 通過網頁授權獲取用戶信息 * * @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; }
6. 封裝https請求類 CommonUtil 類。
上面我們用到了一個支持發送https請求的工具:
import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.X509TrustManager; /** * 類名: MyX509TrustManager </br> * 描述:信任管理器 </br> * 開發人員: fr</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 和 密鑰
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> * 開發人員: fr</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("wx82b8dc4964d758fa", "951d92ccba3df8f8591f6550017858d4", 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); } }
替換官方的鏈接成我們的方法路徑:
官方的請求鏈接:
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用的:http://tool.chinaz.com/tools/urlencode.aspx
至此,授權后,你就可以顯示用戶信息拉,自己編寫頁面吧。
如果需要生成自己平台賬號等操作,直接在獲得openid后進行。