当前位置: 代码网 > it编程>前端脚本>Golang > Go语言中Gin框架使用JWT实现登录认证的方案

Go语言中Gin框架使用JWT实现登录认证的方案

2024年11月26日 Golang 我要评论
gin框架jwt登录认证背景: 在如今前后端分离开发的大环境中,我们需要解决一些登陆,后期身份认证以及鉴权相关的事情,通常的方案就是采用请求头携带token的方式进行实现。在开始学习jwt之前,我们可

gin框架jwt登录认证

背景: 在如今前后端分离开发的大环境中,我们需要解决一些登陆,后期身份认证以及鉴权相关的事情,通常的方案就是采用请求头携带token的方式进行实现。

在开始学习jwt之前,我们可以先了解下早期的几种方案。

1. token、cookie、session的区别

cookie

cookie总是保存在客户端中,按在客户端中的存储位置,可分为内存cookie硬盘cookie
内存cookie由浏览器维护,保存在内存中,浏览器关闭后就消失了,其存在时间是短暂的。硬盘cookie保存在硬盘里,有一个过期时间,除非用户手工清理或到了过期时间,硬盘cookie不会被删除,其存在时间是长期的。所以,按存在时间,可分为非持久cookie和持久cookie
cookie 是一个非常具体的东西,指的就是浏览器里面能永久存储的一种数据,仅仅是浏览器实现的一种数据存储功能。

cookie由服务器生成,发送给浏览器,浏览器把cookie以key-value形式保存到某个目录下的文本文件内,下一次请求同一网站时会把该cookie发送给服务器。由于cookie是存在客户端上的,所以浏览器加入了一些限制确保cookie不会被恶意使用,同时不会占据太多磁盘空间,所以每个域的cookie数量是有限的。

session

session字面意思是会话,主要用来标识自己的身份。比如在无状态的api服务在多次请求数据库时,如何知道是同一个用户,这个就可以通过session的机制,服务器要知道当前发请求给自己的是谁
为了区分客户端请求,服务端会给具体的客户端生成身份标识session,然后客户端每次向服务器发请求的时候,都带上这个“身份标识”,服务器就知道这个请求来自于谁了。至于客户端如何保存该标识,可以有很多方式,对于浏览器而言,一般都是使用cookie的方式

服务器使用session把用户信息临时保存了服务器上,用户离开网站就会销毁,这种凭证存储方式相对于cookie来说更加安全,但是session会有一个缺陷: 如果web服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候session会丢失。因此,通常企业里会使用redis,memcached缓存中间件来实现session的共享,此时web服务器就是一个完全无状态的存在,所有的用户凭证可以通过共享session的方式存取,当前session的过期和销毁机制需要用户做控制。

token

token的意思是“令牌”,是用户身份的验证方式,最简单的token组成: uid(用户唯一标识)+time(当前时间戳)+sign(签名,由token的前几位+盐以哈希算法压缩成一定长度的十六进制字符串),同时还可以将不变的参数也放进token

今天我们主要想讲的就是json web token,也就是本篇的主题:jwt

2. 什么是jwt

jwt: json web token,是一种用于身份验证和授权的开放标准,jwt可以在网络应用间安全的传输。jwt由三个部分组成:头部(header)、载荷(payload)和签名(signature)
jwt具有可扩展性、简单、轻量级、跨语言等优点,是前后端分离框架中最常用的验证方式。jwt工作流程大致如下:

1.当用户成功登录后,服务器会生成一个jwt并返回给客户端

2.客户端将jwt储存在本地

3.之后每次向服务器请求时都会在请求头中携带jwt

4.服务器会验证jwt的合法性,并根据其中的信息判断用户的身份和权限,从而决定是否允许用户访问请求的资源

jwt token组成部分

header: 用来指定使用的算法alg(hmac hs256 rs256)和token类型typ(如jwt)payload: 包含声明(要求),声明通常是用户信息或其他数据的声明,比如用户id,名称,邮箱等. 声明可分为三种: registered,public,privatesignature: 用来保证jwt的真实性,可以使用不同的算法

header
{
“alg”: “hs256”,
“typ”: “jwt”
}
对上面的json进行base64编码即可得到jwt的第一个部分

payload
载荷(payload)用来表示需要传递的数据,例如用户id、权限信息等,
包含声明(claims),即用户的相关信息。这些信息可以是公开的,也可以是私有的,但应避免放入敏感信息,因为该部分可以被解码查看。载荷中的声明可以验证,但不加密。

常用的字段如下:
issuer:发行人,缩写iss
expiresat:过期时间,exp
subject:主题信息,sub
notbefore:在此时间之前不可以用,nbf
issuedat:发布时间,iat
id:jwt的id,jti

