在Go语言中,JWT(JSON Web Token)鉴权可以使用第三方库来实现,比如jwt-go。
库的介绍和使用可见文档:jwt package - github.com/golang-jwt/jwt/v5 - Go Packages
创建JWT令牌
在服务器中,可以使用以下代码创建JWT令牌
package middleware import ( "fmt" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "mybili/serializer" "mybili/utils" "net/http" "os" "time" ) type MyClaims struct { Username string `json:"user_name"` //Password string `json:"password"` jwt.RegisteredClaims } // 生成token func SetToken(username string) (string, int) { SetClaims := MyClaims{ Username: username, //Password: password, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), //有效时间 IssuedAt: jwt.NewNumericDate(time.Now()), //签发时间 NotBefore: jwt.NewNumericDate(time.Now()), //生效时间 Issuer: os.Getenv("JWT_ISSUER"), //签发人 Subject: "somebody", //主题 ID: "1", //JWT ID用于标识该JWT Audience: []string{"somebody_else"}, //用户 }, } //使用指定的加密方式和声明类型创建新令牌 tokenStruct := jwt.NewWithClaims(jwt.SigningMethodHS256, SetClaims) //获得完整的、签名的令牌 token, err := tokenStruct.SignedString([]byte(os.Getenv("JWT_KEY"))) if err != nil { utils.Logger.Errorf("err:%v", err.Error()) return "", utils.TOKEN_CREATE_FAILED } return token, utils.SUCCESS }
jwt.RegisteredClaims是对标准注册声明的封装,可以单独使用它,也可以把它嵌入到自定义类型中,以提供标准验证功能。
jwt.NewWithClaims使用指定的签名方法和声明创建一个新令牌。
创建了一个名为MyClaims 的结构体,用于定义JWT负载。除了标准验证选项外,我们也可以自定义Payload有效载荷字段,例如将用户名Username放到payload中。
JWT验证
验证JWT可以使用如下代码实现:
// 验证token func CheckToken(token string) (*MyClaims, int) { //解析、验证并返回token。 tokenObj, err := jwt.ParseWithClaims(token, &MyClaims{}, func(token *jwt.Token) (interface{}, error) { return []byte(os.Getenv("JWT_KEY")), nil }) if err != nil { utils.Logger.Errorf("err:%v", err.Error()) return nil, utils.ERROR } if claims, ok := tokenObj.Claims.(*MyClaims); ok && tokenObj.Valid { fmt.Printf("%v %v\n", claims.Username, claims.RegisteredClaims) return claims, utils.SUCCESS } else { return nil, utils.ERROR } }
jwt.ParseWithClaims是NewParser().ParseWithClaims()的快捷方式,第一个参数是token的string值,第二个参数是之后需要把解析的数据放入的地方,第三个参数将被Parse方法用作回调函数,以提供用于验证的键。函数接收已解析但未验证的令牌。
解析结果输出结果如下:
Username:mjiarong RegisteredClaims:{mybili somebody [somebody_else] 2023-10-02 20:25:30 +0800 CST 2023-10-01 20:25:30 +0800 CST 2023-10-01 20:25:30 +0800 CST 1}
JWT中间件
在中间件中服务端会获取用户token,基于jwt校验是否合法,合法放行,否则拒绝。
// jwt中间件 func JwtToken() gin.HandlerFunc { return func(c *gin.Context) { tokenHeader := c.Request.Header.Get("Authorization") code := utils.SUCCESS if tokenHeader == "" { code = utils.TOKEN_NOT_EXIST c.JSON(http.StatusOK, serializer.CheckToken( code, utils.GetErrMsg(code))) c.Abort() return } key, tCode := CheckToken(tokenHeader) if tCode == utils.ERROR { code = utils.TOKEN_WRONG c.JSON(http.StatusOK, serializer.CheckToken( code, utils.GetErrMsg(code))) c.Abort() return } //判断token是否过期 if time.Now().Unix() > key.ExpiresAt.Unix() { code = utils.TOKEN_RUNTIME c.JSON(http.StatusOK, serializer.CheckToken( code, utils.GetErrMsg(code))) c.Abort() return } c.Set("username", key.Username) c.Next() } }
前端JWT登录鉴权的实现
一般情况下,客户端在登录后得到了一个JWT方式的token。那这个token放哪里呢?最好把JWT放在HTTP请求的Header Authorization,格式是Authorization: Bearer jwtStr。那是否意味着前端的每个请求都附带后端返回的token吗?显然是否定的,只需要把需要鉴权登陆的请求头中带上token就行了。对此可以使用axios的请求拦截器来对请求进行过滤,以下是某个项目中的一个请求拦截器的实现:
import axios from "axios"; let http = axios.create({ baseURL: process.env.VUE_APP_BASE_API, //配置默认的地址 withCredentials: true, //将会默认携带认证给后端 timeout: 1000 * 10, //请求超时设置,如果超过了10秒,那么就会进入reject }); http.interceptors.request.use( //axios的请求拦截器,它可以拦截所有的请求,为所有的请求添加逻辑 //拦截了请求后,如果不放行,那么所有的请求会一直被拦截,因此需要return不需要拦截的请求。 function(config) { let postWhiteList = [ "/user/login", "/user/register", ]; //将不需要拦截的请求拿出来 let getWhiteList = [ "/rank/daily", "/comment", "/upload/token", "/upload/credentials", "/comment", ]; if (config.method === 'post' && postWhiteList.includes(config.url)) { //如果当前的请求地址中,包含在不需要拦截请求地址中,那么就放行 return config; } else if (config.method === 'get' && (getWhiteList.includes(config.url) || config.url.includes("/video"))) { return config; } else { //如果是需要被拦截的请求 let token = window.sessionStorage.getItem("token") || ""; //将登录成功后,在后端中返回来的token从本地存储取出来 config.headers["Authorization"] = token; //给需要拦截的请求中请求头添加token。config.headers["authorization"]是一个固定的写法 return config; //添加后,放行 } } );
对此,前后端中对JWT的实现就基本完成了。
总结
JWT作为一种安全、简单的认证方式,被广泛应用于Web开发中。JWT不仅可以用于用户认证,还可以用于数据传输、API认证等场景。在实际应用中,应该注意JWT的有效期,以及签名密钥的安全保护。任何技术都不是完美的,JWT 也有很多缺陷,具体是选择 JWT 还是 Session 方案还是要看项目的具体需求。