java实现微信二维码登录(公众号)

微信二维码登录有两种方式

1.使用微信开放平台的二维码登录(对接微信)

2.使用微信公众平台的二维码登录(对接公众号)本文章的内容

准备:

首先准备一个公众号测试公众号,这里不做介绍

要有内网穿透的域名,域名填写不填协议 例如:  www.baidu.com

要设置推送消息的url接口和token,目的:之前关注过公众号或者扫码后关注公众号,微信会调用我们这里填写的url接口(像回调,但微信文档说的是推送消息,很多事件都会用到这个接口)

代码

1.需要的依赖

        糊涂工具类
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.8</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>

2.生成二维码的代码

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.XmlUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.TimeUnit;


@RestController
@RequestMapping("/wx")
public class WxControllerTest{

    @Autowired
    private RedisTemplate redisTemplate;


    private static final String token_name = "weixin_access_token";
    private static final String informUrl = "https://api.weixin.qq.com/cgi-bin/user/info"; //获取用户信息
    private static final String TokenUrl = "https://api.weixin.qq.com/cgi-bin/token"; //获取token
    private static final String QR_CODE = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=";//获取二维码url和ticket
    private static final String GET_QR_CODE = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=";//通过ticket换取二维码

    private static final String appID = "";    //todo  你自己的appid
    private static final String appsecret = ""; //todo  你自己的appsecret
    


    @GetMapping("/qrCode")
    public Map<String, Object> getQRCode() throws UnsupportedEncodingException {
        String wxToken = getWXToken();
        wxToken = URLEncoder.encode(wxToken, "utf-8");
        String url = QR_CODE + wxToken;

        Long snowflake = IdUtil.getSnowflake(1, 1).nextId(); //不重复的随机数

        /**
         * expire_seconds  有效时间 s
         * action_name     二维码类型,QR_SCENE为临时的整型参数值,QR_STR_SCENE为临时的字符串参数值,QR_LIMIT_SCENE为永久的整型参数值,QR_LIMIT_STR_SCENE为永久的字符串参数值
         *                 //注意:如果为scene_str那么action_name为QR_STR_SCENE,如果传入的是scene_id那么action_name为QR_SCENE,一定要对应
         * action_info    二维码详细信息
         *                scene_str   场景值ID(字符串形式的ID),字符串类型,长度限制为1到64
         *                scene_id    场景值ID,临时二维码时为32位非0整型,永久二维码时最大值为100000(目前参数只支持1--100000)
         */
        String json = "{\n" +
                "    \"expire_seconds\": 604800,\n" +
                "    \"action_name\": \"QR_STR_SCENE\",\n" +
                "    \"action_info\": {\"scene\": {\"scene_str\": \" " + snowflake + " \"}}\n" +
                "} ";
        String post = HttpUtil.post(url, json);

        /**
         * 返回的数据map
         *    "ticket": 可以调用  https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET  替换TICKET为这个值和微信换取二维码
         *     "expire_seconds":  过期时间
         *     "url": 如果不想用ticket换取二维码,可以用这个url使用二维码生成工具生成二维码,和ticket作用是一样的,只不过ticket不需要借助二维码生成工具
         */
        Map map = JSONObject.parseObject(post, Map.class);
        map.put("eventKey",snowflake)//这个参数是让前端遍历判断是否扫码登录成功的
        return map;
    }

    /**
     * 获取公众号验证Token
     *
     * @return
     */
    public String getWXToken() {
        //从redis获取token
        String token = (String) redisTemplate.boundValueOps(token_name).get();
        if (StringUtils.isEmpty(token)) {
            Map<String, Object> queries = new HashMap<>();
            queries.put("grant_type", "client_credential");
            queries.put("appid", appID);
            queries.put("secret", appsecret);
            String tokenString = HttpUtil.get(TokenUrl, queries, 10 * 1000);

            Map<String,Object> map= JSONObject.parseObject(tokenString, Map.class);
            redisTemplate.opsForValue().set(token_name, map.get("access_token"), (Integer)map.get("expires_in") - 10, TimeUnit.SECONDS);
            return (String)map.get("access_token");
        }
        return token;
    }
}

3.接收推送消息的代码,就是我们配置url和token的内容调用的接口(放入前面的controller中)

/**
 * 公众号接口验证以及消息接受回复,包括微信公众号关注取消操作
 *
 * @param req
 * @return
 * @throws Exception
 */
