微信扫码登陆验证在Go系统开发时的应用与实践

微信扫码登录实现

登录流程

总体来说,就是三步:

  • 点击微信登录,跳转到微信页面
  • 微信扫码登录,确认登陆
  • 微信跳转回来

这里,我们就得,明确两个问题:

  1. 跳到微信界面,跳过去的 URL 是什么?
  2. 跳转回来的 URL 是什么?
    这些我们就得从微信给我们提供的 API 出发了。

微信登录 API

微信扫码登录其实是一个 OAuth2 授权过程。简单的说,即使你作为用户授权第三方应用获得了对应的access_token, 第三方应用就认为你登录了。

在大多数场景下,第一次登录的时候还会尝试回去用户的信息。


从上面的图可以看出来,我们需要构建一个 URL,里面要携带上一些参数:

  • appid:在微信开放平台注册的 ID
  • redirect_uri:微信扫码登录后跳转回来的地址
  • response_type:固定为 code
  • scope:应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login
  • state:用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验

用户允许授权后,将会重定向到redirect_uri的网址上,并且带上code和state参数。

redirect_uri?code=CODE&state=STATE

如果用户禁止授权,则会阻止重定向。

从微信跳转回来后,会携带上一个 code,我们要用这个 code 去微信里面换取一个access_token。也是一个发起调用的过程,这里就需要传入appidsecret

要点总结

要想实现微信登录,必须经过两次跳转:

  • 点击微信登录,跳转到微信页面。跳转过去的地址是微信的扫码地址,根据要求必须携带上redirect_uri,appidstate三个属性
  • 微信扫码登录,确认登陆,从微信跳转回来。跳转回来的地址就是redirect_uri的地址。微信此时会携带上临时的授权码code
  • 后台处理redirect_uri中带过来的 code,找微信换取真正的长时间有效的授权码

设计过程

接口抽象


经过前面的分析,我们应该明确,需要两个接口:

第一个接口:用于构建跳转到微信的 URL

第二个接口:处理微信跳转回来的请求

func (h *OAuth2WeChatHandler) RegisterRoutes(r *gin.Engine) {we := r.Group("/oauth2/wechat")we.GET("/authurl", h.AuthURL)we.Any("/callback", h.Callback)
}

构造URL

构造URL,实质上是拼接字符串的过程,我们需要定义一个 wechat.Service,并且提供实现。

再实现的过程中,我们使用 shortuuid来代替 state字短,因为我们还不知道state现在有什么用途。

var redirectUrl = url.PathEscape("https://test.com/oauth2/wechat/callback")func (ws *service) AuthURL(ctx context.Context) (string, error) {const urlPattern = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_userinfo&state=%s#wechat_redirect"state := uuid.New()return fmt.Sprintf(urlPattern, ws.appId, redirectUrl, state), nil
}

用户 Handler 的实现。

func (h *OAuth2WeChatHandler) AuthURL(ctx *gin.Context) {url, err := h.svc.AuthURL(ctx)if err != nil {ctx.JSON(http.StatusOK, Result{Code: 5,Msg:  "get auth url failed",})}ctx.JSON(http.StatusOK, Result{Code: 2,Msg:  url,})
}

现在扫码之后,我们就可以跳转回需要登陆的页面了。接下来,就考虑怎么进行验证 code

从微信回调回来的 code 是一个临时的授权码,所以还需要调用微信的接口,获得真正的授权码。(本质上也是调用一个 API)

func (ws *service) VerifyCode(ctx context.Context, code string, state string) (domain.WechatInfo, error) {const targetPattern = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code"target := fmt.Sprintf(targetPattern, ws.appId, ws.appSecret, code)req, err := http.NewRequestWithContext(ctx, http.MethodGet, target, nil)if err != nil {return domain.WechatInfo{}, err}resp, err := ws.client.Do(req)if err != nil {return domain.WechatInfo{}, err}decoder := json.NewDecoder(resp.Body)var result Resulterr = decoder.Decode(&result)if err != nil {return domain.WechatInfo{}, err}if result.ErrCode != 0 {return domain.WechatInfo{}, fmt.Errorf("wechat auth error: %s", result.ErrMsg)}return domain.WechatInfo{OpenId:  result.OpenID,UnionId: result.UnionID,}, nil
}

