微信验证以及登录流程

Posted Mr丶苏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了微信验证以及登录流程相关的知识,希望对你有一定的参考价值。

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

 本篇为原创,转载请标出处: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。。。。。。

 

以上是关于微信验证以及登录流程的主要内容,如果未能解决你的问题,请参考以下文章

微信小程序-微信支付签名验证

网站应用接入微信登录

图解用户登录验证流程,写得太好了!

pbootcms对接微信扫码登录代码核心片段和步骤(前后端)

图解用户登录验证流程,写得太好了!

图解用户登录验证业务流程(推荐)