registered claims: 预定义的声明,通常会放置一些预定义字段,比如过期时间,主题等(iss:issuer,exp:expiration time,sub:subject,aud:audience)public claims: 可以设置公开定义的字段private claims: 用于统一使用他们的各方之间的共享信息

{
“sub”: “xxx-api”,
“name”: “bgbiao.top”,
“admin”: true
}

对payload部分的json进行base64编码后即可得到jwt的第二个部分

注意: 不要在header和payload中放置敏感信息,除非信息本身已经做过脱敏处理

signature

为了得到签名部分,必须有编码过的header和payload,以及一个秘钥,签名算法使用header中指定的那个,然后对其进行签名即可

signature = hmac sha256(base64urlencode(header)+“.”+base64urlencode(payload),secret)

签名是用于验证消息在传递过程中有没有被更改,并且,对于使用私钥签名的token,它还可以验证jwt的发送方是否为它所称的发送方。

jwt token: base64(header).base64(payload).signature
jwt官网:https://jwt.io

下图就是一个典型的jwt-token的组成部分。

在这里插入图片描述

3. 什么时候用jwt

  • authorization(授权): 典型场景,用户请求的token中包含了该令牌允许的路由,服务和资源。单点登录其实就是现在广泛使用jwt的一个特性
  • information exchange(信息交换): 对于安全的在各方之间传输信息而言,json web tokens无疑是一种很好的方式.因为jwts可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容没有被篡改。

jwt的工作流程

在这里插入图片描述

基于token的身份认证是无状态的,服务器或者session中不会存储任何用户信息.(很好的解决了共享session的问题)

  • 用户携带用户名和密码请求获取token(接口数据中可使用appid,appkey等)
  • 服务端校验用户凭证,并返回用户或客户端一个token
  • 客户端存储token,并在请求头中携带token
  • 服务端校验token并返回数据
  • 随后客户端的每次请求都需要使用token
  • token应该放在header中

所以,基本上整个过程分为两个阶段,第一个阶段,客户端向服务端获取token,第二阶段,客户端带着该token去请求相关的资源.
通常比较重要的是,服务端如何根据指定的规则进行token的生成。
在认证的时候,当用户用他们的凭证成功登录以后,一个json web token将会被返回。
此后,token就是用户凭证了,你必须非常小心以防止出现安全问题。
一般而言,你保存令牌的时候不应该超过你所需要它的时间。
无论何时用户想要访问受保护的路由或者资源的时候,用户代理(通常是浏览器)都应该带上jwt,典型的,通常放在authorization header中,用bearer schema: authorization: bearer <token>
服务器上的受保护的路由将会检查authorization header中的jwt是否有效,如果有效,则用户可以访问受保护的资源。如果jwt包含足够多的必需的数据,那么就可以减少对某些操作的数据库查询的需要,尽管可能并不总是如此。
如果token是在授权头(authorization header)中发送的,那么跨源资源共享(cors)将不会成为问题,因为它不使用cookie.

在这里插入图片描述

  • 客户端向授权接口请求授权
  • 服务端授权后返回一个access token给客户端
  • 客户端使用access token访问受保护的资源

4. gin框架封装jwt

我们在go官方提供的包里面搜jwt https://pkg.go.dev/
我们使用第一个最常用的

在这里插入图片描述

下载

go get -u github.com/golang-jwt/jwt/v5

在这里插入图片描述

jwt的功能很多,我们不用每个都搞清楚,目前只需要把examples里面的就可以了

我们先生成一个token,然后再去解析这个token

在这里插入图片描述

我们使用可以自定义参数的

在这里插入图片描述

1. 生成token

package jwtutil

import (
    "github.com/golang-jwt/jwt/v5"
    "jingtian/myproject/config"
    "time"
)

// 这种不能用段变量方式创建
var mysigningkey = []byte(config.jwtsecretkey)

// mycustomclaims 1.自定义声明类型
type mycustomclaims struct {
    username string `json:"username"`
    jwt.registeredclaims
}

// gentoken 2. 封装生成token的函数
// 根据官方定义,返回一个token和error
func gentoken(username string) (string, error) {
    // create claims with multiple fields populated
    claims := mycustomclaims{
        username, //根据用户名来动态生成
        jwt.registeredclaims{
            // a usual scenario is to set the expiration time relative to the current time
            expiresat: jwt.newnumericdate(time.now().add(config.tokenexpire)), //过期时间,是个可变参数
            issuedat:  jwt.newnumericdate(time.now()),
            notbefore: jwt.newnumericdate(time.now()),
            issuer:    "jingtian",
            subject:   "myjwt",
        },
    }
    //生成token
    token := jwt.newwithclaims(jwt.signingmethodhs256, claims)
    ss, err := token.signedstring(mysigningkey)
    return ss, err
}

