微信扫码登录(公众号)

为什么要使用微信公众号扫码登录?

  1. 便捷性: 扫码登录省去了用户输入账号和密码的步骤,使登录过程更加简便和快捷。用户只需通过微信扫一扫功能即可完成登录,无需记忆或输入复杂的登录信息。

  2. 安全性: 使用微信公众号扫码登录可以提高账户安全性。由于无需输入密码,就减少了密码泄露的风险,避免了因为用户使用弱密码或者遭受钓鱼攻击导致的账户安全问题。

  3. 统一身份认证: 微信公众号扫码登录可以实现统一身份认证,用户在不同的应用或网站中都可以使用微信账号进行登录。这种方式使得用户只需维护一个账户,提高了用户体验和管理的便捷性。

  4. 用户信任: 微信作为一个广泛使用的社交平台,用户对其有较高的信任度。通过微信扫码登录,用户可能更愿意信任与微信关联的应用或网站,从而提高用户对服务的信任感。

  5. 社交分享: 扫码登录后,应用或网站可以获取用户在微信上的一些基本信息,例如头像、昵称等,这些信息可以用于个性化用户体验,同时也可以鼓励用户在应用或网站上进行社交分享,增加用户粘性。

1.关联微信公众号时,会填写配置信息

如上图,weCat.do接口需要放开,另外该接口get请求用于校验配置信息,

/**
     * 公众号验证token--验证token
     * 在公众号网页中添加/修改 配置服务器信息时,微信平台会验证echostr值是否正确(token校验)
     *
     * @param signature
     * @param timestamp
     * @param nonce
     * @param echostr
     * @return
     */
    @GetMapping(value = "/weCat.do", produces = {"application/json;charset=UTF-8"})
    public void validationWeCat(@RequestParam String signature,
                                @RequestParam String timestamp,
                                @RequestParam String nonce,
                                @RequestParam String echostr,
                                HttpServletResponse response) throws IOException {

        final boolean b = gzhUtiles.checkQianMing(signature, timestamp, nonce);
        if (b) {
            response.getOutputStream().println(echostr);
        } else {
            System.out.println("token验证失败");
        }
    }

格式校验代码为:

/**
     * 公众号验证token--验证签名
     *
     * @param signature
     * @param timestamp
     * @param nonce
     * @return
     */
    public boolean checkQianMing(String signature, String timestamp, String nonce) {


        String[] tmpArr = {TOKEN, timestamp, nonce};
        Arrays.sort(tmpArr);

        StringBuilder tmpStrBuilder = new StringBuilder();
        for (String str : tmpArr) {
            tmpStrBuilder.append(str);
        }

        String tmpStr = tmpStrBuilder.toString();

        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] digest = md.digest(tmpStr.getBytes());

            StringBuilder hexStrBuilder = new StringBuilder();
            for (byte b : digest) {
                hexStrBuilder.append(String.format("%02x", b));
            }

            String calculatedSignature = hexStrBuilder.toString();

            return calculatedSignature.equals(signature);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return false;
        }
    }

其余post请求接受接受微信一系列相关事件推送:

    /**
     * 接收公众号事件用户事件 订阅,取消订阅
     *
     * @param requestBody
     * @return
     */
    @RequestMapping(value = "/weCat.do", method = RequestMethod.POST,produces = {"application/json;charset=UTF-8"})
    public void validationWeCat(@RequestBody String requestBody, HttpServletResponse response) throws IOException {
        wechatService.validationWeCat(requestBody);
        response.getOutputStream().println("success");
    }

此处的String requestBody,就是微信想你发送的xml,需要解析

解析示例为:

/**
     * 解析公众号用户触发响应的xml
     *
     * @param requestBody
     * @return
     */
    public static XmlData jxXml(String requestBody) {

        String unescapedXml = StringEscapeUtils.unescapeHtml4(requestBody);
        try {
            // 为XmlData类创建JAXB上下文
            JAXBContext jaxbContext = JAXBContext.newInstance(XmlData.class);

            // 创建反序列化器
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();

            // 从XML反序列化为Java对象
            XmlData xmlData = (XmlData) unmarshaller.unmarshal(new StringReader(unescapedXml));

            System.out.println(xmlData.toString());

            return xmlData;
        } catch (JAXBException e) {
            e.printStackTrace();
        }

        return null;
    }