返回字段

当微信校验通过之后,我们会拿到一个 Result 结构,具体这个结构体都有哪些字段呢?

关键的两部分为:授权码和 ID。

  • 授权码部分:
    • access_token:后面我们可以拿着 access_token 去访问微信,获取用户的数据
    • expires_in:access_token 的有效期
    • refresh_token: 当access_token过期之后,我们可以拿着refresh_token去找微信换一个新的access_token

注意,使用 access_tokenrefresh_token是一个典型的长短token实现。
access_token是短token,refresh_token是长token。

  • ID 部分:
    • open_id: 在这个应用下为一的 ID
    • union_id: 在这个公司下唯一的 ID

假设你的公司在微信公众平台上注册了两个产品:A 和 B。
对于某一个用户张三来说,他在A上有一个open_id,在B上也有一个open_id
但是张三在你们公司的A和B两个产品的union_id都是同一个。

type Result struct {ErrCode int64  `json:"errcode"`ErrMsg  string `json:"errmsg"`AccessToken  string `json:"access_token"`ExpiresIn    int64  `json:"expires_in"`RefreshToken string `json:"refresh_token"`OpenID  string `json:"openid"`Scope   string `json:"scope"`UnionID string `json:"unionid"`
}

优化登陆

这个类似于我们之前的短信登陆案例。

  • 如果用户第一次登陆,我们就注册一个新的账号
  • 如果用户不是第一次登陆,就直接设置一个 JWT token
    用户 Handler 接口的实现。
func (h *OAuth2WeChatHandler) Callback(ctx *gin.Context) {code := ctx.Query("code")state := ctx.Query("state")info, err := h.svc.VerifyCode(ctx, code, state)if err != nil {ctx.JSON(http.StatusOK, Result{Code: 5,Msg:  "verify code failed",})}u, err := h.userSvc.FindOrCreateByWechat(ctx, info)if err != nil {ctx.JSON(http.StatusOK, Result{Code: 5,Msg:  "system error",})}err = h.setJWTToken(ctx, u.Id)if err != nil {ctx.JSON(http.StatusOK, Result{Code: 5,Msg:  "set token failed",})}ctx.JSON(http.StatusOK, Result{Code: 2,Msg:  "success",})
}

实现 service 层上的函数。

func (svc *userService) FindOrCreateByWechat(ctx *gin.Context, info domain.WechatInfo) (domain.User, error) {u, err := svc.repo.FindByWechatOpenId(ctx, info.OpenId)if err != repository.ErrUserNotFound {return u, err}u = domain.User{WechatInfo: info,}err = svc.repo.Create(ctx, u)if err != nil || err == repository.ErrUserDuplicate {return u, err}return svc.repo.FindByWechatOpenId(ctx, info.OpenId)
}

实现 repository 层上的函数。

func (ur *CacheUserRepository) FindByWechatOpenId(ctx context.Context, OpenId string) (domain.User, error) {u, err := ur.dao.FindByWechatOpenId(ctx, OpenId)if err != nil {return domain.User{}, err}return ur.toDomainUser(u), nil
}

对于领域对象的设计。

type WechatInfo struct {OpenId  string `json:"openid"`UnionId string `json:"unionid"`
}// User 领域对象,可以理解为 DDD 中的 entity
type User struct {Id       int64Email    stringPassword string//Ctime    time.TimeNickName     stringBirthday     time.TimeIntroduction stringWechatInfo   WechatInfoPhone        string
}

预防 CROS 攻击

这里我们主要是想让大家理解如何使用 state

理解 state 的核心是抓住攻击者让你使用他的临时授权码来登录账号。

具体步骤为:

  • 攻击者首先会弄出来一个绑定微信的临时授权码。
  • 正常用户登录成功
  • 攻击者伪造一个页面,诱导用户点击,攻击者带着正常用户的Cookie(过或者JWT token)去请求,攻击者的临时授权码去绑定。

结果是什么呢?攻击者可以通过微信扫码登录成功,看到正常用户的数据信息。

那么我们如何解决这个问题呢?

整体思路就是:

  • 当生成 AuthURL 的时候,我们标识一下这一次的会话,将 state 和这一次的请求绑定在一起。
  • 等到回调回来的时候,我们看看回调中的 state 是不是我们生成时候用的 state