在这里插入图片描述

然后,在登录的地方调用,先登录,用户名和密码是对的情况下。生成token

在这里插入图片描述

2. 解析token

看下官网用法

在这里插入图片描述

我们使用第一个custom

在这里插入图片描述

我们在config.go里面封装成函数

// parsetoken 3.解析token
func parsetoken(tokenstring string) (*mycustomclaims, error) {
    token, err := jwt.parsewithclaims(tokenstring, &mycustomclaims{}, func(token *jwt.token) (interface{}, error) {
        // byte里面改成我们设置的key
        return []byte(config.jwtsecretkey), nil
    })
    if err != nil {
        //fmt.println("解析token失败", err.error())
        fields := map[string]interface{}{
            "错误原因": err.error(),
        }
        logs.error(fields, "解析token失败")
        return nil, err
    } else if claims, ok := token.claims.(*mycustomclaims); ok && token.valid {
        //说明token合法
        //fmt.println(claims.username, claims.registeredclaims.issuer)
        return claims, nil
    } else {
        logs.error(nil, "token不合法")
        return nil, err
    }

}

在这里插入图片描述

在main里面调用,测试

//验证token是否合法
claims, tokenerr := jwtutil.parsetoken("eyjhbgcioijiuzi1niisinr5cci6ikpxvcj9.eyj1c2vybmftzsi6imppbmd0awfuiiwiaxnzijoiamluz3rpyw4ilcjzdwiioijtewp3dcisimv4cci6mtczmdm0mjk3miwibmjmijoxnzmwmzqyoduylcjpyxqioje3mzazndi4ntj9.aqbeft2n1zkoisfq-b1vvbdrnyhmipz17ct-r0snvgu")

if tokenerr != nil {
    fmt.println("token不合法: ", tokenerr)
} else {
    fmt.println("token合法:", claims)
}

使用合法的token验证

在这里插入图片描述

我们设置的token过期时间是2分钟,过两分钟再验证
可以看到token不合法,已过期

在这里插入图片描述

登录登出代码

我们在router层写路由信息

package auth

import (
    "github.com/gin-gonic/gin"
    "jingtian/myproject/controllers/auth"
)

// 实现登录接口
func login(authgroup *gin.routergroup) {
    //具体逻辑写到控制器controller里面
    authgroup.post("/login", auth.login)
}

// 实现登出接口
func loginout(authgroup *gin.routergroup) {
    authgroup.get("/loginout", auth.loginout)
}

// registersubrouter 认证子路由
func registersubrouter(g *gin.routergroup) {
    //配置登录功能路由策略
    authgroup := g.group("/auth")
    login(authgroup)
    loginout(authgroup)

}

在这里插入图片描述

在controllers.go里面写具体的登录登出逻辑

package auth

import (
    "github.com/gin-gonic/gin"
    "jingtian/myproject/utils/logs"
    "net/http"
)

// userinfo 创建结构体,绑定用户信息
type userinfo struct {
    username string `json:"username"`
    password string `json:"password"`
}

// login 登录逻辑
func login(c *gin.context) {
    //1.获取前端传来的用户信息
    var user userinfo
    //绑定结构体 shouldbing绑定,可以根据结构体中的标签来 确定请求的content-type类型
    if err := c.shouldbind(&user); err != nil {
        c.json(http.statusbadrequest, gin.h{"error": err.error()})
        return
    }

    logs.debug(map[string]interface{}{
        "用户名": user.username,
        "密码":  user.password,
    }, "开始验证用户登录信息")

}

// loginout 登出
func loginout(c *gin.context) {
    //如果我们将token存到了redis里面,需要做清除逻辑,保存到内存,只需要前端把存到本地的token删掉即可
    c.json(http.statusok, gin.h{
        "code": 200,
        "msg":  "success",
    })

    logs.debug(nil, "退出成功")
}

在这里插入图片描述

在routers.go里面调用

在这里插入图片描述

在main.go里面调用

在这里插入图片描述

运行,postman测试登录接口

在这里插入图片描述

拿到数据

在这里插入图片描述

测试登出接口

在这里插入图片描述

登录验证

package auth

import (
    "github.com/gin-gonic/gin"
    "jingtian/myproject/utils/jwtutil"
    "jingtian/myproject/utils/logs"
    "net/http"
)

