title: 从零玩转第三方登录之WeChat公众号扫码关注登陆
date: 2022-09-27 22:46:53.362
updated: 2023-03-30 13:28:41.359
url: https://www.yby6.com/archives/wechatgzh
categories:
- 从零玩转系列
tags:
- 第三方登录
- 从零玩转系列

前言

由于看见了面试鸭的登陆方式,我也想来整一个.注意: 只能微信认证的公众号才能有二维码扫码的权限,那么我们将使用 微信的测试账户来玩转扫码(沙箱)

1. 大致流程思路:

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

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

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

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

思路地址: 接收事件推送

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

1、关注/取消关注事件

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

3、上报地理位置事件

4、自定义菜单事件

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

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

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

2. 进入测试号页面

微信测试号地址

测试号接口配置

接口信息配置: 将会get方法来进行验签你服务器的请求 和 post来回调推送信息到服务器

参考: 接口信息配置

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

参考:JS接口安全配置

3. 介绍

获取 AccessToken

用于请求微信API 需要用到的认证信息

参考: 获取AccessToken

临时二维码

  1. 用户扫描带场景值二维码时,可能推送以下两种事件:

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

    正确的 Json 返回结果:

    {"ticket":"gQH47joAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL2taZ2Z3TVRtNzJXV1Brb3ZhYmJJAAIEZ23sUwMEmm

    3sUw==","expire_seconds":60,"url":"http://weixin.qq.com/q/kZgfwMTm72WWPkovabbI"}
  4. 参考: 临时二维码

4. 代码操作

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

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

解析微信返回参数

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


/**
* @Author yang shuai
* @Date 2022/9/3
*/
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 2022/9/3
* 注入restTemplate用于http请求
*/
@Configuration
public class RestTemplateConfig { @Resource
private RestTemplateBuilder templateBuilder; @Bean
public RestTemplate restTemplate(){ return templateBuilder.build();
} }

生成微信二维码

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

实现


/**
* @Author yang shuai
* @Date 2022/9/3
*/
@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)
})
})

最后操作流程

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

从零玩转第三方登录之WeChat公众号扫码关注登陆 -wechatgzh的更多相关文章

  1. 从零玩转第三方登录之QQ登录

    从零玩转第三方登录之QQ登录 前言 在真正开始对接之前,我们先来聊一聊后台的方案设计.既然是对接第三方登录,那就免不了如何将用户信息保存.首先需要明确一点的是,用户在第三方登录成功之后, 我们能拿到的 ...

  2. 初涉扫码登录:edusoho实现客户端扫码登录(简版)

    一.项目简介及需求 edusoho是一套商业版的在线教育平台,项目本身基于symfony2框架开发,现在有一款自己的APP,要求在不多修改edusoho自身代码的基础上,实现客户端对PC端扫码登录.不 ...

  3. 微信第三方平台开头篇--MVC代码(第三方获取ticket和公众号授权)

    微信公众号授权给开放平台 公众号授权给第三方平台的技术实现流程比较简单 这个步骤遗漏了开头获取第三方平台自己的accessToken 先说下流程 如何注册开放平台的第三方信息看截图 其他不说了,此文只 ...

  4. dedecms织梦第三方登录插件-QQ登录、微博登录、微信登录

    织梦程序集成第三方QQ登录.微博登录.微信登录,获取QQ.微博.微信,并存储至数据库,一键注册为网站会员,不用再次填写绑定信息,方便粘贴用户更强. 织梦第三方登录效果 第三方登录插件特点 1.所有文件 ...

  5. 第三方登录:微信扫码登录(OAuth2.0)

    1.OAuth2.0 OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. 允许用户提供 ...

  6. php微信开放平台--第三方网页微信扫码登录(OAuth2.0)

    第一.OAuth2.0 OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. 允许用户提 ...

  7. 使用 Abp.Zero 搭建第三方登录模块(一):原理篇

    ​第三方登录是基于用户在第三方平台上(如微信,QQ, 百度)已有的账号来快速完成系统的登录.注册-登录等功能. 微信的鉴权 以微信的鉴权为例: 假如你的网站有一个扫码登录的功能,会弹出一个由微信提供的 ...

  8. C#开发微信门户及应用(41)--基于微信开放平台的扫码登录处理

    在现今很多网站里面,都使用了微信开放平台的扫码登录认证处理,这样做相当于把身份认证交给较为权威的第三方进行认证,在应用网站里面可以不需要存储用户的密码了.本篇介绍如何基于微信开放平台的扫码进行网站的登 ...

  9. 微信开放平台开发——网页微信扫码登录(OAuth2.0)

    1.OAuth2.0 OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. 允许用户提供 ...

  10. 微信开放平台 公众号第三方平台开发 教程四 代公众号调用接口的SDK和demo

    原文:微信开放平台 公众号第三方平台开发 教程四 代公众号调用接口的SDK和demo 教程导航: 微信开放平台 公众号第三方平台开发 教程一 平台介绍 微信开放平台 公众号第三方平台开发 教程二 创建 ...

