• golang之jwt的token登录 X


    什么是 JSON Web Token?

    JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以 JSON 方式安全地传输信息。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用秘密(使用** HMAC 算法)或使用 RSA  ECDSA 的公钥/私钥对对 JWT 进行签名**。

    直白的讲 jwt 就是一种用户认证(区别于 session、cookie)的解决方案。

    出现的背景

    众所周知,在 jwt 出现之前,我们已经有 session、cookie 来解决用户登录等认证问题,为什么还要 jwt 呢?

    这里我们先了解一下 session,cookie。

    session

    熟悉 session 运行机制的同学都知道,用户的 session 数据以 file 或缓存(redis、memcached)等方式存储在服务器端,客户端浏览器 cookie 中只保存 sessionid。服务器端 session 属于集中存储,数量不大的情况下,没什么问题,当用户数据逐渐增多到一程度,就会给服务端管理和维护带来大的负担。

    session 有两个弊端:

    1、无法实现跨域。

    2、由于 session 数据属于集中管理里,量大的时候服务器性能是个问题。

    优点:

    1、session 存在服务端,数据相对比较安全。

    2、session 集中管理也有好处,就是用户登录、注销服务端可控。

    cookie

    cookie 也是一种解决网站用户认证的实现方式,用户登录时,服务器会发送包含登录凭据的 Cookie 到用户浏览器客户端,浏览器会将 Cookie 的 key/value 保存用户本地(内存或硬盘),用户再访问网站,浏览器会发送 cookie 信息到服务器端,服务器端接收 cookie 并解析来维护用户的登录状态。

    cookie 避免 session 集中管理的问题,但也存在弊端:

    1、跨域问题。

    2、数据存储在浏览器端,数据容易被窃取及被 csrf 攻击,安全性差。

    优点:

    1、相对于 session 简单,不用服务端维护用户认证信息。

    2、数据持久性。

    jwt

    jwt 通过 json 传输,php、java、golang 等很多语言支持,通用性比较好,不存在跨域问题。传输数据通过数据签名相对比较安全。客户端与服务端通过 jwt 交互,服务端通过解密 token 信息,来实现用户认证。不需要服务端集中维护 token 信息,便于扩展。当然 jwt 也有其缺点。

    缺点:

    1、用户无法主动登出,只要 token 在有效期内就有效。这里可以考虑 redis 设置同 token 有效期一直的黑名单解决此问题。

    2、token 过了有效期,无法续签问题。可以考虑通过判断旧的 token 什么时候到期,过期的时候刷新 token 续签接口产生新 token 代替旧 token。

    jwt 设置有效期

    可以设置有效期,加入有效期是为了增加安全性,即 token 被黑客截获,也只能攻击较短时间。设置有效期就会面临 token 续签问题,解决方案如下

    通常服务端设置两个 token

    • Access Token:添加到 HTTP 请求的 header 中,进行用户认证,请求接口资源。
    • refresh token:用于当 Access Token 过期后,客户端传递 refresh token 刷新 Access Token 续期接口,获取新的 Access Token 和 refresh token。其有效期比 Access Token 有效期长。

    jwt 构成:

    • Header:TOKEN 的类型,就是 JWT,签名的算法,如 HMAC SHA256、HS384
    • Payload:载荷又称为 Claim,携带的信息,比如用户名、过期时间等,一般叫做 Claim
    • Signature:签名,是由 header、payload 和你自己维护的一个 secret 经过加密得来的

    jwt 使用

    这里推荐个使用比较多的开源项目、[github.com/dgrijalva/jwt-go](),更多文档。

    示例:

    package main
    
    import (
        "fmt"
        "github.com/dgrijalva/jwt-go"
        "time"
    )
    const (
        SECRETKEY = "243223ffslsfsldfl412fdsfsdf"//私钥
    )
    //自定义 Claims
    type CustomClaims struct {
        UserId int64
        jwt.StandardClaims
    }
    func main() {
        //生成 token
        maxAge:=60*60*24
        customClaims :=&CustomClaims{
            UserId: 11,//用户 id
            StandardClaims: jwt.StandardClaims{
                ExpiresAt: time.Now().Add(time.Duration(maxAge)*time.Second).Unix(), // 过期时间,必须设置
                Issuer:"jerry",   // 非必须,也可以填充用户名,
            },
        }
        //采用 HMAC SHA256 加密算法
        token:=jwt.NewWithClaims(jwt.SigningMethodHS256, customClaims)
        tokenString,err:= token.SignedString([]byte(SECRETKEY))
        if err!=nil {
            fmt.Println(err)
        }
        fmt.Printf("token: %v\n", tokenString)
    
        //解析 token
        ret,err :=ParseToken(tokenString)
        if err!=nil {
            fmt.Println(err)
        }
        fmt.Printf("userinfo: %v\n", ret)
    }
    
    //解析 token
    func ParseToken(tokenString string)(*CustomClaims,error)  {
        token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
            if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
                return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
            }
            return []byte(SECRETKEY), nil
        })
        if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
            return claims,nil
        } else {
            return nil,err
        }
    }

    Claims:

    Audience string `json:"aud,omitempty"`  
    ExpiresAt int64 `json:"exp,omitempty"`  
    Id string `json:"jti,omitempty"` 
    IssuedAt int64 `json:"iat,omitempty"`  
    Issuer string `json:"iss,omitempty"`  
    NotBefore int64 `json:"nbf,omitempty"`  
    Subject string `json:"sub,omitempty"`
    
    aud: 接收 jwt 的一方
    exp: jwt 的过期时间,这个过期时间必须要大于签发时间
    jti: jwt 的唯一身份标识,主要用来作为一次性 token, 从而回避重放攻击。
    iat: jwt 的签发时间
    iss: jwt 签发者
    nbf: 定义在什么时间之前,该 jwt 都是不可用的。就是这条 token 信息生效时间。这个值可以不设置,但是设定后,一定要大于当前 Unix UTC, 否则 token 将会延迟生效。
    sub: jwt 所面向的用户

     

    示例:

    package main
    
    import (
        "fmt"
        "github.com/dgrijalva/jwt-go"
        "time"
    )
    const (
        SECRETKEY = "243223ffslsfsldfl412fdsfsdf"//私钥
    )
    //自定义 Claims
    type CustomClaims struct {
        UserId int64
        jwt.StandardClaims
    }
    func main() {
        //生成 token
        maxAge:=60*60*24
        // Create the Claims
        //claims := &jwt.StandardClaims{
        //    //    ExpiresAt: time.Now().Add(time.Duration(maxAge)*time.Second).Unix(), // 过期时间,必须设置,
        //    //    Issuer:    "jerry",// 非必须,也可以填充用户名,
        //    //}
    
        //或者用下面自定义 claim
        claims := jwt.MapClaims{
            "id":       11,
            "name":       "jerry",
            "exp": time.Now().Add(time.Duration(maxAge)*time.Second).Unix(), // 过期时间,必须设置,
        }
    
        token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
        tokenString, err := token.SignedString([]byte(SECRETKEY))
        if err!=nil {
            fmt.Println(err)
        }
        fmt.Printf("token: %v\n", tokenString)
    
        //解析 token
        ret,err :=ParseToken(tokenString)
        if err!=nil {
            fmt.Println(err)
        }
        fmt.Printf("userinfo: %v\n", ret)
    }
    
    //解析 token
    func ParseToken(tokenString string)(jwt.MapClaims,error)  {
        token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
            // Don't forget to validate the alg is what you expect:
            if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
                return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
            }
    
            // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
            return []byte(SECRETKEY), nil
        })
        if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
            return claims,nil
        } else {
            return nil,err
        }
    }

    小结:

    • 服务端生成的 jwt 返回客户端可以存到 cookie 也可以存到 localStorage 中(相比 cookie 容量大),存在 cookie 中需加上 HttpOnly 的标记,可以防止 XSS) 攻击。
    • 尽量用 https 带证书网址访问。
    • session 和 jwt 没有绝对好与不好,各有其擅长的应用环境,请根据实际情况选择。

     

     

     

     

     

     

     

  • 相关阅读:
    maven 的 oracle的Missing artifact com.oracle:******:jar:11.2.0.2.0
    [CF 191C]Fools and Roads[LCA Tarjan算法][LCA 与 RMQ问题的转化][LCA ST算法]
    公司估值(贴现现金流量法DCF)
    Shell编程学习---第五篇:Shell的输入和输出
    S3C2410 实验三——块拷贝、字拷贝(寄存器的理解)
    模板方法模式实现组合查询
    关于方程x^2+y^2=p (p为素数)的解问题
    IOS登陆+注册+抽奖+排行榜
    用PersonalRank实现基于图的推荐算法
    Redis3.0--集群安装部署
  • 原文地址:https://www.cnblogs.com/xingxia/p/golang_jwt.html
Copyright © 2020-2023  润新知