主要用于在用户通过手机端微信访问第三方H5页面时获取用户的身份信息(openId,昵称,头像,所在地等。。)可用来实现微信登录、微信账号绑定、用户身份鉴权等功能。
1、需要有一个公众号,拿到AppID和AppSecret;
注意:AppID和AppSecret在以前的版本中是可以直接显示的,但改版之后AppID能够直接看到而AppSecret则无法得知。所以这个AppSecret得保管好了,要不然只能重置了(重置可能会影响你的在线业务和以前程序的正常运行,重置成功后会在页面中显示你一定要保存好了,这个页面关了之后就无法得知了)
2、将你要部署下面代码程序电脑的ip(在浏览器百度搜索"ip"即可知道你自己的ip)添加到白名单中,否则无法获取到access_token。
3、进入公众号开发者中心页配置授权回调域名。具体位置:设置-公众号设置-功能设置-网页授权域名
注意:这里仅需填写全域名(如 www.qq.com、www.baidu.com),勿加 http:// 等协议头及具体的地址字段
这个域名需要是一个备案过的域名。这个条件比较难办,幸好热心的网友qydev为我们无私地提供了一个备案过的域名,我们可以通过使用Ngrok来虚拟一个域名映射到本地开发环境,简直是web开发神器啊。。
qydev版Ngrok使用说明及下载地址:https://blog.csdn.net/qq_26101151/article/details/53114496?locationNum=4&fps=1
启动命令:ngrok -config=ngrok.cfg -subdomain xiaoqiang 8080
本文以xiaoqiang.tunnel.qydev.com域名为例
番外:我也是服了,前几天还好使,今天就不行了。过了几天又好使了,这么不稳定啊。。。
注意:网上有好多都是通过使用Ngrok来虚拟一个域名映射到本地开发环境,可是Ngrok有好多版本,只有上面的这个qydev版的lovebread.tunnel.qydev.com域名能成功。
(1)官方版本失败
(2)Sunny版本,配置了个免费的域名http://suliuu.free.idcfengye.com
这两个版本的Ngrok简单使用可参考:https://blog.csdn.net/liu_005/article/details/79557818和https://blog.csdn.net/qq_33404395/article/details/80788233
注意:
1.改版之后还得把它规定的一个txt文件放到你域名根目录下,所以说这步先别管,先进行下面的内容(在eclipse中把整个项目搭建好后再把这个txt文件下载后放到你的项目中去,否则这步根本通过不了)
2.把上面说的TXT文件放到下图的位置,如果你访问到http://xiaoqiang.tunnel.qydev.com/MP_verify_xxxxxxxxxxxxxxxx.txt则说明你放对了
4.在mysql库中建相应的表并插入数据:
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`account` char(10) DEFAULT NULL,
`openid` char(50) DEFAULT NULL,
`password` char(10) DEFAULT NULL,
`nickname` char(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk;
INSERT INTO user VALUES(1,'xiaoqiang','','123456','小强签名设计');
5.如果嫌手机上测试麻烦,可以使用微信官方提供的web开发者工具直接在浏览器中进行调试。
前提是需要在微信公众号中绑定开发者账号:登录公众号-人员设置-绑定运营者微信号
使用说明及下载地址:https://mp.weixin.qq.com/wiki?action=doc&id=mp1455784140&t=0.7272727088156665&token=&lang=zh_CN#6
1.项目结构:
在eclipse中右键新建“Dynamic Web Project”
本文所需jar包下载地址:https://download.csdn.net/download/m0_37739193/10854643
web.xml:
WxAuth
index.html
index.htm
index.jsp
default.html
default.htm
default.jsp
wxCallBack
com.xingshang.servlet.CallBackSerclet
dbUrl
jdbc:mysql://127.0.0.1:3306/xiaoqiang
driverClassName
com.mysql.jdbc.Driver
userName
root
passWord
123456
1
wxCallBack
/wxCallBack
AuthUtil:
package com.xingshang.util;
import java.io.IOException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import net.sf.json.JSONObject;
public class AuthUtil {
public static final String APPID = "xxxxxxxxxxxxxxxxxx";
public static final String APPSECRET = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
public static JSONObject doGetJson(String url) throws ClientProtocolException, IOException{
JSONObject jsonObject = null;
//首先初始化HttpClient对象
// DefaultHttpClient client = new DefaultHttpClient();
HttpClient client = HttpClientBuilder.create().build();//获取DefaultHttpClient请求,上面注释的那行已经不建议使用
//通过get方式进行提交
HttpGet httpGet = new HttpGet(url);
//通过HTTPclient的execute方法进行发送请求
HttpResponse response = client.execute(httpGet);
//从response里面拿自己想要的结果
HttpEntity entity = response.getEntity();
if(entity != null){
String result = EntityUtils.toString(entity,"UTF-8");
jsonObject = JSONObject.fromObject(result);
}
//把链接释放掉
httpGet.releaseConnection();
return jsonObject;
}
}
Java实现:
1、引导用户进入授权页面同意授权,获取code
这一步其实就是将需要授权的页面url拼接到微信的认证请求接口里面,比如需要用户在访问页面时进行授权认证
其中的scope参数有两个值:
snsapi_base:只能获取到用户openid。好处是静默认证,无需用户手动点击认证按钮,感觉上像是直接进入网站一样。
snsapi_userinfo:可以获取到openid、昵称、头像、所在地等信息。需要用户手动点击认证按钮。并且,即使在未关注的情况下,只要用户授权,也能获取其信息
LoginServlet:
package com.xingshang.servlet;
import java.io.IOException;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.xingshang.util.AuthUtil;
/**
* 入口地址
* @author Administrator
*/
@WebServlet("/wxLogin")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//第一步:引导用户进入授权页面同意授权,获取code
//回调地址
// String backUrl = "http://xiaoqiang.tunnel.qydev.com/WxAuth/callBack"; //第1种情况使用
String backUrl = "http://xiaoqiang.tunnel.qydev.com/WxAuth/wxCallBack"; //第2种情况使用,这里是web.xml中的路径
//授权页面地址
String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+AuthUtil.APPID
+ "&redirect_uri="+URLEncoder.encode(backUrl, "UTF-8") //URLEncoder.encode(backUrl)已不建议使用。这里推荐一个encode转义的网页工具https://www.jianshu.com/p/f0966f28ddac
+ "&response_type=code"
+ "&scope=snsapi_userinfo"
+ "&state=STATE#wechat_redirect";
//重定向到授权页面
response.sendRedirect(url);
}
}
2、通过第一步获取的code换取网页授权access_token(与基础支持中的access_token不同)
(1)网页授权的access_token在每次获取openID时一起更新,在接口调用频次限制中为“无上限”。
(2)基础access_token一般限制为2000次/日,需要自己保存起来并定时更新。
这一步需要在控制器中获取微信回传给我们的code,通过这个code来请求access_token,通过access_token和openid获取用户基本信息。
CallBackSerclet:
package com.xingshang.servlet;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.xingshang.util.AuthUtil;
import net.sf.json.JSONObject;
/**
* 回调地址
* @author Administrator
*/
//@WebServlet("/callBack")
//第1种情况的两种写法
//@WebServlet(urlPatterns = "/callBack")
public class CallBackSerclet extends HttpServlet {
private static final long serialVersionUID = 1L;
private String dbUrl;
private String driverClassName;
private String userName;
private String passWord;
private Connection conn =null;
private PreparedStatement ps =null;
private ResultSet rs = null;
//初始化数据库
@Override
public void init(ServletConfig config) throws ServletException {
//加载驱动
try {
this.dbUrl = config.getInitParameter("dbUrl");
this.driverClassName = config.getInitParameter("driverClassName");
this.userName = config.getInitParameter("userName");
this.passWord = config.getInitParameter("passWord");
Class.forName(driverClassName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//第二步:通过code换取网页授权access_token
//从request里面获取code参数(当微信服务器访问回调地址的时候,会把code参数传递过来)
String code = request.getParameter("code");
System.out.println("code:"+code);
//获取code后,请求以下链接获取access_token
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + AuthUtil.APPID
+ "&secret=" + AuthUtil.APPSECRET
+ "&code=" + code
+ "&grant_type=authorization_code";
//通过网络请求方法来请求上面这个接口
JSONObject jsonObject = AuthUtil.doGetJson(url);
System.out.println("==========================jsonObject"+jsonObject);
//从返回的JSON数据中取出access_token和openid,拉取用户信息时用
String token = jsonObject.getString("access_token");
String openid = jsonObject.getString("openid");
// 第三步:刷新access_token(如果需要)
// 第四步:拉取用户信息(需scope为snsapi_userinfo)
String infoUrl ="https://api.weixin.qq.com/sns/userinfo?access_token=" + token
+ "&openid=" + openid
+ "&lang=zh_CN";
//通过网络请求方法来请求上面这个接口
JSONObject userInfo = AuthUtil.doGetJson(infoUrl);
/** userInfo样例:
* {"openid":"xiaoqiangxxxxxxx_xxxxxxxxxOs","nickname":"小强签名设计","sex":1,"language":"zh_CN","city":"张家口","province":"河北","country":"中国",
* "headimgurl":"http://thirdwx.qlogo.cn/mmopen/vi_32/TGm9MicTQp8icMoA2mFDXIKhsHXfamAVibskR11VwZWu6I2trEb038ufVh6ianSAQz6zDuYEsxicFfElWskVTYmldrA/132",
* "privilege":[],"unionid":"xiaoxx-xiaoGVcRqiangxiaxiaxT"}
*/
//第1种情况:使用微信用户信息直接登录,无需注册和绑定
// request.setAttribute("info", userInfo);
//直接跳转
// request.getRequestDispatcher("/index1.jsp").forward(request, response);
//第2种情况: 将微信与当前系统的账号进行绑定(需将第1种情况和@WebServlet("/callBack")注释掉)
//第一步,根据当前openid查询数据库,看是否该账号已经进行绑定
try {
String nickname = getNickName(openid);
if(!"".equals(nickname)){
//已绑定
request.setAttribute("nickname", nickname);
request.getRequestDispatcher("/index2.jsp").forward(request, response);
}else{
//未绑定
request.setAttribute("openid", openid);
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
//数据库的查询
public String getNickName(String openid) throws SQLException{
String nickName = "";
//创建数据库链接
conn = DriverManager.getConnection(dbUrl, userName, passWord);
String sql = "select nickname from user where openid = ?";
ps = conn.prepareStatement(sql);
ps.setString(1, openid);
rs = ps.executeQuery();
while (rs.next()) {
nickName = rs.getString("nickname"); //昵称
}
//关闭链接
rs.close();
ps.close();
conn.close();
return nickName;
}
//数据库的修改(openid的綁定)
public int updateUser(String account,String password,String openid) throws SQLException{
//创建数据库链接
conn = DriverManager.getConnection(dbUrl, userName, passWord);
String sql = "update user set openid = ? where account = ? and password = ?";
ps = conn.prepareStatement(sql);
ps.setString(1, openid);
ps.setString(2, account);
ps.setString(3, password);
int temp = ps.executeUpdate();
//关闭链接
rs.close();
ps.close();
conn.close();
return temp;
}
//post方法,用来接受登录请求
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String account = request.getParameter("account");
String password = request.getParameter("password");
String openid = request.getParameter("openid");
try {
int temp = updateUser(account, password, openid);
if(temp > 0){
String nickname = getNickName(openid);
request.setAttribute("nickname", nickname);
request.getRequestDispatcher("/index2.jsp").forward(request, response);
System.out.println("账号绑定成功");
}else{
System.out.println("账号绑定失败");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
login.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
Insert title here
index.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
Insert title here
微信公众授权登录
index1.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
Insert title here
登陆成功!
用户昵称:${info.nickname}
用户头像:
index2.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
Insert title here
登陆成功!
用户昵称:${nickname}
电脑:
手机:
注意:不能直接在浏览器中打开,要在微信客户端打开链接http://xiaoqiang.tunnel.qydev.com/WxAuth/
1.如果你已经授权过,电脑客户端比手机客户端(我试了多个手机包括苹果安卓都是)多一个页面(近期你已经授权登陆过XXXXX 自动登录中)。
我分别在用手机和电脑直接打开这两个网址,发现手机这两个地址都是直接跳转到http://xiaoqiang.tunnel.qydev.com/WxAuth/页面(你复制该页面链接可以看到有code参数,如http://xiaoqiang.tunnel.qydev.com/WxAuth/?code=077zL1rm1c26mp0zxIom1YwXqm1zL1r1&state=123),而电脑在第一个地址先弹出“近期你已经授权登陆过XXXXX 自动登录中”页面在跳转到http://xiaoqiang.tunnel.qydev.com/WxAuth/页面,而第二个地址和手机效果一样都是直接跳转到了回调地址。(我怀疑微信官方做了相应的处理,网上也没有搜到相应的说法)
https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxxxxxxxxxxxxxxxxx&redirect_uri=http%3A%2F%2Fxiaoqiang.tunnel.qydev.com%2FWxAuth%2F&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxxxxxxxxxxxxxxxxx&redirect_uri=http%3A%2F%2Fxiaoqiang.tunnel.qydev.com%2FWxAuth%2F &response_type=code&scope=snsapi_base&state=123#wechat_redirect
2.在使用snsapi_base的时候还必须在redirect_uri后面加上%0A,否则跳转后的网页链接还为上面的那个长链接根本没有code参数(虽然复制链接没有code参数,但是程序里却还是能获取到code参数不受影响),也不知道是为什么,垃圾腾讯官方文档啥也没说,而且使用snsapi_base调用接口最终也可以获得用户的其他信息如头像啊,并不像官方文档说的是能获得openID。网上有种说法“如果用户之前进行了snsapi_userinfo授权,那么在一定时间内进行snsapi_base授权拿到的access_token是可以拿到用户信息的,这个时间就不好测试了。”垃圾官方文档啥也没说,还得让人猜,真是服了。
参考:
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
https://www.cnblogs.com/sutao/p/8727019.html