微信验证以及登陆流程

前言: 如今大多数网站项目都支持微信登陆,付款,以及支付宝登陆付款,这种方式也是可以让用户很快速便捷的注册本网站的帐号,进行登陆,以及后续的操做。相信小伙伴们看完以后,会对怎么与微信或者支付宝服务器打交道有很深的理解,就当作是一个敲门砖吧。那么本篇主要针对微信的验证登陆来打开通往微信服务器的大门,下一篇会主要讲解一下支付宝付款验证对接。html

 本篇为原创,转载请标出处:http://www.cnblogs.com/gudu1/p/8087130.html 前端

下面我会使用大量的代码,代码中添加完整的注释来解释与微信服务器对接的详细步骤:

首先呢,须要有一个微信公众号,固然是收费的,不过呢,微信给咱们开发人员提供的有微信公众测试号来进行开发测试,虽然提供功能不是很全,开发测试是够用了,仍是很到位的java

微信测试号地址:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/logingit

扫码登录后呢,就是下面这个样子了:web

上面这个appID 和 appsecret 很重要,正常的公众号appsecret 必定不要暴露出去,否则是很危险的。spring

>> URL: 规则 http://服务器地址或者域名/项目验证接口路由,好比: http://www.mmm.cn/testProject/weChat.do,这个weChat.do 就是要跟微信服务器验证对接的接口json

>> Token:这个是由用户本身来定义,主要是咱们的项目跟微信服务器对接时候须要进行SHA1加密和解密的字段之一,详细请看官方文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319api

>> 域名: 这里就须要使用你本身的域名了,固然域名是要指向可以被外网访问到的服务器地址。数组

当咱们在接口配置信息这里点击提交,微信服务器就会向咱们的服务器接口发送消息来进行验证是否正确。浏览器

固然还须要修改一处,这里须要的仍是服务器域名:

 

接下来呢,就要看咱们的程序跟微信验证的代码流程:

这里呢,我使用的 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。。。。。。

相关文章
相关标签/搜索