您的位置:首页 > 编程语言 > Java开发

基于Springboot的微信公众号接入、通过网页授权机制获取用户信息

2018-04-11 22:31 621 查看

因为基于Springboot,所以有些地方需要用Spring的方式来解决,本文默认你已经搭建好Maven环境,我们将通过花生壳做内网穿透,接入公众号并通过网页授权机制获取用户基本信息

· 获得一个测试号,通过花生壳将内网映射在外

· 到微信公众平台(测试号)配置接口信息,接入微信公众号

· 通过网页授权机制获取用户信息

一、花生壳是一套动态域名解析服务客户端软件,方便、稳定,今天用它来做内网穿透

因为微信消息发送流程是从用户到微信服务器,再到你的服务器,接收消息后将响应消息发到微信服务器,其再发给用户。这里将自己的电脑作为一台服务器映射在外,方便测试

1、你需要一个测试号,它具有所有接口的使用权限,登录即可使用:微信公众平台接口测试帐号

2、注册花生壳,做内网穿透:点我



之后进入网页,点击添加映射,进入添加页面:



注意 :微信只接受80端口,所以映射类型选择网站80端口

我本机访问地址为127.0.0.1:80,映射后得到外网访问地qiangqiangchen.55555.io

启动服务器(我用的tomcat),即可访问

至此,你已经获得一个所有人都能访问的地址了

二、到微信公众平台(测试号)配置接口信息,接入微信公众号

如图,这里的URL即我们刚刚映射到外网的地址,Token为随意填写,提交信息后,微信服务器将发送GET请求到你填写的服务器地址URL上,GET请求携带四个参数,我们需要将其中三个参数做字典排序、SHA-1加密,然后拿它与另一个参数作对比,相同则证明该信息来自于微信服务器,即校验通过



具体实现:新建校验类WxPubController

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class WxPubController {
//此处TOKEN即我们刚刚所填的token
private String TOKEN = "good";

/**
* 接收并校验四个请求参数
* @param signature
* @param timestamp
* @param nonce
* @param echostr
* @return echostr
*/
@RequestMapping(value = "/",method=RequestMethod.GET)
public String checkName(@RequestParam(name="signature")String signature,
@RequestParam(name="timestamp")String timestamp,
@RequestParam(name="nonce")String nonce,
@RequestParam(name="echostr")String echostr){
System.out.println("-----------------------开始校验------------------------");
//排序
String sortString = sort(TOKEN, timestamp, nonce);
//加密
String myString = sha1(sortString);
//校验
if (myString != null && myString != "" && myString.equals(signature)) {
System.out.println("签名校验通过");
//如果检验成功原样返回echostr,微信服务器接收到此输出,才会确认检验完成。
return echostr;
} else {
System.out.println("签名校验失败");
return "";
}
}

/**
* 排序方法
*/
public String sort(String token, String timestamp, String nonce) {
String[] strArray = {token, timestamp, nonce};
Arrays.sort(strArray);
StringBuilder sb = new StringBuilder();
for (String str : strArray) {
sb.append(str);
}

return sb.toString();
}

/**
* 将字符串进行sha1加密
*
* @param str 需要加密的字符串
* @return 加密后的内容
*/
public String sha1(String str) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(str.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
// 字节数组转换为 十六进制 数
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();

} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
}


至此,我们已经接入公众号,接下来就可以实现具体业务了

三、 通过网页授权机制获取用户信息

网页授权机制,开发者文档写的很详细,一共五个步骤:

1 :用户同意授权,获取code
2 :通过code换取网页授权access_token
3 :刷新access_token(如果需要)
4 :拉取用户信息(需scope为 snsapi_userinfo)
5 附:检验授权凭证(access_token)是否有效


在这里,我们只需要三个步骤就可以了,即:

1 :用户同意授权,获取code
2 :通过code换取网页授权access_token
3 :拉取用户信息(需scope为 snsapi_userinfo)


接下来,我们来实现它:

1、用户同意授权,获取code

1.1>>>新建UserInfoUtil,得到一个URL,这个URL是让用户去访问的,当用户进入该URL后,如果用户同意授权,微信服务器收到请求,页面将跳转至 redirect_uri/?code=CODE&state=STATE,也就是你的外网访问网址,携带两个参数,一个为code,一个为state

