当面试官突然提到第三方登录时,我不禁微笑了~ 探秘WeChat公众号扫码关注登录!

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 当面试官突然提到第三方登录时,我不禁微笑了~ 探秘WeChat公众号扫码关注登录!

前言


当面试官突然提到第三方登录时,我不禁微笑了~ 探秘WeChat公众号扫码关注登录!,来整一个.

注意: 只能微信认证的公众号才能有二维码扫码的权限认证费用300快还要企业资质,那么我们将使用 微信的测试账户来玩转扫码(沙箱)


🌊 关注我不迷路,如果本篇文章对你有所帮助,或者你有什么疑问,欢迎在评论区留言,我一般看到都会回复的。大家点赞支持一下哟~ 💗




1. 大致流程思路:


一、用户打开网页进行登陆/注册 扫码(微信的)


二、用户扫码成功后 微信会根据我们配置的回调地址访问我们的回调并且传递某些参数


三、用户扫码成功并且进行了关注我们的公众号 微信也会访问回调 传递参数


四、++域名使用内网穿透(我这里使用花生壳)++


思路地址:  接收事件推送

在微信用户和公众号产生交互的过程中,用户的某些操作会使得微信服务器通过事件推送的形式通知到开发者在开发者中心处设置的服务器地址,从而开发者可以获取到该信息。其中,某些事件推送在发生后,是允许开发者回复用户的,某些则不允许,详细内容如下:


1、关注/取消关注事件

2、 扫描带参数二维码事件

3、上报地理位置事件

4、自定义菜单事件

5、点击菜单拉取消息时的事件推送

6、点击菜单跳转链接时的事件推送

根据上述六点我们PC端只需要 1、2点即可只是来扫码公众号并且关注后登录



2. 进入测试号页面

微信测试号地址


测试号接口配置

接口信息配置:  将会get方法来进行验签你服务器的请求 和 post来回调推送信息到服务器 参考: 接口信息配置JS接口安全配置:我们在日常当中经常可以看见js接口安全域名。那么,js接口安全域名是什么?js接口安全域名主要用于微信公众号,如果大家要进行微信的开发,创建公众号是需要填写js接口安全域名的。当我们运用程序的时候,网络是会自动验证安全域名的,它可以解决服务器终端的语言问题,能够让访问正常的运行,只有使用好js接口安全域名,网上的用户才能够访问到网页。 参考:JS接口安全配置



3. 介绍

获取 AccessToken

用于请求微信API 需要用到的认证信息 参考: 获取AccessToken


临时二维码

