微信验证以及登录流程
前言: 现在大多数网站项目都支持微信登录,付款,以及支付宝登录付款,这种方式也是能够让用户很快速便捷的注册本网站的账号,进行登录,以及后续的操作。相信小伙伴们看完之后,会对怎么与微信或者支付宝服务器打交道有很深的理解,就当做是一个敲门砖吧。那么本篇主要针对微信的验证登录来打开通往微信服务器的大门,下一篇会主要讲解一下支付宝付款验证对接。
本篇为原创,转载请标出处:http://www.cnblogs.com/gudu1/p/8087130.html
下面我会使用大量的代码,代码中添加完整的注释来解释与微信服务器对接的详细步骤:
首先呢,需要有一个微信公众号,当然是收费的,不过呢,微信给我们开发人员提供的有微信公众测试号来进行开发测试,虽然提供功能不是很全,开发测试是够用了,还是很到位的
微信测试号地址:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
扫码登陆后呢,就是下面这个样子了:
上面这个appID 和 appsecret 很重要,正常的公众号appsecret 一定不要暴露出去,不然是很危险的。
>> URL: 规则 http://服务器地址或者域名/项目验证接口路由,比如: http://www.mmm.cn/testProject/weChat.do,这个weChat.do 就是要跟微信服务器验证对接的接口
>> Token:这个是由用户自己来定义,主要是我们的项目跟微信服务器对接时候需要进行SHA1加密和解密的字段之一,详细请看官方文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319
>> 域名: 这里就需要使用你自己的域名了,当然域名是要指向能够被外网访问到的服务器地址。
当我们在接口配置信息这里点击提交,微信服务器就会向我们的服务器接口发送消息来进行验证是否正确。
当然还需要修改一处,这里需要的还是服务器域名:
接下来呢,就要看我们的程序跟微信验证的代码流程:
这里呢,我使用的 Spring 来做:
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.util.wechat.SignUtil;
@Controller
@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("weixin 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("weixin get success....");
// 我们验证通过,确定是微信发过来的消息,就将 随机字符串原样返回给微信
out.print(echostr);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null)
out.close();
}
}
}
微信发过来的消息会到这里,可以看到我们的Controller的RequestMappiing 路由为 "wechat", 这就是我们在微信测试号中配置的接口路由地址,很明显,这是一个Servlet ,请求的是doGet 方法。
上面代码只是我们的入口,主要验证逻辑在下面:
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
* 微信请求校验工具类
*/
public class SignUtil {
// 与接口配置信息中的Token要一致
private static String token = "mytoken";
/**
* 验证签名
*
* @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 {
// 获得SHA-1 加密的对象
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;
}
}
细心看的小伙伴肯定已经注意到了, 在方法上面有一个字段就是token,这里一定要跟微信测试号中配置的保持 一致,不相信的同学可以测试着改一个不一致的,保证过不了。
这里的流程就是,微信首先 把我们的 token+时间戳+随机数 进行一次SHA-1 加密,然后发送给我们的程序,然后我们再进行一次SHA-1加密,然后跟微信发送过来的加密数据进行匹配。我们想象一下,有人故意破坏我们的程序,然后模拟发送数据,能吗?答案是能,不过他首先得拿到我们的token字段,才能使我们的程序给出正确响应,所以这里的token 配置一定要和微信测试号中配置保持一致。
好了,既然极影和微信服务器连通了,那么就开始我们的业务需求登录操作吧。
用户使用微信登录,肯定是需要向微信服务器发送登录请求,然后微信再回调我们的接口,接口获取微信服务器传递过来的信息,确定用户是否登录成功,包括用户的一些数据,比如:昵称,头像地址 等等。
业务流程:
第一步:请求CODE
首先要知道这些接口是谁定义的,肯定是微信定义的了,那我们怎么知道向哪个接口发送消息,这就需要查看微信给我们提供的官方文档了。
https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect , 该地址是程序向微信发送的第一条请求,请求参数中携带code
下面来分析一下这条URL:
appid:应用唯一标识,也就是微信测试号中的第一条 appid
redirect_uri:使用urlEncode对链接进行处理,也就是微信服务器回调地址,地址就指向我们程序中处理用户是否登录的接口
response_type: 就填写 code
scope:应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即可
state:用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验
接下来就贴一下我的程序代码帮助理解:
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.RequestMethod;import com.util.wechat.WechatUtil;
/**
* 获取关注公众号之后的微信用户信息的接口,如果在微信浏览器里访问
* https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxd7f6c5b8899fba83&redirect_uri=http://o2o.yitiaojieinfo.com/o2o/wechatlogin/logincheck&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect
* 则这里将会获取到code,之后再可以通过code获取到access_token 进而获取到用户信息
* @author xiangze
*
*/
@Controller
@RequestMapping("wechatlogin")
public class WechatLoginController {
private static Logger log = LoggerFactory.getLogger(WechatLoginController.class);
private static final String FRONTEND = "1";
private static final String SHOPEND = "2";
@Autowired
private PersonInfoService personInfoService;
@Autowired
private WechatAuthService wechatAuthService;
@RequestMapping(value = "/logincheck", method = { RequestMethod.GET })
public String doGet(HttpServletRequest request, HttpServletResponse response) {
log.debug("weixin login get...");
// 获取微信公众号传输过来的code,通过code可获取access_token,进而获取用户信息
String code = request.getParameter("code");
// 这个state可以用来传我们自定义的信息,方便程序调用,这里也可以不用
String roleType = request.getParameter("state");
log.debug("weixin login code:" + code);
WechatUser user = null;
String openId = null;
WechatAuth auth = null;
if (null != code) {
UserAccessToken token;
try {
// 通过code获取access_token
token = WechatUtil.getUserAccessToken(code);
log.debug("weixin login token:" + token.toString());
// 通过token获取accessToken
String accessToken = token.getAccessToken();
// 通过token获取openId
openId = token.getOpenId();
// 通过access_token和openId获取用户昵称等信息
user = WechatUtil.getUserInfo(accessToken, openId);
log.debug("weixin login user:" + user.toString());
request.getSession().setAttribute("openId", openId);
auth = wechatAuthService.getWechatAuthByOpenId(openId);
} catch (IOException e) {
log.error("error in getUserAccessToken or getUserInfo or findByOpenId: " + e.toString());
e.printStackTrace();
}
}
// 若微信帐号为空则需要注册微信帐号,同时注册用户信息
if (auth == null) {
PersonInfo personInfo = WechatUtil.getPersonInfoFromRequest(user);
auth = new WechatAuth();
auth.setOpenId(openId);
if (FRONTEND.equals(roleType)) {
personInfo.setUserType(1);
} else {
personInfo.setUserType(2);
}
auth.setPersonInfo(personInfo);
WechatAuthExecution we = wechatAuthService.register(auth);
if (we.getState() != WechatAuthStateEnum.SUCCESS.getState()) {
return null;
} else {
personInfo = personInfoService.getPersonInfoById(auth.getPersonInfo().getUserId());
request.getSession().setAttribute("user", personInfo);
}
} else {
request.getSession().setAttribute("user", auth.getPersonInfo());
}
// 若用户点击的是前端展示系统按钮则进入前端展示系统
if (FRONTEND.equals(roleType)) {
return "frontend/index";
} else {
return "shop/shoplist";
}
}
}
第二步:通过code获取用户access_token
调用接口:https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;/**
* 微信工具类
*
* @author xiangze
*
*/
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("secret:" + 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 json字符串
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;
}
/**
* 发起https请求并获取结果
*
* @param requestUrl
* 请求地址
* @param requestMethod
* 请求方式(GET、POST)
* @param outputStr
* 提交的数据
* @return json字符串
*/
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);
// 设置请求方式(GET/POST)
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 ce) {
log.error("Weixin server connection timed out.");
} catch (Exception e) {
log.error("https request error:{}", e);
}
return buffer.toString();
}
}
因为我们发送的是https请求,所以还需要有这么一个工具方法,发起 https 请求。。。
OK,以上就是微信验证以及登录的全过程,可能并不是很详细,不过理解这个过程应该是可以了,那么下篇会讲解一下支付宝付款的流程以及代码。
The End。。。。。。