前言:
这些年微信异常火爆,甚至爷爷奶奶辈的人都会用微信。所以很多网站都支持用微信账号登录,那么接下来就来看看如何当用户通过微信访问我们的链接时,我们如何获取到该用户的微信公开资料,以及如何用eclipse 远程调试代码。
(本文的前提是有一个可以在阿里云上运行的web项目)。
一、微信测试号的连接与申请:
1、编写servlet响应测试号:
当用户关注我们的测试号(公众号)时,微信测试号便会发请求到我们配置好的URL中,我们现在就要编写这个响应的URL对应的程序,这样才能顺利连通。
需要编写两个类:
SignUtil.java
/**
* 微信请求校验工具类
* 校验在微信公众平台填写的配置
*/
public class SignUtil {
// 与接口配置信息中的Token要一致
private static String token = "meilianMall";
/**
* 验证签名
*
* @param signature
* @param timestamp
* @param nonce
* @return
*/
public static boolean checkSignature(String signature, String timestamp, String nonce) {
String[] arr = new String[] { token, timestamp, nonce };
// 将token、timestamp、nonce三个参数进行字典序排序
Arrays.sort(arr);
StringBuilder content = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
MessageDigest md = null;
String tmpStr = null;
try {
md = MessageDigest.getInstance("SHA-1");
// 将三个参数字符串拼接成一个字符串进行sha1加密
byte[] digest = md.digest(content.toString().getBytes());
tmpStr = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
content = null;
// 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
}
/**
* 将字节数组转换为十六进制字符串
*
* @param byteArray
* @return
*/
private static String byteToStr(byte[] byteArray) {
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return strDigest;
}
/**
* 将字节转换为十六进制字符串
*
* @param mByte
* @return
*/
private static String byteToHexStr(byte mByte) {
char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
char[] tempArr = new char[2];
tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
tempArr[1] = Digit[mByte & 0X0F];
String s = new String(tempArr);
return s;
}
}
这是一个工具类,主要作用注释里有写,这里就不重复了。
接下来就需要编写一个Controller去响应微信测试号。
WechatController.java
/**
* 获取微信信息的controller
* @author zhu
*
*/
@Controller
// 一会儿微信测试号url就设置上这个路由
@RequestMapping("wechat")
public class WechatController {
private static Logger log = LoggerFactory.getLogger(WechatController.class);
@RequestMapping(method = {RequestMethod.GET})
public void doGet(HttpServletRequest request, HttpServletResponse response) {
log.debug("########################## wechat get ###########################");
// 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数,nonce参数
String signature = request.getParameter("signature");
// 时间戳
String timestamp = request.getParameter("timestamp");
// 随机数
String nonce = request.getParameter("nonce");
// 随机数字符串
String echostr = request.getParameter("echostr");
// 通过检验signature对请求进行校验,若成功就返回echostr
PrintWriter out = null;
try {
out = response.getWriter();
if (SignUtil.checkSignature(signature, timestamp, nonce)) {
log.debug("######################### success ##########################");
out.print(echostr);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (out != null) {
out.close();
}
}
}
}
这个类有个路由,等下在微信公众平台配置时就填写这个路由。
完成以上两个类,把程序重新部署到阿里云上。
2、微信测试号申请:
①、访问微信测试号申请。
②、微信扫码登录公众平台,然后就会出现如下界面,并做相关配置:
还有一处需要配置,如下图:
③、关注测试号:
关注了测试号的用户信息就可以在这里获取到。
3、在Java中获取微信用户的信息:
刚才获取了关注测试号的用户信息,但是只是微信官方获取到了,我们并没有在程序中获取到用户的信息,所以我们现在就需要编写程序把微信测试号中获取到的用户信息获取到Java程序中,需要编写2个工具类2个dto实体类和1个controller:
①、代码编写:
MyX509TrustManager.java
这个工具类是证书信任管理器。
/**
* 证书信任管理器
* @author zhu
*
*/
public class MyX509TrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
WechatUtil.java
这个工具类是用来获取用户信息的。
/**
* 提交请求给微信,来获取用户信息
*
* @author zhu
*
*/
public class WechatUtil {
private static Logger log = LoggerFactory.getLogger(WechatUtil.class);
/**
* 获取UserAccessToken 实体类
*
* @param code
* @return
* @throws IOException
*/
public static UserAccessToken getUserAccessToken(String code) throws IOException {
// 测试号信息里的appId
String appId = "你的appId";
log.debug("appId" + appId);
// 测试号信息里的appsecret
String appsecret = "你的appsecret";
log.debug("appsecret" + appsecret);
// 根据传入的code,拼接出访问定义好的微信接口的url
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId + "&secret=" + appsecret
+ "&code=" + code + "&grant_type=authorization_code";
// 向相应的url发送请求获取token
String tokenStr = httpsRequest(url, "GET", null);
log.debug("userAccessToken:" + tokenStr);
UserAccessToken token = new UserAccessToken();
ObjectMapper objectMapper = new ObjectMapper();
try {
// 将json字符串转换为相应的对象
token = objectMapper.readValue(tokenStr, UserAccessToken.class);
} catch (JsonParseException e) {
log.error("获取用户accessToken失败:" + e.getMessage());
e.printStackTrace();
} catch (JsonMappingException e) {
log.error("获取用户accessToken失败:" + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
log.error("获取用户accessToken失败:" + e.getMessage());
e.printStackTrace();
}
if (token == null) {
log.error("获取用户accessToken失败。");
return null;
}
return token;
}
public static WechatUser getUserInfo(String accessToken, String openId) {
// 根据传入的accessToken以及openId拼接出访问微信定义的端口并获取用户信息的url
String url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openId
+ "&lang=zh_CN";
// 访问该url获取用户信息json字符串
String userStr = httpsRequest(url, "GET", null);
log.debug("userInfo:" + userStr);
WechatUser user = new WechatUser();
ObjectMapper objectMapper = new ObjectMapper();
try {
// 将json字符串转换成对象
user = objectMapper.readValue(userStr, WechatUser.class);
} catch (JsonParseException e) {
log.error("获取用户信息失败:" + e.getMessage());
e.printStackTrace();
} catch (JsonMappingException e) {
log.error("获取用户信息失败:" + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
log.error("获取用户信息失败:" + e.getMessage());
e.printStackTrace();
}
if (user == null) {
log.error("获取用户信息失败。");
return null;
}
return user;
}
/**
* 发起https请求并获取结果
*
* @param requestUrl
* 请求地址
* @param requestMethod
* 请求方式
* @param outputStr
* 提交的数据
* @return
*/
public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
StringBuffer buffer = new StringBuffer();
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 httpUrlConn = (HttpsURLConnection) url.openConnection();
httpUrlConn.setSSLSocketFactory(ssf);
httpUrlConn.setDoOutput(true);
httpUrlConn.setDoInput(true);
httpUrlConn.setUseCaches(false);
// 设置请求方式
httpUrlConn.setRequestMethod(requestMethod);
if ("GET".equalsIgnoreCase(requestMethod)) {
httpUrlConn.connect();
}
// 当有数据需要提交时
if (null != outputStr) {
OutputStream outputStream = httpUrlConn.getOutputStream();
// 注意编码格式,防止中文乱码
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 将返回的输入流转换成字符串
InputStream inputStream = httpUrlConn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
bufferedReader.close();
inputStreamReader.close();
// 释放资源
inputStream.close();
inputStream = null;
httpUrlConn.disconnect();
log.debug("https buffer:" + buffer.toString());
} catch (ConnectException e) {
log.error("连接超时");
} catch (Exception e) {
log.error("https request error:{}" + e);
}
return buffer.toString();
}
}
WechatUser.java
这个dto实体类是用来接收微信传过来的用户信息的。
/**
* 微信用户实体类,用来接收昵称和openId等信息
* @author zhu
*
*/
public class WechatUser implements Serializable {
/**
*
*/
private static final long serialVersionUID = -6210131950300179472L;
//openId,标识该公众号下面的该用户的唯一Id
@JsonProperty("openid")
private String openId;
//用户昵称
@JsonProperty("nickname")
private String nickName;
//性别
@JsonProperty("sex")
private int sex;
//省份
@JsonProperty("province")
private String province;
//城市
@JsonProperty("city")
private String city;
//区
@JsonProperty("country")
private String country;
//头像图片地址
@JsonProperty("headimgurl")
private String headimgurl;
//语言
@JsonProperty("language")
private String language;
//用户权限,这里没什么用
@JsonProperty("privilege")
private String [] privilege;
}
UserAccessToken.java
这个dto实体类是用来接收微信传过来的access _token等信息的。
/**
* 用户accessToken的实体类,用来接收access_token和openId等信息
*
* @author zhu
*
*/
public class UserAccessToken {
// 获取到的凭证
@JsonProperty("access_token")
private String accessToken;
// 凭证有效时间
@JsonProperty("expires_in")
private String expiresIn;
// 更新令牌,获取下一次令牌
@JsonProperty("refresh_token")
private String refreshToken;
// 该用户在此公众号下的身份标识,对此微信号具有唯一性
@JsonProperty("openid")
private String openId;
// 权限范围,这个项目中可以省略
@JsonProperty("scope")
private String scope;
}
WechatLoginController.java
这个controller作用就是把关注了测试号的用户信息获取到程序中。如果获取到了用户信息,就跳转到index页面。
@Controller
@RequestMapping("/wechatlogin")
public class WechatLoginController {
private static Logger log = LoggerFactory.getLogger(WechatLoginController.class);
@RequestMapping(value = "/logincheck",method = {RequestMethod.GET})
public String doGet(HttpServletRequest request, HttpServletResponse response) {
log.debug("######################### login get ############################");
// 获取微信公众号传递过来的code,通过code获取access_token,进而获取用户信息
String code = request.getParameter("code");
// 这个state可以传递我们自定义的信息,方便程序调用,这里可以不用
// String roleType = request.getParameter("state");
log.debug("######################### code:" + code + "######################");
WechatUser user = null;
String openId = null;
if (null != code) {
UserAccessToken token;
try {
// 通过code获取access_token
token = WechatUtil.getUserAccessToken(code);
log.debug("#################### token:" + token + "####################");
// 通过token获取accessToken
String accessToken = token.getAccessToken();
// 通过token获取openId
openId = token.getOpenId();
// 通过access_token和openId获取用户昵称等信息
user = WechatUtil.getUserInfo(accessToken, openId);
log.debug("################ user:" + user.toString() + "###############");
request.getSession().setAttribute("openId", openId);
} catch (Exception e) {
log.error("################# 获取用户信息失败! ###################");
e.printStackTrace();
}
}
// ######## todo begin ########
// 获取到openId后,通过它查看数据库中是否有对应记录
// 没有的话可以在这里创建,直接实现微信与网站的无缝对接
// ######## todo end ########
if (user != null) {
return "frontend/index";
} else {
return null;
}
}
}
完成代码编写后把项目重新部署到阿里云服务器上。
②、测试:
微信关注测试号后,在手机微信app或者微信web开发工具中访问如下链接:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=你的appId&redirect_uri=http://阿里云服务器公网IP/meilianMall/wechatlogin/logincheck&role_type=1&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect
访问了这个链接,如果跳转到了我们指定的index页面,说明就成功了。
注:建议使用手机微信访问,我用微信web开发工具访问老慢了,半天没反应。
③、查看日志:
因为我们在程序中加入了log,所以可以进入到tomcat的logs/webapps目录下查看debug.log:
可以看到确实获取到了微信用户的信息,只不过出现了乱码Ծ‸Ծ,待解决。
二、eclipse远程调试:
1、新建用户:
服务器的root账户不支持远程调试,所以新建账户work,停掉root用户启动的tomcat,在work账户中重装tomcat。
tomcat的安装这里不再讲解了,有疑问可以参考:centos中开发环境的安装。
2、配置:
因为微信公众平台那里填写的url不能带端口信息,80是默认端口,可以不写,所以要把8080端口修改成80端口,但是非root用户的程序端口不能低于1000,所以端口不能改,还是8080。解决办法两种,通常是用nginx做转发,但是nginx不太会用,那么就用centos的iptables做转发。
问题又来了,centos7之后防火墙是firewall,没有iptables,解决办法如下:
①、切换回root账户:su root
②、停掉firewall:firewall systemctl stop firewalld.service
systemctl disable firewalld.service
③、安装iptables:yum install iptables-services
④、将80请求转发到8080:
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
⑤、让设置永久生效:service iptables save
systemctl restart iptables.service
systemctl enable iptables.service
以上5步就可以将80的请求转发给8080了。
但是要用eclipse进行远程调试,还需要进行如下配置:
⑥、修改catalina.sh:
修改tomcat的bin目录下的catalina.sh,加上下面的配置:
CATALINA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,address=8888,server=y,suspend=n"
配置完重启tomcat,然后执行ps -ef | grep tomcat
就可以看到配置生效了。
⑦、回到eclipse进行调试:
将要调试的方法打上断点:
然后选择debug configuration,进行远程调试配置:
这个时候再用微信访问:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=你的appId&redirect_uri=http://阿里云服务器公网IP/meilianMall/wechatlogin/logincheck&role_type=1&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect
eclipse就会进入刚才断点方法进行调试,按F6就可以一步步的往下调试。
总结:
获取微信用户信息整个流程:编写两个类用来响应微信测试号,然后编写5个类,把微信测试号中的用户信息获取到项目中。当用户关注了测试号后访问我们给出的链接,我们就能把他的公开信息获取到项目中,然后转发到指定的页面。这个链接就是由appId、我们定义的controller中的路由等组成,可以把这个链接做成一个登录按钮,就可以实现微信登录。
微信公众号的操作和测试号是一样的。