用户扫描带场景值二维码时,可能推送以下两种事件:  如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。 如果用户已经关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值扫描事件推送给开发者。 获取带参数的二维码的过程包括两步,首先创建二维码ticket,然后凭借 ticket 到指定 URL 换取二维码。 正确的 Json 返回结果:      {"ticket":"gQH47joAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL2taZ2Z3TVRtNzJXV1Brb3ZhYmJJAAIEZ23sUwMEmm

3sUw==","expire_seconds":60,"url":"http://weixin.qq.com/q/kZgfwMTm72WWPkovabbI"}

参考: 临时二维码



4. 代码操作

编写接口配置以便能修改接口

/***
     * 微信服务器触发get请求用于检测签名-
     * 如果需要绝对的安全就按照微信来进行验签
     */
    @GetMapping("/weChatScanCodeCallback")
    @ResponseBody
    public String weChatScan(HttpServletRequest request) {
        log.info("验签章:{}", request.getParameterMap());
        return request.getParameter("echostr");
    }


解析微信返回参数

使用DOM4J将微信返回XML格式转换一下

/**
 * @Author yang shuai
 * @Date 2023/9/18
 */
public class XmlUtil {
    /**
     * 读取xml标签内容存放map当中
     */
    public static Map<String,Object> parseXML(InputStream in){
        Map<String,Object> map=new HashMap<>();
        try {
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(in);
            Element root = document.getRootElement();
            Iterator iterator = root.elementIterator();
            while (iterator.hasNext()){
                Element element = (Element) iterator.next();
                map.put(element.getName(),element.getStringValue());
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return map;
    }
}


接收微信回调

/**
     * 接收微信推送事件
     */
    @PostMapping("/weChatScanCodeCallback")
    @ResponseBody
    public String weChatCallback(HttpServletRequest request) {
        try {
            InputStream inputStream = request.getInputStream();
            Map<String, Object> map = XmlUtil.parseXML(inputStream);
            log.info("接收参数:{}", map);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "success";


Last

注入restTemplate请求

/**
 * @Author yang shuai
 * @Date 2023/9/18
 * 注入restTemplate用于http请求
 */
@Configuration
public class RestTemplateConfig {
    @Resource
    private RestTemplateBuilder templateBuilder;
    @Bean
    public RestTemplate restTemplate(){
        return templateBuilder.build();
    }
}


生成微信二维码

/**
 * @Author yang shuai
 * @Date 2023/9/18
 */
public interface WeChatService {
    /**
     * 获取token
     *
     * @return
     */
    String getAccessToken();
    /**
     * 获取生成二维码参数
     *
     * @return
     */
    Map<String, Object> getQrCode();
}


实现

/**
 * @Author yang shuai
 * @Date 2023/9/18
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class WeChatServiceImpl implements WeChatService {
    @Value("${weChat.gzh.appid:''}")
    private String appid;
    @Value("${weChat.gzh.secret:''}")
    private String secret;
    private final RestTemplate restTemplate;
    private final RedisCache redisCacheManager;
    /**
     * 获取token用于操作微信接口
     */
    @Override
    public String getAccessToken() {
        String key = "wx_access_token";
        if (redisCacheManager.hashKey(key)) {
            return redisCacheManager.getCacheObject(key);
        }
        // 获取微信扫码 token
        String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appid, secret);
        ResponseEntity<String> result = restTemplate.getForEntity(url, String.class);
        if (result.getStatusCode() == HttpStatus.OK) {
            JSONObject jsonObject = JSON.parseObject(result.getBody());
            String access_token = jsonObject.getString("access_token");
            Long expires_in = jsonObject.getLong("expires_in");
            redisCacheManager.setCacheObject(key, access_token, expires_in, TimeUnit.SECONDS);
            return access_token;
        }
        return null;
    }
    /**
     * 获取微信公众号二维码
     */
    @Override
    public Map<String, Object> getQrCode() {
        // 获取临时二维码
        String url = String.format("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s", getAccessToken());
        ResponseEntity<String> result = restTemplate.postForEntity(url, "{\"expire_seconds\": 604800, \"action_name\": \"QR_STR_SCENE\", \"action_info\": {\"scene\": {\"scene_str\": \"test\"}}}", String.class);
        log.info("二维码:{}", result.getBody());
        JSONObject jsonObject = JSON.parseObject(result.getBody());
        Map<String, Object> map = new HashMap<>();
        map.put("ticket", jsonObject.getString("ticket"));
        map.put("url", jsonObject.getString("url"));
        return map;
    }
}


5. 改造Controller

新增获取二维码

/**
     * 获取二维码参数
     *
     * @return
     */
    @GetMapping("/getQrCode")
    @ResponseBody
    public Object getQrCode() {
        return weChatService.getQrCode();
    }


6.编写前段Demo

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登陆</title>
</head>
<body>
<div style="width: 200px;margin: 50px auto">
    <div id="qrcode"></div>
    <div id="msg" style="display: none">
        扫码成功!
    </div>
</div>
<script type='text/javascript' src='http://cdn.staticfile.org/jquery/2.1.1/jquery.min.js'></script>
<script src="https://cdn.bootcss.com/jquery.qrcode/1.0/jquery.qrcode.min.js"></script>
<script>
    $(function () {
        let count = 0;
        //获取二维码参数
            $.get('https://34i33045l8.oicp.vip/weChat/getQrCode', function (res) {
            //生成二维码
            $('#qrcode').qrcode(res.url);
        })
    })
</script>
</body>
</html>


7.启动后端查看效果

1、使用idea打开html挂载一个node

2、打开前面要求设置的内网穿透用于接收微信的回调

3、进行扫码-查看后台打印参数数据

4、扫码后查看控制台

推送 XML 数据包示例:

  1. 用户未关注时,进行关注后的事件推送
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[FromUser]]></FromUserName>
  <CreateTime>123456789</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[subscribe]]></Event>
  <EventKey><![CDATA[qrscene\\_123123]]></EventKey>
  <Ticket><![CDATA[TICKET]]></Ticket>
</xml>


示例:

在这里大家应该大致的知道下面的该如何实现了!  1. 微信回调会一直存在 Ticket 字段 用于表示每次二维码的唯一标识  我们将它进行存储redis当中并且可以看到 Event 我们利用它来区分当前是否为扫码还是关注的推送  2. 则前段进行段轮训来请求校验当前为什么状态?

参数说明:

参数

描述

ToUserName

开发者微信号

FromUserName

发送方帐号(一个OpenID)

CreateTime

消息创建时间 (整型)

MsgType

消息类型,event

Event

事件类型,subscribe(扫码关注) or SCAN (扫码)

EventKey

事件 KEY 值,qrscene_为前缀,后面为二维码的参数值

Ticket

二维码的ticket,可用来换取二维码图片



8. 改造Controller

新增短轮询检查扫码状态

/**
     * 用于检测扫码和关注状态
     *
     * @return
     */
    @PostMapping("/checkLogin")
    @ResponseBody
    public Object checkLogin(String ticket) {
        // 存在该信息并且为关注了公众号
        if (redisCache.hashKey(ticket)) {
            if (!redisCache.getCacheObject(ticket).equals("subscribe")) {
                return AjaxResult.error(201, "扫码成功");
            }
            //扫码通过则删除
            redisCache.deleteObject(ticket);
            return AjaxResult.success();
        }
        return AjaxResult.error("无动作");
    }


修改微信回调完善业务

/**
     * 接收微信推送事件
     *
     * @param request
     * @return
     */
    @PostMapping("/weChatScanCodeCallback")
    @ResponseBody
    public String weChatCallback(HttpServletRequest request) {
        try {
            InputStream inputStream = request.getInputStream();
            Map<String, Object> map = XmlUtil.parseXML(inputStream);
            log.info("接收参数:{}", map);
            String userOpenId = (String) map.get("FromUserName");
            String event = (String) map.get("Event");
           //  自己生成的二维码不管是关注还是扫码都能取到ticket凭证,这里我使用Ticket作为每次二维码的唯一标识
            String ticket = (String) map.get("Ticket");
            if ("subscribe".equals(event)) {
                //  根据openid判断用户是否存在,不存在则获取新增用户
                // 或者根据前段传递手机号或者用户名称来进行openId绑定 看你自己的业务.
                redisCache.setCacheObject(ticket, "subscribe", (long) (10 * 60), TimeUnit.SECONDS);
                log.info("用户关注:{}", userOpenId);
            } else if ("SCAN".equals(event)) {
                redisCache.setCacheObject(ticket, "scan", (long) (10 * 60), TimeUnit.SECONDS);
                log.info("用户扫码:{}", userOpenId);
            }
        } catch (IOException e) {
              log.error("回调异常:",e);
        }
        return "success";
    }


新增前段短轮询

替换你自己的内网穿透

$(function () {
        let count = 0;
        //获取二维码参数
            $.get('https://34i33045l8.oicp.vip/weChat/getQrCode', function (res) {
            //生成二维码
            $('#qrcode').qrcode(res.url);
            // 轮训获取用户扫码登陆状态
            let task = setInterval(function () {
                $.post('https://34i33045l8.oicp.vip/weChat/checkLogin', {ticket: res.ticket}, function ({code,msg}) {
                    console.log(code);
                    if (code === 200) { // 扫码并且关注成功
                        clearInterval(task)
                        location.href = 'http://yby6.com'
                    } else if (code === 201) { // 扫码成功
                        $("#msg").text(msg);
                        document.querySelector("#msg").style.display = "block"
                    } else {
                    }
                    count ++;
                })
            }, 2000)
        })
    })


最后操作流程

注: 前端记得整扫码超时!





最后


本期结束咱们下次再见👋~

🌊 关注我不迷路,如果本篇文章对你有所帮助,或者你有什么疑问,欢迎在评论区留言,我一般看到都会回复的。大家点赞支持一下哟~ 💗

相关文章
|
6天前
|
存储 NoSQL 前端开发
美团面试:手机扫描PC二维码登录,底层原理和完整流程是什么?
45岁老架构师尼恩详细梳理了手机扫码登录的完整流程,帮助大家在面试中脱颖而出。该过程分为三个阶段:待扫描阶段、已扫描待确认阶段和已确认阶段。更多技术圣经系列PDF及详细内容,请关注【技术自由圈】获取。
|
存储 NoSQL Redis
【面试题】:说一下登录模块的思路以及登录的优化
说一下登录模块的思路以及登录的优化
227 1
|
测试技术
软件测试面试题:使用QTP做功能测试,录制脚本的时候,要验证多个用户的登录情况\/查询情况,如何操作?
软件测试面试题:使用QTP做功能测试,录制脚本的时候,要验证多个用户的登录情况\/查询情况,如何操作?
114 0
|
测试技术
软件测试面试题:接口测试中,依赖登录状态的接口如何测试?
软件测试面试题:接口测试中,依赖登录状态的接口如何测试?
154 0
|
JSON 测试技术 数据格式
软件测试面试题:依赖于登录的接口如何处理?
软件测试面试题:依赖于登录的接口如何处理?
342 0
|
SQL
|
存储 缓存 安全
面试官:设计一个安全的登录都要考虑哪些?我一脸懵逼。。
标准的HTML语法中,支持在form表单中使用 &lt;input&gt;&lt;/input&gt; 标签来创建一个HTTP提交的属性,现代的WEB登录中,常见的是下面这样的表单:
面试官:设计一个安全的登录都要考虑哪些?我一脸懵逼。。
|
3月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
3月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
3月前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
100 4

热门文章

最新文章