@RequestMapping("/reply")  //校验url是否可用,和推送url是同一个
@Transactional
public String get(HttpServletRequest req) throws Exception {
    String method = req.getMethod();
    //get做验证
    if (method.equalsIgnoreCase("get")) {
        //接口验证
        Enumeration parameterNames = req.getParameterNames();
        String signature = req.getParameter("signature");/// 微信加密签名
        String timestamp = req.getParameter("timestamp");/// 时间戳
        String nonce = req.getParameter("nonce"); /// 随机数
        String echostr = req.getParameter("echostr"); // 随机字符串
        if (this.checkSignature(signature, timestamp, nonce)) {
            System.out.println("校验成功!!!!!!!!!!!!!");
            return echostr;
        }
        return null;
    } else {
        req.setCharacterEncoding("UTF-8");
        String message = "success";
        try {
            //把微信返回的xml信息转义成map
            InputStream inputStream = req.getInputStream();
            String xml = IoUtil.read(inputStream, StandardCharsets.UTF_8);

            // 将XML转换为Map
            Map<String, Object> map = XmlUtil.xmlToMap(xml);
            String fromUserName = String.valueOf(map.get("FromUserName"));//消息来源用户标识openId
            String toUserName = String.valueOf(map.get("ToUserName"));//消息目的用户标识
            String msgType = String.valueOf(map.get("MsgType"));//消息类型
            String content = String.valueOf(map.get("Content"));//消息内容
            String eventKey = String.valueOf(map.get("EventKey")); //事件KEY值,qrscene_为前缀,后面为二维码的参数值
            String ticket = String.valueOf(map.get("Ticket")); //二维码的ticket,可用来换取二维码图片
            String eventType = String.valueOf(map.get("Event")); //时间类型
            //如果为事件类型,扫码关注公众号返回的是subscribe,已经关注公众号了扫码返回SCAN
            if ("event".equals(msgType)) {
                if ("SCAN".equals(eventType) || "subscribe".equals(eventType)) {

                    //这里是你的代码逻辑

                    //能到这里,证明身份校验完成了,将我们登录的信息保存到redis中120s,前端遍历这个eventKey,实现登录
                    redisTemplate.opsForValue().set(eventKey,fromUserName,120,TimeUnit.SECONDS);
                }
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            return message;
        }

    }
}

/**
 * 验证签名
 *
 * @param signature
 * @param timestamp
 * @param nonce
 * @return
 */
public boolean checkSignature(String signature, String timestamp, String nonce) {
    String[] arr = new String[]{"weixinCore", timestamp, nonce}; //weixinCore是你定义个token,和网站上保持一致
    // 将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 {
        md = MessageDigest.getInstance("SHA-1");
        // 将三个参数字符串拼接成一个字符串进行sha1加密
        byte[] digest = md.digest(content.toString().getBytes());
        tmpStr = HexUtil.encodeHexStr(digest).toUpperCase();  //将字节数组转换为十六进制字符串,且字母必须是大写(为什么要有这一步呢)
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
    return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;

4.前端调用的遍历接口,用来确定是否登录成功

    /**
     * 遍历查询是否登录成功
     * @param eventKey
     * @return
     */
    @GetMapping("/checkLogin")
    public String checkLogin(@RequestParam String eventKey){
        Object value = redisTemplate.opsForValue().get(eventKey);
        if (value == null){
            return "";
        }
        String eventValue = String.valueOf(value);
        
        //你自己的逻辑
        
        String token = ""; //生成token等登录需要的信息
        return token;
    }

扩展:

如果你想要通过openId获取unionId等用户信息,可以调用下面的方法(测试号没办法获取unionId)

/**
 * 获取公众号的个人信息
 *
 * @param openid
 * @return
 */
public Map<String,Object> getInform(String openid) {
    //获取token
    String wxToken = getWXToken();
    if (StringUtils.isEmpty(wxToken)) {
        return null;
    }
    Map<String, Object> queries = new HashMap<>();
    queries.put("access_token", wxToken);
    queries.put("openid", openid);
    queries.put("lang", "zh_CN");
    String s = HttpUtil.get(informUrl, queries, 10 * 1000);
    Map<String,Object> map = JSONObject.parseObject(s, Map.class);
    return map;
}

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值