gin学习笔记(三)—— 会话管理

会话管理


HTTP会话管理

  HTTP 协议的特点是一问一答(请求然后响应)。基本上,Web 应用都实现了用户管理,因此当用户发送请求时,服务器要能识别出是哪个用户,最简单的方法就是客户端每次请求,都附上用户信息。这样既不安全也不高效,故提出会话(Session),会话一般存储用户信息。

服务端会话(Server Side Session)

  会话内容存储在服务器中。服务器会给每个发送请求的客户端分配一个会话id(Session_id),来识别用户。用户发送请求时会附带会话id,服务器通过会话id 查询会话内容,根据会话内容给出服务。

 

客户端会话(Client Side Session)

  会话内容存储在客户端中。客户端发送请求时会附带会话内容,服务器可以直接根据会话内容给出服务。第一次发送请求的新客户端,服务器会为其生成会话内容,并返给客户端。为了防止篡改,服务器会对生成的会话内容进行加密签名。

 

JWT

介绍

  JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。一个 JWT 通常有 HEADER (),PAYLOAD (有效载荷)和 SIGNATURE (签名)三个部分组成,三者之间使用“.”链接。

 JWT 标准只是设计了一个防篡改令牌,并非是为会话管理而设计,常用于客户端会话

  在 Go 语言可以使用github的 jwt 库来实现 JWT 的功能:

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

 

生成JWT

  在jwt库中,生成JWT的函数是:NewWithClaims

复制代码
func NewWithClaims(method SigningMethod, claims Claims, opts ...TokenOption) *Token {
    return &Token{
        Header: map[string]interface{}{
            "typ": "JWT",
            "alg": method.Alg(),
        },
        Claims: claims,
        Method: method,
    }
}
复制代码

  从上述函数可以看到:JWT的Header 已经为我们处理好了,Payload (有效载荷)在库中被称为 Claims,而 Signature(签名)可以用库中现成的加密算法来签名,因此我们只需要编写我们想要的 Calims 就好。Claims 是一个接口:

type Claims interface {
    GetExpirationTime() (*NumericDate, error)
    GetIssuedAt() (*NumericDate, error)
    GetNotBefore() (*NumericDate, error)
    GetIssuer() (string, error)
    GetSubject() (string, error)
    GetAudience() (ClaimStrings, error)
}

  如果没有其他需求,我们可以直接使用 jwt 库给的 jwt.RegisteredClaims :

复制代码
type RegisteredClaims struct {

    Issuer string `json:"iss,omitempty"`  //JWT的签发者
    Subject string `json:"sub,omitempty"`  //JWT的所有者
    Audience ClaimStrings `json:"aud,omitempty"`  //JWT的接收者
    ExpiresAt *NumericDate `json:"exp,omitempty"`  //过期时间
    NotBefore *NumericDate `json:"nbf,omitempty"`  //生效时间
    IssuedAt *NumericDate `json:"iat,omitempty"`  //签发时间
    ID string `json:"jti,omitempty"`  //JWT标识
}
复制代码

来个生成JWT例子:

复制代码
package main

import (
    "time"

    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v5"
)

type Myclaims struct {
    UserName             string `json:"username"`
    PassWord             string `json:"pwd"`
    jwt.RegisteredClaims        // 继承jwt.RegisteredClaims
}

func createToken(user string, pwd string) (*jwt.Token, string, error) {
    claims := Myclaims{  //实例化claims
        UserName: user,
        PassWord: pwd,
        RegisteredClaims: jwt.RegisteredClaims{
            Issuer:    "admin",
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(5 * time.Minute)), // 5分钟后过期
            NotBefore: jwt.NewNumericDate(time.Now()),                      // 签发后立即生效
        },
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // 生成JWT,到这步还没完,还需要签名
    tokenString, err := token.SignedString([]byte("secret")) // 签名

    if err != nil {
        return token, "", err
    }

    return token, tokenString, err
}

func main() {
    r := gin.Default()

    r.POST("/login", func(c *gin.Context) {

        user := c.PostForm("username")
        pwd := c.PostForm("pwd")

        token, tokenString, _ := createToken(user, pwd)

        c.JSON(200, gin.H{
            "没签名的JWT": token,
            "签名后的JWT": tokenString,
        })
    })

    r.Run("127.0.0.1:8000")
}
复制代码

  效果:

   除了jwt.NewWithClaims,jwt.New也可以生成JWT,只不过 New 只能携带实现了Claims 接口的 map[string]inferface{},所以一般都用 jwt.NewWithClaims:

复制代码
func createToken2(user string, pwd string) (*jwt.Token, string, error) {

    token := jwt.New(jwt.SigningMethodHS256)
    claims := token.Claims.(jwt.MapClaims)

    //设置claims
    claims["username"] = user
    claims["pwd"] = pwd
    claims["iss"] = "admin"
    claims["exp"] = jwt.NewNumericDate(time.Now().Add(5 * time.Minute))
    claims["nbf"] = jwt.NewNumericDate(time.Now())

    tokenString, err := token.SignedString([]byte("secret"))

    if err != nil {
        return token, "", err
    }

    return token, tokenString, err
}
复制代码

 

解析JWT

  使用 jwt.ParseWithClaims 来解析JWT:

复制代码
func ParseToken(tokenString string) (*Myclaims, error) { // 解析token

    token, err := jwt.ParseWithClaims(tokenString, &Myclaims{}, func(token *jwt.Token) (i interface{}, err error) {
        return []byte("secret"), nil
    })

    if err != nil {
        return nil, err
    }

    return token.Claims.(*Myclaims), nil
}

func main() {
    r := gin.Default()

    r.POST("/test", func(c *gin.Context) {
        //获取json数据
        data, _ := c.GetRawData()
        var body map[string]interface{}
        _ = json.Unmarshal(data, &body)

        //从 json 获取token
        token := body["token"]

        //解析token
        claims, err := ParseToken(token.(string))

        if err != nil {
            c.JSON(200, gin.H{
                "error":    err.Error(),
                "接收的token": token,
            })
            return
        }

        user := claims.UserName
        pwd := claims.PassWord

        c.JSON(200, gin.H{
            "解析出的claims":   claims,
            "提取出的username": user,
            "提取出的passwoed": pwd,
        })
    })

    r.Run("127.0.0.1:8000")
}
复制代码

  效果:

 (上面的token过期了)

关于JWT的操作一般放在中间件上去执行,而不是在路由里

 

Cookie-Session

介绍

服务端会话常用 Cookie-Session 模式实现用户认证,其相关流程大致如下:

  1. 用户在浏览器端填写用户名和密码,并发送给服务端
  2. 服务端对用户名和密码校验通过后会生成一份保存当前用户相关信息的 session 数据和一个与之对应的标识(通常称为session_id)
  3. 服务端返回响应时将上一步的 session_id 写入用户浏览器的 Cookie
  4. 后续用户来自该浏览器的每次请求都会自动携带包含 session_id 的 Cookie
  5. 服务端通过请求中的 session_id 就能找到之前保存的该用户那份 session 数据,从而获取该用户的相关信息。
标准库 net/http 中定义了 Cookie,它代表一个出现在HTTP响应头中Set-Cookie的值里或者HTTP请求头中Cookie的值的HTTP cookie:
复制代码
type Cookie struct {
    Name       string
    Value      string
    Path       string  //设置当前的 Cookie 值只有在访问指定路径时才能被服务器程序读取
    Domain     string  //表达这个cookie是属于哪个网站的,默认值是当前正在访问的Host的域名
    Expires    time.Time  //过期时间,若为空则Cookie随浏览器关闭而销毁
    RawExpires string
    // MaxAge=0表示未设置Max-Age属性
    // MaxAge<0表示立刻删除该cookie,等价于"Max-Age: 0"
    // MaxAge>0表示存在Max-Age属性,单位是秒
    MaxAge   int
    Secure   bool  //是否安全传输,设置后只能随https请求发送
    HttpOnly bool  //设置是否仅能用于传输
    Raw      string
    Unparsed []string // 未解析的“属性-值”对的原始文本
}
复制代码

 

设置Cookie

  gin 提供函数支持设置Cookie:

func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)

  其内容是调用标准库 net/http 中的 SetCookie函数:

func SetCookie(w ResponseWriter, cookie *Cookie) {
    if v := cookie.String(); v != "" {
        w.Header().Add("Set-Cookie", v)    //向响应头添加Cookie
    }
}

 

获取Cookie

获取Cookie有两种方法:

// 解析并返回该请求的Cookie头设置的所有cookie
func (r *Request) Cookies() []*Cookie

// 返回请求中名为name的cookie,如果未找到该cookie会返回nil, ErrNoCookie。
func (r *Request) Cookie(name string) (*Cookie, error)

 

 

例子

复制代码
func main() {
    r := gin.Default()   
r.GET(
"/setcookie", func(c *gin.Context) { //cookie名字为gin_cookie,内容是"设置一个cookie",允许/getcookie获取 c.SetCookie("gin_cookie", "设置一个cookie", 3600, "/", "/getcookie", false, true) }) r.GET("/getcookie", func(c *gin.Context) { cookie, err := c.Cookie("gin_cookie") // 获取Cookie if err != nil { c.JSON(200, gin.H{ "getcookie_error": err.Error(), }) return } c.JSON(200, gin.H{ "get_cookie": cookie, }) }) r.Run("127.0.0.1:8000") }
复制代码

  效果:

 

posted @   昨晚没做梦  阅读(74)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示