// userinfo 创建结构体,绑定用户信息
type userinfo struct {
    username string `json:"username"`
    password string `json:"password"`
}

// login 登录逻辑
func login(c *gin.context) {
    //1.获取前端传来的用户信息
    var user userinfo
    //绑定结构体 shouldbing绑定,可以根据结构体中的标签来 确定请求的content-type类型
    if err := c.shouldbind(&user); err != nil {
        c.json(http.statusbadrequest, gin.h{"error": err.error()})
        return
    }

    logs.debug(map[string]interface{}{
        "用户名": user.username,
        "密码":  user.password,
    }, "开始验证用户登录信息")

    //登录成功之后开始验证,验证通过生成token
    //模拟从数据库中查询用户名和密码
    if user.username == "jingtian" && user.password == "123456" {
        logs.info(nil, "用户名密码正确")
        //生成token
        ss, err := jwtutil.gentoken(user.username)
        if err != nil {
            logs.error(map[string]interface{}{
                "用户名":  user.username,
                "错误信息": err.error(),
            }, "用户名密码正确,生成token失败")
            c.json(http.statusok, gin.h{
                "error":  err.error(),
                "status": 400,
            })
            return
        }
        logs.info(nil, "用户名密码正确,生成token成功")
        //将token返回给前端
        data := make(map[string]interface{})
        data["token"] = ss
        c.json(http.statusok, gin.h{
            "status": 200,
            "data":   data,
            "msg":    "登录成功",
        })
        return

    } else {
        c.json(http.statusok, gin.h{
            "status": 400,
            "msg":    "用户名或密码不正确",
        })
        return
    }

}

// loginout 登出
func loginout(c *gin.context) {
    //如果我们将token存到了redis里面,需要做清除逻辑,保存到内存,只需要前端把存到本地的token删掉即可
    c.json(http.statusok, gin.h{
        "code": 200,
        "msg":  "success",
    })

    logs.debug(nil, "退出成功")
}

用户名和密码都正确,返回token

在这里插入图片描述

当用户名或密码不正确,拿不到token

在这里插入图片描述

登录验证成功后,前端在访问其他接口的时候,都需要验证是否携带正确的token

此时,我们需要通过中间件来验证,除了登录和登出的接口,其他接口都需要验证

// package middlewares 中间件层 配置中间件
package middlewares

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "jingtian/myproject/utils/jwtutil"
    "jingtian/myproject/utils/logs"
    "net/http"
)

// checktoken 校验jwt token
func checktoken(c *gin.context) {

    //验证token是否合法,除了login和loginout之外的请求,都要验证token是否合法
    //获取请求路径,c.fullpath()获取请求群路径  这个也可以c.request.url.path
    //requesturl := c.fullpath()
    requesturl := c.request.url.path
    //requesturl := c.fullpath()
    logs.debug(map[string]interface{}{
        "url": requesturl,
    }, "获取的请求路径")
    //我们可以做下判断,当请求路径不是登录或者登出的路径时,就做token校验
    if requesturl == "/api/auth/login" || requesturl == "/api/auth/loginout" {
        c.next()

    } else {
        //其他接口需要验证合法性
        //token一般会存放在请求头header中的 authorization字段中
        //先获取请求头中是否包含该字段
        //tokenstring := c.request.header.get("authorization")
        tokenstring := c.getheader("authorization")
        if tokenstring == "" {
            c.json(http.statusok, gin.h{
                "code": http.statusunauthorized,
                "msg":  "请求没有携带token,请登录后在尝试",
            })
            c.abort()

        } else {
            claims, tokenerr := jwtutil.parsetoken(tokenstring)

            if tokenerr != nil {
                fmt.println("token不合法: ", tokenerr)
                c.json(http.statusok, gin.h{
                    "code": http.statusunauthorized,
                    "msg":  "token不合法",
                })
                c.abort()

            } else {
                //验证通过的话,把claims放在context里面
                c.set("claims", claims)
                //其他的逻辑里面,如果需要获取claims值,可以使用c.get("claims")
                c.next()
                fmt.println("token合法:", claims)

            }
        }

    }

}

在这里插入图片描述

正常的登录登出,都不验证token

在这里插入图片描述

登录生成token

在这里插入图片描述

其他请求,不带authorization 请求头的,一律拦截

在这里插入图片描述

带上authorization请求头,但是token不合法的,也拦截

在这里插入图片描述

只有带上authorization请求头,token也合法的请求,才能通过

在这里插入图片描述

以上就是go语言中gin框架使用jwt实现登录认证的方案的详细内容,更多关于go gin实现jwt登录认证的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com