微信验证以及登录流程

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

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

    /**
     * 验证签名
     *
     * @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。。。。。。

时间: 2024-08-29 09:25:30

微信验证以及登录流程的相关文章

微信小程序--登录流程梳理

前言 微信小程序凡是需要记录用户信息都需要登录,但是也有几种不同的登录方式,但是在小程序部分的登录流程是一样的.之前就朦朦胧胧地用之前项目的逻辑改改直接用了,这个新项目要用就又结合官方文档重新梳理了下,并记录一下,好记性不如烂笔头嘛,哈哈. 几种登录流程设计 利用OpenId 创建新用户 这种方式我的理解大体上就是一种静默登录,获取用户信息之后解密用户信息并通过OpenId直接创建新用户 利用Unionid 创建新用户 这种方式的特点是可以利用Unionid实现多个小程序.公众号.已有登录体系的

网站使用微信扫码登录流程

微信扫码登录是一个标准的oauth 2.0的过程. 1.用户请求访问网站,选择微信登录. 2.redirect到微信二维码页面 3.获取微信登录二维码,请求参数包括本网站的appId和登录成功后跳转回的地址,即relaystate. 4.返回二维码网页. 5.二维码扫入手机. 6.微信客户端将微信用户信息和二维码内的信息传给微信后台. 7.微信后台返回给浏览器授权成功,并附带授权码. 8.浏览器重定向到relaystate的地址,并将授权码作为参数传给网络后台. 9.网站将授权码和自己的地址发给

微信小程序登录流程

微信登陆流程 微信多个载体(APP微信授权,微信公众号授权登陆),openId是不一致的,但是unionId是一致的 所以在走流程时, 先判断unionId在数据库中存在不存在 存在,拿unionId去数据库换token,获取用户信息,更新openId,---首页 不存在,在启动页,让用户点击授权登陆,获取用户信息,再进行手机号授权 微信授权手机号 ---首页 登陆注册手机号 ---首页 代码方面: 进入启动页 wx.login();//获取code,code只能获取一次 将code传给后端,换

微信小程序登录流程和api接口处理分层

登录流程图:分层处理示意图: php菜鸟,不足之处多多指教 原文地址:http://blog.51cto.com/11016194/2318591

SpringBoot整合微信小程序登录

1. 开发前准备 1.1 前置知识 java基础 SpringBoot简单基础知识 1.2 环境参数 开发工具:IDEA 基础环境:Maven+JDK8 所用技术:SpringBoot.lombok.mybatisplus.微信小程序 SpringBoot版本:2.1.4 1.3 涉及知识点 微信小程序登录流程 2. 微信小程序登录流程 微信小程序登录流程涉及到三个角色:小程序.开发者服务器.微信服务器 三者交互步骤如下: 第一步:小程序通过wx.login()获取code. 第二步:小程序通过

Android通过微信实现第三方登录并使用OKHttp获得Token及源码下载

这里对于App在微信开放平台上申请AppID和secret在这里就略过了,我们微信的授权登录流程,腾讯官网给的流程如下: 1. 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数: 2. 通过code参数加上AppID和AppSecret等,通过API换取access_token: 3. 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作. 下边我们看看代码是怎么实现的.源码地址 一.加

微信小程序登录对接Django后端实现JWT方式验证登录

先上效果图 点击授权按钮后可以显示部分资料和头像,点击修改资料可以修改部分资料. 流程 1.使用微信小程序登录和获取用户信息Api接口 2.把Api获取的用户资料和code发送给django后端 3.通过微信接口把code换取成openid 4.后端将openid作为用户名和密码 5.后端通过JSON web token方式登录,把token和用户id传回小程序 6.小程序将token和用户id保存在storage中 下次请求需要验证用户身份的页面时,在header中加入token这个字段 微信

单页面应用在微信服务号下的登录流程

最近我们的小程序涉及到虚拟支付的问题,在ios端的支付被封掉了??,所以有了在服务号上搞一套H5版的小程序的需求.由于我们小程序是mpvue写的,为了尽量复用之前的样式和逻辑,选择了前后端分离的模式,于是一段新的踩坑之旅开始了.放下wx的jssdk暂且不表,今天来说说登录时遇到的坑. 服务号的登录流程 以前搞过服务号的同学对于它的登录流程应该不陌生,就是当后端检测到当前用户没有授权时,将会重定向到微信的授权页面,当用户点击这个授权的button时,微信会根据Url查询字符串中的重定向URL,重新

【微信】第三方登录接口流程

目录 微信登录介绍: 准备工作: 创建网站应用 提交审核,等待获取到APPID和AppSecret 代码操作示例: 第一步:请求CODE 第二步:通过code获取access_token 刷新access_token有效期 第三步:通过access_token调用接口 附加二维码登录代码示例: F.A.Q 回到顶部 微信登录介绍: 微信OAuth2.0授权登录让微信用户使用微信身份安全登录第三方应用或网站,在微信用户授权登录已接入微信OAuth2.0的第三方应用后,第三方可以获取到用户的接口调用