【Golang】基于OAuth2.0微信扫码实现客户端用户登录(原理+代码实现+视频讲解)

本文详细介绍了OAuth2.0授权协议原理,重点讲解了授权码模式,并给出了使用Golang实现微信扫码登录的步骤,包括内网穿透配置、微信测试账号申请、验证和微信服务器连接、二维码生成及回调地址设置。最后进行了测试,当用户扫码登录成功后,日志打印出用户登录信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:

细心汇总,包括原理+配置+代码详细实现

在这里插入图片描述

原理讲解

什么是OAuth2.0

OAuth 2.0是一种授权协议,用于授权第三方应用访问用户资源,而无需将用户的凭据(例如用户名和密码)直接提供给第三方应用。这种授权机制为用户提供了更好的安全性,同时允许用户更好地控制他们的数据。

简介说明:

一个关于授权的开放网络标准 允许用户授权第三方应用访问用户存储在其他服务提供者上的信息 不需要将用户名和密码提供给第三方应用

OAuth 2.0协议的参与者有以下几种角色:

  1. 资源所有者(Resource Owner):通常是指用户,拥有访问受保护资源的所有权。(资源所有者,用户)

  2. 客户端(Client):第三方应用程序,希望获取资源所有者的授权来访问受保护的资源。(你要登录的网站)

  3. 授权服务器(Authorization Server):负责验证资源所有者的身份并颁发访问令牌给客户端。(授权服务器,服务提供商专门来处理认证授权的服务器)

  4. 资源服务器(Resource Server):存储和提供受保护资源的服务器,只接受有效的访问令牌来授权客户端对资源的访问。(资源服务器,服务提供商存放用户生成的资源的服务器)

  5. 用户代理(User Agent):通常是指用户使用的浏览器或移动设备,用于与授权服务器进行交互。(用户代理,比如浏览器)

  6. 服务提供商(第三方服务提供商,github,微信等)

在这里插入图片描述
OAuth 2.0的授权流程通常如下所示:

  1. 客户端向资源所有者发起授权请求,请求访问特定资源。

  2. 资源所有者登录到授权服务器,并向其授予访问权限。

  3. 授权服务器生成一个访问令牌(Access Token)。

  4. 授权服务器将访问令牌传递给客户端。

  5. 客户端使用访问令牌来请求受保护资源。

  6. 资源服务器验证访问令牌的有效性,并决定是否授权客户端访问受保护资源。

解决方案

在这里插入图片描述

在OAuth 2.0中,有多种授权流程可供选择,以适应不同的应用场景和安全需求。最常见的授权流程包括:

  1. 授权码模式(Authorization Code Grant):适用于Web应用和服务器端应用。客户端通过授权码获取访问令牌。

  2. 简单模式(Implicit Grant):适用于纯前端应用和移动应用。访问令牌直接从授权服务器返回给客户端。

  3. 密码模式(Resource Owner Password Credentials Grant):适用于受信任的应用,允许客户端直接使用用户的用户名和密码来交换访问令牌。

  4. 客户端模式(Client Credentials Grant):适用于没有用户交互的应用,客户端使用自己的凭证来获取访问令牌。

这里我们主要讲解授权码模式

授权码模式讲解

授权流程
在这里插入图片描述
在这里插入图片描述
申请token接口
在这里插入图片描述

认证流程

请添加图片描述
请添加图片描述
当用户将自己的服务地址在此处认证之后,就相当于微信服务器将该域名地址下的链接认定为其服务下的一个客服端,之后便会颁发给他一个clientId和ClientSecret,这一个信息标识了对应的是你的第三方服务,而不是其他网站,同时和这个ClientSecret是用来去获取token来加密使用

在这里插入图片描述
请添加图片描述
当用户通过权限后,就会回到回调地址,同时微信会将state+code返回给第三方应用,第三方应用拿到code和本身的clientId和clientSecret去向微信服务商获取AccessToken令牌,拿着这个令牌就可以去微信资源服务器去获取用户的相关信息

Go语言实现微信扫码登录

1. 内网穿透配置

配置网站:NatApp网站

  1. 进入网站 选择免费通道
    在这里插入图片描述
  2. 配置通道信息,将本地的配置添加进去
    在这里插入图片描述
  3. 之后下载客户端
    在这里插入图片描述
  4. 配置
    .在这里插入图片描述
    config配置文件:
#将本文件放置于natapp同级目录 程序将读取 [default] 段
#在命令行参数模式如 natapp -authtoken=xxx 等相同参数将会覆盖掉此配置
#命令行参数 -config= 可以指定任意config.ini文件
[default]
authtoken=你的隧道token                     #对应一条隧道的authtoken
clienttoken=                    #对应客户端的clienttoken,将会忽略authtoken,若无请留空,
log=none                        #log 日志文件,可指定本地文件, none=不做记录,stdout=直接屏幕输出 ,默认为none
loglevel=ERROR                  #日志等级 DEBUG, INFO, WARNING, ERROR 默认为 DEBUG
http_proxy=                     #代理设置 如 http://10.123.10.10:3128 非代理上网用户请务必留空

./natapp -authtoken=9ab6b9040a624f40

  1. 配置完成
    在这里插入图片描述

2. 微信测试账号申请

微信公众平台测试号申请链接:

页面1内容讲解:
在这里插入图片描述
页面内容2讲解:
在这里插入图片描述
页面内容3讲解:
在这里插入图片描述
页面内容4讲解:
在这里插入图片描述
以上就是配置页面讲解,下面进行代码进行阐述:

3. 验证和微信服务器连接

  1. 确认路由的地址:
    在这里插入图片描述

// TOKEN 假设您在Go代码中定义了一个名为TOKEN的常量,用于存储您的令牌值
const TOKEN = "111"

