第一部分:微信受权获取基本信息的介绍php
咱们首先来看看官方的文档怎么说:css
若是用户在微信客户端中访问第三方网页,公众号能够经过微信网页受权机制,来获取用户基本信息,进而实现业务逻辑。html
关于网页受权回调域名的说明java
一、在微信公众号请求用户网页受权以前,开发者须要先到公众平台官网中的开发者中心页配置受权回调域名。请注意,这里填写的是域名(是一个字符串),而不是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的区别说明web
一、以snsapi_base为scope发起的网页受权,是用来获取进入页面的用户的openid的,而且是静默受权并自动跳转到回调页的。用户感知的就是直接进入了回调页(每每是业务页面) 二、以snsapi_userinfo为scope发起的网页受权,是用来获取用户的基本信息的。但这种受权须要用户手动赞成,而且因为用户赞成过,因此无须关注,就可在受权后获取该用户的基本信息。 三、用户管理类接口中的“获取用户基本信息接口”,是在用户和公众号产生消息交互或关注后事件推送后,才能根据用户OpenID来获取用户基本信息。这个接口,包括其余微信接口,都是须要该用户(即openid)关注了公众号后,才能调用成功的。
关于网页受权access_token和普通access_token的区别json
一、微信网页受权是经过OAuth2.0机制实现的,在用户受权给公众号后,公众号能够获取到一个网页受权特有的接口调用凭证(网页受权access_token),经过网页受权access_token能够进行受权后接口调用,如获取用户基本信息; 二、其余微信接口,须要经过基础支持中的“获取access_token”接口来获取到的普通access_token调用。
关于UnionID机制api
一、请注意,网页受权获取用户基本信息也遵循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机制)
第二部分:实现微信网页受权的详细方法缓存
下面,咱们来按照这个步骤来实现这个功能:
在确保微信公众帐号拥有受权做用域(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> * 开发人员: 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. 网页受权信息 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; } }
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; } }
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请求的工具:
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; }
2、写受权类:
注意替换成本身的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); } }
3、受权后,显示信息的页面
<%@ 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>
4、写请求的路径
<?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>
5、替换官方的连接成咱们的方法路径:
官方的请求连接:
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编码(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
6、复制上面替换好的连接,而后丢进浏览器,而后用微信来扫一扫。会出现如下的效果:
说明,恭喜你,咱们获取到了用户的基本信息。
其余文章关联:
第一篇:微信公众平台开发实战Java版之了解微信公众平台基础知识以及资料准备
第二篇 :微信公众平台开发实战Java版之开启开发者模式,接入微信公众平台开发
第三篇 :微信公众平台开发实战Java版之请求消息,响应消息以及事件消息类的封装
第四篇 :微信公众平台开发实战Java版之完成消息接受与相应以及消息的处理
第五篇 :微信公众平台开发实战Java版之如何获取公众号的access_token以及缓存access_token
第六篇 :微信公众平台开发实战Java版之如何自定义微信公众号菜单
第七篇 :微信公众平台开发实战Java版之如何获取微信用户基本信息
第八篇 :微信公众平台开发实战Java版之如何网页受权获取用户基本信息