从零玩转第三方登录之WeChat公众号扫码关注登陆 -wechatgzh
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
临时二维码
- 用户扫描带场景值二维码时,可能推送以下两种事件:
如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。- 如果用户已经关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值扫描事件推送给开发者。
- 获取带参数的二维码的过程包括两步,首先创建二维码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 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 数据包示例:
- 用户未关注时,进行关注后的事件推送
<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>
示例:

在这里大家应该大致的知道下面的该如何实现了!
- 微信回调会一直存在
Ticket字段 用于表示每次二维码的唯一标识
我们将它进行存储redis当中并且可以看到Event我们利用它来区分当前是否为扫码还是关注的推送- 则前段进行段轮训来请求校验当前为什么状态?
参数说明:
| 参数 | 描述 |
|---|---|
| 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的更多相关文章
- 从零玩转第三方登录之QQ登录
从零玩转第三方登录之QQ登录 前言 在真正开始对接之前,我们先来聊一聊后台的方案设计.既然是对接第三方登录,那就免不了如何将用户信息保存.首先需要明确一点的是,用户在第三方登录成功之后, 我们能拿到的 ...
- 初涉扫码登录:edusoho实现客户端扫码登录(简版)
一.项目简介及需求 edusoho是一套商业版的在线教育平台,项目本身基于symfony2框架开发,现在有一款自己的APP,要求在不多修改edusoho自身代码的基础上,实现客户端对PC端扫码登录.不 ...
- 微信第三方平台开头篇--MVC代码(第三方获取ticket和公众号授权)
微信公众号授权给开放平台 公众号授权给第三方平台的技术实现流程比较简单 这个步骤遗漏了开头获取第三方平台自己的accessToken 先说下流程 如何注册开放平台的第三方信息看截图 其他不说了,此文只 ...
- dedecms织梦第三方登录插件-QQ登录、微博登录、微信登录
织梦程序集成第三方QQ登录.微博登录.微信登录,获取QQ.微博.微信,并存储至数据库,一键注册为网站会员,不用再次填写绑定信息,方便粘贴用户更强. 织梦第三方登录效果 第三方登录插件特点 1.所有文件 ...
- 第三方登录:微信扫码登录(OAuth2.0)
1.OAuth2.0 OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. 允许用户提供 ...
- php微信开放平台--第三方网页微信扫码登录(OAuth2.0)
第一.OAuth2.0 OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. 允许用户提 ...
- 使用 Abp.Zero 搭建第三方登录模块(一):原理篇
第三方登录是基于用户在第三方平台上(如微信,QQ, 百度)已有的账号来快速完成系统的登录.注册-登录等功能. 微信的鉴权 以微信的鉴权为例: 假如你的网站有一个扫码登录的功能,会弹出一个由微信提供的 ...
- C#开发微信门户及应用(41)--基于微信开放平台的扫码登录处理
在现今很多网站里面,都使用了微信开放平台的扫码登录认证处理,这样做相当于把身份认证交给较为权威的第三方进行认证,在应用网站里面可以不需要存储用户的密码了.本篇介绍如何基于微信开放平台的扫码进行网站的登 ...
- 微信开放平台开发——网页微信扫码登录(OAuth2.0)
1.OAuth2.0 OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. 允许用户提供 ...
- 微信开放平台 公众号第三方平台开发 教程四 代公众号调用接口的SDK和demo
原文:微信开放平台 公众号第三方平台开发 教程四 代公众号调用接口的SDK和demo 教程导航: 微信开放平台 公众号第三方平台开发 教程一 平台介绍 微信开放平台 公众号第三方平台开发 教程二 创建 ...
随机推荐
- Kafka Stream 流和状态
4.2将状态操作应用到Kafka Stream 在上图的拓扑中生成了一个购买-交易事件流,拓扑中的一个处理节点根据销售额来计算客户的奖励级分.但在这个处理其中,要做的也仅仅时计算单笔交易的总积分,并转 ...
- MySQL高级10-InnoDB引擎存储架构
一.逻辑存储结构 表空间(Tablespace):一个mysql实例,及一个数据库实例,可以对应多个表空间(ibd文件),用于存储记录,索引等数据. 段(Segment):分为数据段(Leaf nod ...
- k8s1.25版本上实践 Ingress-nginx
背景: 领导要求的最新最新版本k8s...使用ingress-nginx 对外暴露内部服务 环境 节点 master worker 主机/ip calico-master01/192.168.195. ...
- oracle问题:ORA-09817及解决办法
某天以管理员身份登录公司测试库报ORA-09817错误,查了网上的文章说是审计文件没有存储空间造成的.我的这问题也证实了这一点,现将解决步骤分享: 1.发现问题:报ORA-09817 oracle@l ...
- c语言代码练习--函数
函数: 一,概念: 1,在计算科学中,子程序(英语:Subroutione,procedure,function,rotine,method.subprogram,callable unit),是一个 ...
- 其它——Apache-ab压力测试工具使用
文章目录 一 介绍 二 安装 2.1 windows安装 2.2 Linux安装 三 使用 四 参数介绍 一 介绍 Apache Benchmark(简称ab) 是Apache安装包中自带的压力测试工 ...
- 若依(ruoyi)开源系统-多数据源问题踩坑实录
内容概要 上一节内容 介绍了用开源系统若依(ruoyi)搭建页面的过程.在实际项目中,经常遇到多数据源后者主从库的情况.本节记录若依多数据源配置过程中遇到的问题排查过程. 背景描述 1.上一节在r ...
- 《数据结构》王卓老师 p48-p62学习反馈
跟着青岛大学-王卓老师的视频进行到链队列时,运行链队列代码的时候遇到了两个问题: 1.)Program received signal SIGSEGV Segmentation fault 附代码: ...
- 高性能日志脱敏组件:已支持 log4j2 和 logback 插件
项目介绍 日志脱敏是常见的安全需求.普通的基于工具类方法的方式,对代码的入侵性太强,编写起来又特别麻烦. sensitive提供基于注解的方式,并且内置了常见的脱敏方式,便于开发. 同时支持 logb ...
- Rust学习 | Rustlings通关记录与题解
2023年6月19日决定对rust做一个重新的梳理,整理今年4月份做完的rustlings,根据自己的理解来写一份题解,记录在此. 周折很久,因为中途经历了推免的各种麻烦事,以及选择数据库作为未来研究 ...