原本xml的格式为:关注/取消关注事件 | 微信开放文档

根据解析的内容进行相应逻辑处理

例如:

 @Override
    public void validationWeCat(String requestBody) {


        XmlData xmlData = GzhUtiles.jxXml(requestBody);


        Objects.requireNonNull(xmlData, "xml格式校验失败-null");

        //TODO:先将用户解析出来

        Objects.requireNonNull(xmlData.getEvent(), "xml格式校验失败-eventTypeNull");

        Objects.requireNonNull(xmlData.getFromUserName(), "xml格式校验失败-eventTypeNull");

        //事件类型
        String eventType = xmlData.getEvent();

        //用户的openId
        String openId = xmlData.getFromUserName();

        //用户的事件类型
        String eventKey = xmlData.getEventKey();

        //todo.1 在公众号点击关注不在进行用户的注册登录,仅仅在用户进行官网扫码时间进行用户的相关操作
        //登录注册
        if ((EventType.Login.getValue().equals(eventType) && "login".equals(eventKey)) || (EventType.SUBSCRIBE.getValue().equals(eventType) && "qrscene_login".equals(eventKey))) {//用户已关注扫描临时二维码或未关注扫描临时二维码进行关注
            String ticket = xmlData.getTicket();
            login(ticket, openId);
        }

        //点击关注事件,不做处理
//        else if (EventType.SUBSCRIBE.getValue().equals(eventType)) {
//
//        }
        //取消关注事件,不做处理或者修改用户账号状态,过期30天不关注注销账号
//        else if (EventType.UNSUBSCRIBE.getValue().equals(eventType)) {
//
//        }

    }
如何实现扫码登录:

1.获取临时二维码或者永久二维码(建议使用临时二维码,量大管饱)

    /**
     * 获取微信二维码
     *
     * @return /**
     * 获取微信二维码信息
     * @return 包含二维码 ticket 和 codeUrl 的 Map
     */
    @Override
    public Map<String, String> getQRCode() {
        try {
            // 获取公众号 AccessToken
            String gzhAccessToken = gzhUtiles.getToken();

            // 构建请求URL
            String createQRCodeUrl = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + gzhAccessToken;

            // 构建二维码数据
            String jsonData = "{\"expire_seconds\": 600, \"action_name\": \"QR_STR_SCENE\", \"action_info\": {\"scene\": {\"scene_str\": \"login\"}}}";

            // 发起POST请求获取二维码 ticket
            Map<String, Object> ticketMap = httpUtil.doPost(createQRCodeUrl, jsonData, 600);
            String ticket = String.valueOf(ticketMap.get("ticket"));

            if (ticket != null) {

                // 构建二维码显示URL
                String codeUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + ticket;
                HashMap<String, String> wxMap = new HashMap<>();
                wxMap.put("uuid", ticket);
                wxMap.put("codeUrl", codeUrl);
                return wxMap;
            }
        } catch (Exception e) {
            // 发生异常时抛出自定义异常
            throw new WechatQrCodeException();
        }

        // 默认返回空 Map
        return Collections.emptyMap();
    }

其中,调用微信相应接口createQRCodeUrl,构建二维码参数,

最后构建的codeUrl交给前端展示,形成网页二维码,在调用该接口时间,会存在相应工具类

@Component
public class GzhUtiles {


    @Autowired
    private RedisCache redisCache;
    private final Environment env;

    private static String AppSecret;
    private static String appid;
    private static String TOKEN;

    @Autowired
    public GzhUtiles(Environment env) {
        this.env = env;

        AppSecret = env.getProperty("ruoyi.AppSecret");
        appid = env.getProperty("ruoyi.appid");
        TOKEN = env.getProperty("ruoyi.gzhTooken");

    }