// 配置公众号的token
func CheckSignature(c *gin.Context) {
	// 获取查询参数中的签名、时间戳和随机数
	signature := c.Query("signature")
	timestamp := c.Query("timestamp")
	nonce := c.Query("nonce")
	echostr := c.Query("echostr")
	// 创建包含令牌、时间戳和随机数的字符串切片
	tmpArr := []string{TOKEN, timestamp, nonce}
	// 对切片进行字典排序
	sort.Strings(tmpArr)
	// 将排序后的元素拼接成单个字符串
	tmpStr := ""
	for _, v := range tmpArr {
		tmpStr += v
	}
	// 对字符串进行SHA-1哈希计算
	tmpHash := sha1.New()
	tmpHash.Write([]byte(tmpStr))
	tmpStr = fmt.Sprintf("%x", tmpHash.Sum(nil))
	fmt.Println(tmpStr)
	fmt.Println(signature)
	// 将计算得到的签名与请求中提供的签名进行比较,并根据结果发送相应的响应
	if tmpStr == signature {
		c.String(200, echostr)
		redis.Client.Set(context.Background(), "library:token", tmpStr, 7*24*time.Hour)
	} else {
		c.String(403, "签名验证失败 "+timestamp)
	}
}

在这里插入图片描述
点击 提交 如果显示配置成功, 就说明你的本地服务器已经可以和微信服务器建立连接了,接下来就进行二维码的生成和过程处理。

二维码生成

// Redirect 微信扫码登录
// @Summary 用户登录接口3
// @Description 通过微信扫码登录,手机进行登录验证
// @Tags 公开
// @Accept json
// @Produce application/json
// @Param Url query string true "内网穿透地址"
// @Router /api/v1/wechat/login [get]
func Redirect(c *gin.Context) {
	path := c.Query("Url")
	state := Pcaptcha.RandString(5)                                              //防止跨站请求伪造攻击 增加安全性
	redirectURL := url.QueryEscape("http://" + path + "/api/v1/wechat/callback") //userinfo,
	wechatLoginURL := fmt.Sprintf("https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&state=%s&scope=snsapi_userinfo#wechat_redirect", "你的appid", redirectURL, state)
	wechatLoginURL, _ = url.QueryUnescape(wechatLoginURL)
	// 生成二维码
	qrCode, err := qrcode.Encode(wechatLoginURL, qrcode.Medium, 256)
	if err != nil {
		// 错误处理
		c.String(http.StatusInternalServerError, "Error generating QR code")
		return
	}
	// 将二维码图片作为响应返回给用户
	c.Header("Content-Type", "image/png")
	c.Writer.Write(qrCode)
}

回调地址


func Callback(c *gin.Context) {
	// 获取微信返回的授权码
	code := c.Query("code")
	// 向微信服务器发送请求,获取access_token和openid
	tokenResp, err := http.Get(fmt.Sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", "appid", "appsecret", code))
	if err != nil {
		fmt.Println(err)
		resp := &ResponseData{
			Data:    nil,
			Message: "error,获取token失败",
			Code:    CodeServerBusy,
		}
		c.JSON(http.StatusBadRequest, resp)
		return
	}
	// 解析响应中的access_token和openid
	var tokenData struct {
		AccessToken  string `json:"access_token"`
		ExpiresIn    int    `json:"expires_in"`
		RefreshToken string `json:"refresh_token"`
		OpenID       string `json:"openid"`
		Scope        string `json:"scope"`
	}
	if err1 := json.NewDecoder(tokenResp.Body).Decode(&tokenData); err1 != nil {
		resp := &ResponseData{
			Data:    nil,
			Message: "error,获取token失败",
			Code:    CodeServerBusy,
		}
		c.JSON(http.StatusBadRequest, resp)
		return
	}
	userInfoURL := fmt.Sprintf("https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s", tokenData.AccessToken, tokenData.OpenID)
	userInfoResp, err := http.Get(userInfoURL)
	if err != nil {
		// 错误处理
		zap.L().Error("获取失败")
		return
	}
	defer userInfoResp.Body.Close()

	//------------------------------------
	var userData struct {
		OpenID   string `json:"openid"`
		Nickname string `json:"nickname"`
	}
	if err1 := json.NewDecoder(userInfoResp.Body).Decode(&userData); err1 != nil {
		// 错误处理
		zap.L().Error("获取用户信息失败")
		return
	}
	//用户的名字
	var user1 model.User
	nickname := userData.Nickname
	if err2 := mysql.DB.Where("user_name=?", nickname).First(&user1).Error; err2 != nil {
		if errors.Is(err2, gorm.ErrRecordNotFound) {
			user1.UserName = nickname
			user1.UserID, _ = snowflake.GetID()
			user1.Identity = "普通用户"
		} else {
			zap.L().Error("验证登录信息过程中出错")
			ResponseError(c, CodeServerBusy)
			return
		}
	}
	//添加jwt验证
	atoken, rtoken, err3 := jwt.Token.GetToken(uint64(user1.UserID), user1.UserName, user1.Identity)
	if err3 != nil {
		zap.L().Error("生成JWT令牌失败")
		return
	}
	c.Header("Authorization", atoken)
	//发送成功响应
	ResponseSuccess(c, &model.LoginData{
		AccessToken:  atoken,
		RefreshToken: rtoken,
	})
	zap.L().Info("登录成功")
	return
}


测试

测试接口:
在这里插入图片描述
手机端认证后收到返回信息
在这里插入图片描述

日志打印输出信息:用户登录成功
在这里插入图片描述

给个三连吧,创作不易,感谢支持!! 下一篇文章更加精彩!
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

万物皆可der

感谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值