首页 > 其他分享 >gin学习笔记(三)—— 会话管理

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

时间:2024-01-13 22:55:54浏览次数:27  
标签:string JWT jwt 笔记 会话 token Cookie 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") }

  效果:

 

标签:string,JWT,jwt,笔记,会话,token,Cookie,gin
From: https://www.cnblogs.com/Owhy/p/17959974

相关文章

  • 学习笔记4
    RDD操作对于RDD而言,每一次转换操作都会产生不同的RDD,供给下一个“转换”使用,转换得到的RDD是惰性求值的,也就是说,整个转换过程只是记录了转换的轨迹,并不会发生真正的计算,只有遇到行动操作时,才会发生真正的计算,开始从血缘关系源头开始,进行物理的转换操作;常用的RDD转换操作,总结如下......
  • 大三寒假学习进度笔记4
    今日学习时间两小时,开始学习RDD的内容,学习了RDD的创建和RDD算子的定义分类RDD学习:RDD五大特征:RDD是有分区的分区是RDD数据存储的最小单位计算方法会作用到每一个分区上RDD之间是由相互依赖的关系的KV型RDD可以有分区器KV型RDD:存储的数据是二元元组RDD分区数据的读取会尽......
  • 2024/1/13 算法笔记
    1.二分查找的原则当要查找的值target>mid就在mid和right中查找当要查找的值target<mid就在left和mid中查找对于边界条件的处理:while(l<r)mid的取值是[l,r)重点是下面部分,直接决定使用哪个二分模板。1.3中间值归属问题这个问题其实比较灵活,这里我只讨论3种情况,其余情况......
  • AT_arc125_c [ARC125C] LIS to Original Sequence 题解
    题目传送门前置知识贪心|构造解法对于任意一个未加入序列\(P\)的数\(x<A_{i}(1\lei\lek-1)\),如果其放在了\(A_{i}\)的前面,会导致最长上升子序列长度加一,从而不符合题目要求。因此我们需要把\(x\)放在\(A_{i}\)后面,同理,为符合题目要求,我们仅选择放最小的那一个......
  • Python学习笔记
    Python学习因为我之前使用Python复现算法,所以环境早已经进行了配置要点简介脚本语言是一种介乎于HTML和诸如JAVA、VisualBasic、C++等编程语言之间的一种特殊的语言,尽管它更接近后者,但它却不具有编程语言复杂、严谨的语法和规则。有一些脚本语言已经发生了变化,如Py......
  • 微软企业库Unity学习笔记(一)
    微软企业库Unity学习笔记(一) 本文主要介绍:关于Unitycontainer配置,注册映射关系、类型,单实例、已存在对象和指出一些container的基本配置,这只是我关于Unity的学习心得和笔记,希望能够大家多交流相互学习到更多知识,谢谢大家的支持。我们可以通过以下两种方法给Unitycontain......
  • 微软企业库Unity学习笔记(二)
    微软企业库Unity学习笔记(二) 接下来介绍一下依赖注入的方式:构造函数注入属性注入方法注入一、构造函数注入我们将介绍单构造函数和多构造函数注入1)单构造函数使用自动注入单构造函数自动注入,这里我们使用一个简单的例子具体类MyObject依赖于具体类MyDependentC......
  • 学习进度笔记2
    frompysparkimportSparkConf,SparkContext#创建sparkconf对象conf=SparkConf().setMaster("local[*]").setAppName("test_app")#基于sparkconf对象创建sparkContext对象sc=SparkContext(conf=conf)##########基本结构#map计算#rdd=sc.parallelize([1,2,3,4,5])#......
  • AtCoder Beginner Contest 335 G Discrete Logarithm Problems
    洛谷传送门AtCoder传送门考虑若我们对于每个\(a_i\)求出来了使得\(g^{b_i}\equiva_i\pmodP\)的\(b_i\)(其中\(g\)为\(P\)的原根),那么\(a_i^k\equiva_j\pmodP\)等价于\(kb_i\equivb_j\pmod{P-1}\),有解的充要条件是\(\gcd(b_i,P-1)\midb_j\)。显然......
  • kepware关于U-CON的使用笔记 (串口_非主动问答式)
            8,新建两个变量t1,t2 (均为6字节的字符串)用来存储接收到的关键数据;   9,内容接收及解析逻辑     11 测试成功图片展示   ......