    /**
     * 公众号验证token--验证签名
     *
     * @param signature
     * @param timestamp
     * @param nonce
     * @return
     */
    public boolean checkQianMing(String signature, String timestamp, String nonce) {


        String[] tmpArr = {TOKEN, timestamp, nonce};
        Arrays.sort(tmpArr);

        StringBuilder tmpStrBuilder = new StringBuilder();
        for (String str : tmpArr) {
            tmpStrBuilder.append(str);
        }

        String tmpStr = tmpStrBuilder.toString();

        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] digest = md.digest(tmpStr.getBytes());

            StringBuilder hexStrBuilder = new StringBuilder();
            for (byte b : digest) {
                hexStrBuilder.append(String.format("%02x", b));
            }

            String calculatedSignature = hexStrBuilder.toString();

            return calculatedSignature.equals(signature);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 获取微信公众号的AccessToken
     *
     * @return
     */
    public String getToken() {


        // 判断数据是否存在
        Boolean gzhAccessToken = redisCache.hasKey("gzh_access_token");


        //存入缓存  存在两小时
        //三秒
        if (!gzhAccessToken) {
            // 授予形式
            System.out.println("第一次进入");
            String grant_type = "client_credential";
            // 接口地址拼接参数(appid为微信服务号的appid,secret为服务号的秘钥)
            String getTokenApi = "https://api.weixin.qq.com/cgi-bin/token?grant_type=" + grant_type + "&appid=" + appid
                    + "&secret=" + AppSecret;
            String tokenJsonStr = doGetPost(getTokenApi, "GET", null);
            JSONObject tokenJson = JSONObject.parseObject(tokenJsonStr);
            String token = tokenJson.get("access_token").toString();
            // 存入缓存 120分钟 重新获取

            // 存储数据并设置过期时间为 120 分钟
            redisCache.setCacheObject("gzh_access_token", token, 2, TimeUnit.HOURS);
            return token;
        } else {
            // 获取数据
            return redisCache.getCacheObject("gzh_access_token");
        }
    }


    /**
     * 调用接口 post
     *
     * @param apiPath
     */
    public static String doGetPost(String apiPath, String type, Map<String, Object> paramMap) {
        OutputStreamWriter out = null;
        InputStream is = null;
        String result = null;

        try {
            URL url = new URL(apiPath);// 创建连接
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setUseCaches(false);
            connection.setInstanceFollowRedirects(true);
            connection.setRequestMethod(type); // 设置请求方式
            connection.setRequestProperty("Accept", "application/json"); // 设置接收数据的格式
            connection.setRequestProperty("Content-Type", "application/json"); // 设置发送数据的格式
            connection.connect();
            if (type.equals("POST")) {
                out = new OutputStreamWriter(connection.getOutputStream(), "UTF-8"); // utf-8编码
                out.append(JSON.toJSONString(paramMap));
                out.flush();
                out.close();
            }
            // 读取响应
            is = connection.getInputStream();
            int length = (int) connection.getContentLength();// 获取长度
            if (length != -1) {
                byte[] data = new byte[length];
                byte[] temp = new byte[512];
                int readLen = 0;
                int destPos = 0;
                while ((readLen = is.read(temp)) > 0) {
                    System.arraycopy(temp, 0, data, destPos, readLen);
                    destPos += readLen;
                }
                result = new String(data, "UTF-8"); // utf-8编码
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }


    /**
     * 解析公众号用户触发响应的xml
     *
     * @param requestBody
     * @return
     */
    public static XmlData jxXml(String requestBody) {

        String unescapedXml = StringEscapeUtils.unescapeHtml4(requestBody);
        try {
            // 为XmlData类创建JAXB上下文
            JAXBContext jaxbContext = JAXBContext.newInstance(XmlData.class);

            // 创建反序列化器
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();

            // 从XML反序列化为Java对象
            XmlData xmlData = (XmlData) unmarshaller.unmarshal(new StringReader(unescapedXml));

            System.out.println(xmlData.toString());

            return xmlData;
        } catch (JAXBException e) {
            e.printStackTrace();
        }

        return null;
    }


}

其中:getToken,类似于访问微信的钥匙,该钥匙获取之后会存在两个小时,调用次数是存在限制的,最好保存到缓存。

当用户扫描二维码后,会存在相应的事件推送到weCat.do接口,解析后处理相应事件,具体逻辑可自行处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值