public class UserInfoUtil {

//获取code的请求地址
public static String Get_Code = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=STAT#wechat_redirect";
//替换字符串
public static String getCode(String APPID, String REDIRECT_URI,String SCOPE) {
return String.format(Get_Code,APPID,REDIRECT_URI,SCOPE);
}

public static void main(String[] args) {
String  REDIRECT_URI = "http://qiangqiangchen.55555.io/vote.do";
String SCOPE = "snsapi_userinfo";
//appId
String appId = "wx69b8accef39ebb40";

String getCodeUrl = getCode(appId, REDIRECT_URI, SCOPE);
System.out.println("getCodeUrl:"+getCodeUrl);
}


运行main函数,得到

getCodeUrl:https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx69b8accef39ebb40&redirect_uri=http://qiangqiangchen.55555.io/vote.do&response_type=code&scope=snsapi_userinfo&state=STAT#wechat_redirect

值得注意的是,我这里的scope=snsapi_userinfo,微信文档说的很明确,它还有另一个可选参数snsapi_base,那么他们有什么区别呢?

区别就是当scope=snsapi_userinfo的时候,弹出询问授权页面,用户同意,才可进一步获取用户基本信息,而后者,不会出现询问页面,但只能获取用户openid(用户的唯一标识)

好了,接下来,我们需要让用户去访问这个URL,那么,该怎么实现呢?

分两步:

首先,需要到自己的微信公众号,到接口权限表里找到网页账号,并修改之,如图:



说明一下,这里的域名即我们通过花生壳映射出去的全域名,比如你的域名是aaaaa.cn,那么aaaaa.cn/bbbb/cccc也是可以弹出权限询问页面的



其次,我们需要引导用户通过getCodeUrl来进入我们的首页,所以,我们要将此URL加到微信菜单上去

这里所说菜单,指的是公众号最下边的那一列———



至于怎么加进去,可以用代码方式,也可以直接设置,但目前测试号没有菜单设置功能,如果需要代码设置,请点击百度这里不做介绍

1.2>>>获取code

这里说明一下:code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。

微信服务器收到用户请求,

@Controller
public class Ind
e9fc
exController extends BaseController{
@RequestMapping("/vote.do")
public ModelAndView listVote(@RequestParam(name="code",required=false)String code,
@RequestParam(name="state")String state) {

System.out.println("-----------------------------收到请求,请求数据为:"+code+"-----------------------"+state);
//……………………业务代码,此处省略
return new ModelAndView("mypages/index", model);

}

}


2、通过code换取网页授权access_token

2.1>>>新建UserInfoUtil

public class UserInfoUtil {

//获取code的请求地址
public static String Get_Code = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=STAT#wechat_redirect";
//替换字符串
public static String getCode(String APPID, String REDIRECT_URI,String SCOPE) {
return String.format(Get_Code,APPID,REDIRECT_URI,SCOPE);
}

//获取Web_access_tokenhttps的请求地址
public static String Web_access_tokenhttps = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
//替换字符串
public static String getWebAccess(String APPID, String SECRET,String CODE) {
return String.format(Web_access_tokenhttps, APPID, SECRET,CODE);
}

}


2.2>>>因为需要用https的方式请求微信服务器,我们还需要一个以https方式发送请求的工具类:

package com.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class HttpsUtil {

/**
* 以https方式发送请求并将请求响应内容以String方式返回
*
* @param path   请求路径
* @param method 请求方法
* @param body   请求数据体
* @return 请求响应内容转换成字符串信息
*/
public static String httpsRequestToString(String path, String method, String body) {
if (path == null || method == null) {
return null;
}

String response = null;
InputStream inputStream = null;
InputStreamReader inputStreamReader = null;
BufferedReader bufferedReader = null;
HttpsURLConnection conn = null;
try {
//创建SSLConrext对象,并使用我们指定的信任管理器初始化
TrustManager[] tm = {new JEEWeiXinX509TrustManager()};
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());

//从上述对象中的到SSLSocketFactory
SSLSocketFactory ssf = sslContext.getSocketFactory();

System.out.println(path);

URL url = new URL(path);
conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(ssf);

conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);

//设置请求方式(git|post)
conn.setRequestMethod(method);

//有数据提交时
if (null != body) {
OutputStream outputStream = conn.getOutputStream();
outputStream.write(body.getBytes("UTF-8"));
outputStream.close();
}

//将返回的输入流转换成字符串
inputStream = conn.getInputStream();
inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}

response = buffer.toString();
} catch (Exception e) {

} finally {
if (conn != null) {
conn.disconnect();
}
try {
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
} catch (IOException execption) {

}
}
return response;
}

}

class JEEWeiXinX509TrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}

public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}

public X509Certificate[] getAcceptedIssuers() {
return null;
}
}


2.3>>>接下来,换取access_token,需要说明一下,这里的access_token和基础access_token并不相同,此处指的是专门在网页中使用的access_token,我们暂且称之为web_access_token

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.entity.oDCon;
import com.entity.vOpt;
import com.entity.vPro;
import com.entity.voDet;
import com.github.pagehelper.PageInfo;
import com.service.VoteOpService;
import com.util.Const;
import com.util.HttpsUtil;
import com.util.UserInfoUtil;

