为什么使用JWT
在过去的web项目中,通常使用的是Cookie-Session的认证方式。大致的流程如下:
- 用户使用用户名个密码登录,发送信息到服务器。
- 服务器拿到用户的信息,后会生成一份保存用户信息的session数据和与之对应的cookie id,然后保存session在在服务器,把对应的session id返回给用户并保存在浏览器。
- 后续来自该用户的每次请求都会携带相应的cookie。
- 服务端通过带来的cookie找到之前保存的与之对应的用户信息。
那么这种方式有什么弊端呢?
- 不支持跨域
- 通过数据库查询响应的用户信息耗时
- CSRF攻击(跨站伪造请求)
什么是JWT
JWT就是一种基于Token的轻量级认证模式,服务端认证通过后,会生成一个JSON对象,经过签名后得到一个Token(令牌)再发回给用户,用户后续请求只需要带上这个Token,服务端解密之后就能获取该用户的相关信息了。
在gin框架中使用JWT
- 需要提前安装的包:
github.com/dgrijalva/jwt-go
github.com/gin-gonic/gin
- 生成token
type MyClaims struct {
Username string `json:"username"`
jwt.StandardClaims
}
// 定义过期时间
const TokenExpireDuration = time.Hour * 2
//定义secret
var MySecret = []byte("这是一段用于生成token的密钥")
//生成jwt
func GenToken(username string) (string, error) {
c := MyClaims{
username,
jwt.StandardClaims{
ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(),
Issuer: "my-project",
},
}
//使用指定的签名方法创建签名对象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
//使用指定的secret签名并获得完成的编码后的字符串token
return token.SignedString(MySecret)
}
- 解析JWT
//解析JWT
func ParseToken(tokenString string) (*MyClaims, error) {
//解析token
token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (i interface{}, err error) {
return MySecret, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*MyClaims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("invalid token")
}
- 编写基于JWT认证中间件
//基于JWT认证中间件
func JWTAuthMiddleware() func(c *gin.Context) {
return func(c *gin.Context) {
authHeader := c.Request.Header.Get("Authorization")
if authHeader == "" {
c.JSON(http.StatusOK, gin.H{
"code": 2003,
"msg": "请求头中的auth为空",
})
c.Abort()
return
}
parts := strings.SplitN(authHeader, " ", 2)
if !(len(parts) == 2 && parts[0] == "Bearer") {
c.JSON(http.StatusOK, gin.H{
"code": 2004,
"msg": "请求头中的auth格式错误",
})
//阻止调用后续的函数
c.Abort()
return
}
mc, err := ParseToken(parts[1])
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 2005,
"msg": "无效的token",
})
c.Abort()
return
}
//将当前请求的username信息保存到请求的上下文c上
c.Set("username", mc.Username)
//后续的处理函数可以通过c.Get("username")来获取请求的用户信息
c.Next()
}
}
5 . 注册一条理由/auth,获取token
type UserInfo struct {
Username string `json:"username"`
Password string `json:"password"`
}
func authHandler(c *gin.Context) {
var user UserInfo
err := c.ShouldBind(&user)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 2001,
"msg": "无效的参数",
})
return
}
if user.Username == "cyl" && user.Password == "123456" {
//生成token
tokenString, _ := GenToken(user.Username)
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "success",
"data": gin.H{"token": tokenString},
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 2002,
"msg": "鉴权失败",
})
return
}
- 编写main函数进行测试
package main
import (
"errors"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"net/http"
"strings"
"time"
)
func main() {
r := gin.Default()
r.POST("/auth", authHandler)
r.GET("/home", JWTAuthMiddleware(), homeHandler)
r.Run(":9000")
}
- 使用postman进行测试 首先访问一下http://localhost:9000/auth 返回结果如下:
接着我们继续注册一个需要token的路由/home
func homeHandler(c *gin.Context) {
username := c.MustGet("username").(string)
c.JSON(http.StatusOK, gin.H{
"code": 2000,
"msg": "success",
"data": gin.H{"username": username},
})
}
访问http://localhost:9000/home 并且带上上次获取的token(记得加上Bearer)
结果:
go中使用JWT就到此结束啦,附上原文连接:www.liwenzhou.com/posts/Go/jw…