第一部分:微信受權獲取基本信息的介紹php
咱們首先來看看官方的文檔怎麼說:html
若是用戶在微信客戶端中訪問第三方網頁,公衆號能夠經過微信網頁受權機制,來獲取用戶基本信息,進而實現業務邏輯。java
關於網頁受權回調域名的說明web
一、在微信公衆號請求用戶網頁受權以前,開發者須要先到公衆平臺官網中的開發者中心頁配置受權回調域名。請注意,這裏填寫的是域名(是一個字符串),而不是URL,所以請勿加http://等協議頭; 二、受權回調域名配置規範爲全域名,好比須要網頁受權的域名爲: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鑑權 三、若是公衆號登陸受權給了第三方開發者來進行管理,則沒必要作任何設置,由第三方代替公衆號實現網頁受權便可
關於網頁受權的兩種scope的區別說明json
一、以snsapi_base爲scope發起的網頁受權,是用來獲取進入頁面的用戶的openid的,而且是靜默受權並自動跳轉到回調頁的。用戶感知的就是直接進入了回調頁(每每是業務頁面) 二、以snsapi_userinfo爲scope發起的網頁受權,是用來獲取用戶的基本信息的。但這種受權須要用戶手動贊成,而且因爲用戶贊成過,因此無須關注,就可在受權後獲取該用戶的基本信息。 三、用戶管理類接口中的「獲取用戶基本信息接口」,是在用戶和公衆號產生消息交互或關注後事件推送後,才能根據用戶OpenID來獲取用戶基本信息。這個接口,包括其餘微信接口,都是須要該用戶(即openid)關注了公衆號後,才能調用成功的。
關於網頁受權access_token和普通access_token的區別api
一、微信網頁受權是經過OAuth2.0機制實現的,在用戶受權給公衆號後,公衆號能夠獲取到一個網頁受權特有的接口調用憑證(網頁受權access_token),經過網頁受權access_token能夠進行受權後接口調用,如獲取用戶基本信息; 二、其餘微信接口,須要經過基礎支持中的「獲取access_token」接口來獲取到的普通access_token調用。
關於UnionID機制數組
一、請注意,網頁受權獲取用戶基本信息也遵循UnionID機制。即若是開發者有在多個公衆號,或在公衆號、移動應用之間統一用戶賬號的需求,須要前往微信開放平臺(open.weixin.qq.com)綁定公衆號後,纔可利用UnionID機制來知足上述需求。 二、UnionID機制的做用說明:若是開發者擁有多個移動應用、網站應用和公衆賬號,可經過獲取用戶基本信息中的unionid來區分用戶的惟一性,由於同一用戶,對同一個微信開放平臺下的不一樣應用(移動應用、網站應用和公衆賬號),unionid是相同的。
關於特殊場景下的靜默受權服務器
一、上面已經提到,對於以snsapi_base爲scope的網頁受權,就靜默受權的,用戶無感知; 二、對於已關注公衆號的用戶,若是用戶從公衆號的會話或者自定義菜單進入本公衆號的網頁受權頁,即便是scope爲snsapi_userinfo,也是靜默受權,用戶無感知。
具體而言,網頁受權流程分爲四步:微信
一、引導用戶進入受權頁面贊成受權,獲取code 二、經過code換取網頁受權access_token(與基礎支持中的access_token不一樣) 三、若是須要,開發者能夠刷新網頁受權access_token,避免過時 四、經過網頁受權access_token和openid獲取用戶基本信息(支持UnionID機制)
第二部分:實現微信網頁受權的詳細方法app
下面,咱們來按照這個步驟來實現這個功能:
在確保微信公衆帳號擁有受權做用域(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; }
2、寫受權類:
注意替換成本身的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參數的解釋:
一、以snsapi_base爲scope發起的網頁受權,是用來獲取進入頁面的用戶的openid的,而且是靜默受權並自動跳轉到回調頁的。用戶感知的就是直接進入了回調頁(每每是業務頁面)
二、以snsapi_userinfo爲scope發起的網頁受權,是用來獲取用戶的基本信息的。但這種受權須要用戶手動贊成,而且因爲用戶贊成過,因此無須關注,就可在受權後獲取該用戶的基本信息。
一個小工具,編碼URL用的:http://tool.chinaz.com/tools/urlencode.aspx
至此,受權後,你就能夠顯示用戶信息拉,本身編寫頁面吧。
若是須要生成本身平臺帳號等操做,直接在得到openid後進行。