因为在整个系统中,我们使用的JWT来做身份验证的,所以这里依旧使用JWT。

使用JWT的好处是,直接用JWT里面的state和回调过来的state比较就可以了,不需要存储到 redis 中。

我们这里放在Cookie中,因为微信回来的时候直接经过后端,并未经过前端。

设置 State 到Cookie中。

func (h *OAuth2WeChatHandler) SetStateCookie(ctx *gin.Context, state string) error {token := jwt.NewWithClaims(jwt.SigningMethodHS256, StateClaims{State: state,RegisteredClaims: jwt.RegisteredClaims{ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 10)),},})tokenStr, err := token.SignedString(h.stateKey)if err != nil {return err}ctx.SetCookie("jwt-state", tokenStr, 600, "/oauth2/wechat/callback", "",h.cfg.Secure, true)return nil
}

校验 State 字短。

整个校验的过程就是我们从 JWT 中拿到我们存储的 state,然后进行比较。

func (h *OAuth2WeChatHandler) VerifyState(ctx *gin.Context) error {state := ctx.Query("state")ck, err := ctx.Cookie("jwt-state")if err != nil {return fmt.Errorf("get cookie failed: %w", err)}var sc StateClaimstoken, err := jwt.ParseWithClaims(ck, &sc, func(token *jwt.Token) (interface{}, error) {return h.stateKey, nil})if err != nil || !token.Valid {return fmt.Errorf("verify state failed: %w", err)}if state != sc.State {return errors.New("state not match")}return nil
}

那么就有人会问了,你这还是会有 CORS 的问题呀。但是 Cookie泄漏是一件比较困难的事情,一般是你电脑本身中病毒了,单纯的跨域攻击是没办法拿到的。

这里,我们向大家讲述了如何进行微信扫码登录,并且介绍如何使用state字段。

绝大部分的公司都没有处理 state,所以你要解释清楚,什么情况下不处理state会造成跨域问题,以及如何解决的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/875541.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

异常处理和swagger使用

全局异常处理类 定义全局异常处理类,会将错误全部提交到这个异常处理类中进行处理,这个类会将处理的统一结果响应给前端,如果不添加异常处理类,异常不会按照统一的响应格式进行,前端无法识别,当然也可以在…

vue-快速入门

Vue 前端体系、前后端分离 1、概述 1.1、简介 Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,可以高效地开发用户界面。…

浏览器的最大并发数(http1.1)

HTTP/1.1:每个资源请求通常需要单独的TCP连接,尽管支持Keep-Alive机制,允许在同一个TCP连接上连续发送多个请求。但通常浏览器限制并发TCP连接数(例如,每个域名最多6个并发连接)。 HTTP/2:引入…

HarmonyOS三方库的使用

系统组件难以提供比较优秀的界面,需要第三方库来实现一些比较好看的界面效果 三方库的获取: 开源社区支持OpenHarmony-TPC 和 Openharmony三方库中心仓 先目前已经拥有各种各样的三方库,包括UI 图片 动画 网络 数据存储 工具类 多媒体 安全等…

leetcode 2236.判断根节点是否等于字节点

1.题目要求: 给你一个 二叉树 的根结点 root,该二叉树由恰好 3 个结点组成:根结点、左子结点和右子结点。如果根结点值等于两个子结点值之和,返回 true ,否则返回 false 。2.思路: 直接数组前序遍历,然后判断后面两个…

unity美术资源优化(资源冗余,主界面图集过多)

图片资源冗余: UPR unity的性能优化工具检查资源 1.检查纹理读/写标记 开启纹理资源的读/写标志会导致双倍的内存占用 检查Inspector -> Advanced -> Read/Write Enabled选项 2.检查纹理资源alpha通道 如果纹理的alpha通道全部为0,或者全部为2…

UEFI DebugLib 介绍

