golang之JWT实现的示例代码

作者:guyan0319 时间:2024-01-30 03:35:30 

什么是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
 }
}

运行结果:

token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjExLCJleHAiOjE1OTA5MTk1NDAsImlzcyI6ImplcnJ5In0.FppmbbHRrS4wd5wen73vYPOvtzycOrn2JZlK6JRjEGk
userinfo: &{11 { 1590919540 0 jerry 0 }}

以上用到了CustomClaims,也可以用简单的方法

示例


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
 }
}

运行结果类似

token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTA5MzUzMDUsImlkIjoxMSwibmFtZSI6ImplcnJ5In0.fapE0IiOEe_TqoMCThbNTHUvgWiHPEk0rm-9uPIcvPU
userinfo: map[exp:1.590935305e+09 id:11 name:jerry]

小结:

  • 服务端生成的jwt返回客户端可以存到cookie也可以存到localStorage中(相比cookie容量大),存在cookie中需加上 HttpOnly 的标记,可以防止 XSS) 攻击。

  • 尽量用https带证书网址访问。

  • session和jwt没有绝对好与不好,各有其擅长的应用环境,请根据实际情况选择。

参考资料

https://github.com/guyan0319/golang_development_notes

https://godoc.org/github.com/dgrijalva/jwt-go

https://blog.csdn.net/weixin_43613053/article/details/84642140

https://www.cnblogs.com/flipped/p/12973557.html

来源:https://segmentfault.com/a/1190000022792874

标签:golang,JWT
0
投稿

猜你喜欢

  • Go语言中new()和 make()的区别详解

    2024-05-22 17:45:33
  • python多线程抓取天涯帖子内容示例

    2021-03-18 11:56:42
  • Python OpenCV获取视频的方法

    2023-02-13 04:03:23
  • 无闪烁更新网页内容JS实现

    2024-05-09 10:37:18
  • Pyramid添加Middleware的方法实例

    2022-11-25 18:50:34
  • 基于Python实现简单的定时器详解

    2021-04-12 01:21:28
  • Python如何读写JSON格式数据

    2022-05-20 17:42:05
  • ASP中生成文件的两种方式

    2007-08-30 09:49:00
  • SQL指令植入式攻击的危害及其防范措施

    2008-12-19 14:07:00
  • 一篇文章告诉你如何用python进行自动化测试,调用c程序

    2021-10-08 09:14:49
  • 在IntelliJ IDEA中使用Java连接MySQL数据库的方法详解

    2024-01-27 06:56:35
  • 编写脚本令Xtrabackup对MySQL数据进行备份的教程

    2024-01-16 06:38:25
  • Python完美还原超级玛丽游戏附代码与视频

    2022-03-20 16:57:41
  • Python网络编程基于多线程实现多用户全双工聊天功能示例

    2021-01-30 06:29:37
  • python实现从字典中删除元素的方法

    2023-11-10 17:26:33
  • PHP程序员玩转Linux系列 备份还原MySQL

    2023-11-15 07:39:28
  • 详解python做UI界面的方法

    2023-05-24 08:07:41
  • 详解Django中六个常用的自定义装饰器

    2021-02-10 03:03:09
  • Python自动化之数据驱动让你的脚本简洁10倍【推荐】

    2022-08-20 12:48:57
  • Python接口测试文件上传实例解析

    2023-04-19 15:59:28
  • asp之家 网络编程 m.aspxhome.com