随机推荐

  1. Web3.0时代的全新合作模式:DAO

    你有没有遇到这种情况:我有一个很棒的想法,想要开发出一个"改变世界"的项目,但是我既没有技术,也没有人脉,甚至没有资金,导致我始终没有办法开始行动,痛苦万分.就比如在黑客大赛上,我 ...

  2. 算法打卡|Day3 链表part01

    Day3 链表part01 今日任务 ● 链表理论基础 ● 203.移除链表元素 ● 707.设计链表 ● 206.反转链表 目录 Day3 链表part01 链表理论基础 Problem: 203. ...

  3. 查漏补缺,这些热门开源项目你都知道么?「GitHub 热点速览」

    本期热点速览的周榜部分的项目,基本上每周都会在 GitHub Trending 见到它们的身影,因为它们实在太火了.一般来说,这些火爆的项目大家都耳熟能详,但是为了防止有些小伙伴不怎么逛 GitHub ...

  4. .Net核心级的性能优化(GC篇)

    1.前言 大部分人对于.Net性能优化,都停留在业务层面.或者简单的.Net框架配置层面.本篇来看下.Net核心部分GC垃圾回收配置:保留VM,大对象,独立GC,节省内存等.Net8里面有很多的各种G ...

  5. Vue之属性

    Vue中的属性:举例 看一下就明白了 <!DOCTYPE html> <html lang="en"> <head> <meta char ...

  6. nginx虚拟机及热部署(在线升级)

    实现热部署(在线升级): 热部署方案一 (有弊端,不利于回滚) 查看nginx版本及源编译差数: /usr/local/nginx/sbin/nginx -V 预编译/ 编译/ 安装:在预编译之前,先 ...

  7. 【matplotlib 实战】--箱型图

    箱型图(Box Plot),也称为盒须图或盒式图,1977年由美国著名统计学家约翰·图基(John Tukey)发明.是一种用作显示一组数据分布情况的统计图,因型状如箱子而得名. 它能显示出一组数据的 ...

  8. SpringBoot数据响应、分层解耦、三层架构

    响应数据 @ResponseBody 类型:方法注解.类注解 位置:Controller方法.类上 作用:将方法返回值直接响应,如果返回值类型是 实体对象/集合 ,将会转换为json格式响应 说明:@ ...

  9. VS2022修改cs文件模板

    在路径:C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\ItemTemplates\AspNetCore\Co ...

  10. 等保测评之主机测评——Windows Sever

    目录 (一)身份鉴别 (二)访问控制 (三)安全审计 (四)入侵防范 (五)恶意代码防范 (六)可信验证 (七)数据完整性 (八)数据保密性 (九)数据备份恢复 (十)剩余信息保护 在测评过程中最为常 ...