1.我们调试中常用Debug 打印信息,这些会输出到BIOS串口日志中 EFI_STATUSEFIAPIHelloWorld2(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE *SystemTable){EFI_STATUS Status;StatusEFI_SUCCESS;gST->ConOut->OutputString(gST->ConOut,L&q…

【vluhub】zabbix漏洞

介绍: zabbix是对服务器资源状态例如、内存空间、CPU、程序运行状态进行检测、设置预警值、短信设置等功能等一款开源工具。配置不当存在未授权,SQL注入漏洞 弱口令 nameadmin&passwordzabbix nameguest&password POST /index.php HTTP/1.1 Host: 192.1…

Flex布局中元素主轴上平均分布 多余的向左对齐

content:父元素 content-item: 子元素 主轴上子元素平均分布 .content {display: flex;flex-wrap: wrap;justify-content: space-between;.service-item {display: flex;flex-direction: column;justify-content: center;align-items: center;width: 80px;height:…

【C++】19.红黑树模拟实现 set 和 map

我们想要实现STL中的set和map,那么第一步就需要看一下库函数是如何实现的: 通过查看源代码我们发现两个容器都包含了stl_tree.h,因此我们猜测此头文件实现的是红黑树。 但是set和map很显然不是使用同一棵树实现的,那么STL库是怎么…

vue3 Axios封装使用

先安装axios: npm install axios 第一步:项目src下创建一个名为request的文件夹(看一下示例图): 然后在request下创建两个api.ts和index.ts的文件 api.ts里的内容:(url写你自己的接口&#xff…

Vscode——如何快速搜索项目工程中的某个文件的位置

第一步:按 shift ctrl p 第二步:然后把 > 删除 第三步:输入文件名称即可

飞书群聊机器人自定义机器人接入,并实现艾特@群成员功能

飞书群聊机器人还是比钉钉的要麻烦一点,钉钉的直接通过手机号就可以艾特群里面的人,但是飞书的要想艾特群里面的人,需要使用用户的 Open ID 或 User ID。这两个ID怎么获取呢?还需要在飞书的开放平台上创建一个应用,然后…

《Java初阶数据结构》----6.<优先级队列之PriorityQueue底层:堆>

前言 大家好,我目前在学习java。之前也学了一段时间,但是没有发布博客。时间过的真的很快。我会利用好这个暑假,来复习之前学过的内容,并整理好之前写过的博客进行发布。如果博客中有错误或者没有读懂的地方。热烈欢迎大家在评论区…

使用FileZilla Cilent快速让手机与电脑进行文件互传(无需生态)

目录 前言使用 FileZilla笔者的话 前言 当设备多的时候文件的传输就成了一种问题。 就比如说我想将手机上的文件传到电脑里面,因为我使用的电脑跟我的手机不是一个生态的,它们唯一的联系或许就是连接到了统一 WIFI 下,也就是说它们在同一个…

【React】全面解析:从基础知识到高级应用,掌握现代Web开发利器

文章目录 一、React 的基础知识1. 什么是 React?2. React 的基本概念3. 基本示例 二、React 的进阶概念1. 状态(State)和属性(Props)2. 生命周期方法(Lifecycle Methods)3. 钩子(Hoo…

计算存储背景与发展

随着云计算、企业级应用以及物联网领域的飞速发展,当前的数据处理需求正以前所未有的规模增长,以满足存储行业不断变化的需求。这种增长导致网络带宽压力增大,并对主机计算资源(如内存和CPU)造成极大负担,进…

TikTok养号的网络环境及相关代理IP知识

TikTok作为一个流行的短视频分享平台,其用户量非常庞大,很多商家和个人都会使用TikTok来进行引流和推广。由于TikTok的规则和政策限制了每个用户每天发布视频的数量,因此许多用户会使用多个账号来发布更多的视频以提高曝光率。 然而&#xff…

Oracle中LISTAGG 函数的介绍以及使用详情

LISTAGG 函数介绍 listagg 函数是 Oracle 11.2 推出的新特性。 其主要功能类似于 wmsys.wm_concat 函数, 即将数据分组后, 把指定列的数据再通过指定符号合并。 LISTAGG 使用 listagg 函数有两个参数: 1、 要合并的列名 2、…

一种多策略改进鹅智能优化算法IGOOSE(2024年新出优化算法) 种群初始化精英反向策略+非线性下降因子+黄金正弦变异策略

一种多策略改进鹅智能优化算法IGOOSE(2024年新出优化算法) 种群初始化精英反向策略非线性下降因子黄金正弦变异策略 文章目录 前言一种多策略改进鹅智能优化算法IGOOSE(2024年新出优化算法) 种群初始化精英反向策略非线性下降因子…