@Controller
public class IndexController extends BaseController{

@RequestMapping("/vote.do")
public ModelAndView listVote(@RequestParam(name="code",required=false)String code,
@RequestParam(name="state")String state) {

System.out.println("-----------------------------收到请求,请求数据为:"+code+"-----------------------"+state);

//通过code换取网页授权web_access_token
if(code != null || !(code.equals(""))){

String APPID = Const.appId;
String SECRET = Const.appSecret;
String CODE = code;
String WebAccessToken = "";
String openId  = "";
//String nickName,sex,openid = "";
String  REDIRECT_URI = "http://qiangqiangchen.55555.io/vote.do";
String SCOPE = "snsapi_userinfo";

String getCodeUrl = UserInfoUtil.getCode(APPID, REDIRECT_URI, SCOPE);
System.out.println("---------------getCodeUrl--------------"+getCodeUrl);

//替换字符串,获得请求URL
String token = UserInfoUtil.getWebAccess(APPID, SECRET, CODE);
System.out.println("----------------------------token为:"+token);
//通过https方式请求获得web_access_token
String response = HttpsUtil.httpsRequestToString(token, "GET", null);
JSONObject jsonObject = JSON.parseObject(response);
System.out.println("jsonObject------"+jsonObject);
if (null != jsonObject) {
try {

WebAccessToken = jsonObject.getString("access_token");
openId = jsonObject.getString("openid");
System.out.println("获取access_token成功-------------------------"+WebAccessToken+"----------------"+openId);

} catch (JSONException e) {
WebAccessToken = null;// 获取code失败
System.out.println("获取WebAccessToken失败");
}
}
}

//此处业务代码省略 ^_^
return new ModelAndView("mypages/index", model);

}

}


至此,我们拿到WebAccessToken、openId,就算完成这一步骤了。

3 :拉取用户信息

3.1>>>在UserInfoUtil中加入:

//拉取用户信息的请求地址
public static String User_Message = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN";
//替换字符串
public static String getUserMessage(String access_token, String openid) {
return String.format(User_Message, access_token,openid);
}


3.2>>>在IndexController中加入拉取用户信息的代码

@Controller
public class IndexController extends BaseController{

//required=false的意思就是可以不传此参数
@RequestMapping("/vote.do")
public ModelAndView listVote(@RequestParam(name="code",required=false)String code,
@RequestParam(name="state")String state) {

System.out.println("-----------------------------收到请求,请求数据为:"+code+"-----------------------"+state);

//通过code换取网页授权web_access_token
if(code != null || !(code.equals(""))){

String APPID = Const.appId;
String SECRET = Const.appSecret;
String CODE = code;
String WebAccessToken = "";
String openId  = "";
String nickName,sex,openid = "";
String  REDIRECT_URI = "http://qiangqiangchen.55555.io/vote.do";
String SCOPE = "snsapi_userinfo";

String getCodeUrl = UserInfoUtil.getCode(APPID, REDIRECT_URI, SCOPE);
System.out.println("---------------getCodeUrl--------------"+getCodeUrl);

//替换字符串,获得请求URL
String token = UserInfoUtil.getWebAccess(APPID, SECRET, CODE);
System.out.println("----------------------------token为:"+token);
//通过https方式请求获得web_access_token
String response = HttpsUtil.httpsRequestToString(token, "GET", null);
JSONObject jsonObject = JSON.parseObject(response);
System.out.println("jsonObject------"+jsonObject);
if (null != jsonObject) {
try {
WebAccessToken = jsonObject.getString("access_token");
openId = jsonObject.getString("openid");
System.out.println("获取access_token成功-------------------------"+WebAccessToken+"----------------"+openId);

//-----------------------拉取用户信息...替换字符串,获得请求URL
String userMessage = UserInfoUtil.getUserMessage(WebAccessToken, openId);
System.out.println(" userMessage==="+ userMessage);
//通过https方式请求获得用户信息响应
String userMessageResponse = HttpsUtil.httpsRequestToString(userMessage, "GET", null);

JSONObject userMessageJsonObject = JSON.parseObject(userMessageResponse);

System.out.println("userMessagejsonObject------"+userMessageJsonObject);

if (userMessageJsonObject != null) {
try {
//用户昵称
nickName = userMessageJsonObject.getString("nickname");
//用户性别
sex = userMessageJsonObject.getString("sex");
sex = (sex.equals("1")) ? "男":"女";
//用户唯一标识
openid = userMessageJsonObject.getString("openid");

System.out.println("用户昵称------------------------"+nickName);
System.out.println("用户性别------------------------"+sex);
System.out.println("用户的唯一标识-------------------"+openid);
} catch (JSONException e) {
System.out.println("获取userName失败");
}
}

} catch (JSONException e) {
WebAccessToken = null;// 获取code失败
System.out.println("获取WebAccessToken失败");
}

}
}

return new ModelAndView("mypages/index", model);

}

}


当启动tomcat后,在手机上打开微信公众号,点击公众号菜单,访问页面,获得用户基本信息的json数据



至此,最初目的已经实现,谢谢你的耐心阅读,大神多批评~本文部分代码参考大神孤傲苍狼所写: 微信开发学习总结
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