go使用jwt生成token
常见的认证方式
一般用户认证主流的方式大致上分为基于 session 和基于 token 这两种。
基于 sesion 的认证方式
- 用户向服务器发送用户名和密码。
- 服务器验证通过后,在当前对话(sesion)里面保存相关数据,比如用户角色、登录时间等等。
- 服务器向用户返回一个 session_id,写入用户的 Cookie 或其他存储。
- 用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
- 服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。
- 用户退出登录,服务器将对应 session_id 的数据清除。
这种方式服务端需要将 session_id 及相关的数据保存起来,在接收到用户请求时进行校验,比如可以存储到 Redis 中。
基于 token 的认证方式
- 用户向服务器发送用户名和密码。
- 服务器将相关数据,比如用户 ID,认证有效期等信息签名后生成 token 返回给客户端。
- 客户端将 token 写入本地存储。
- 用户随后的每一次请求,都将 token 附加到 header 中。
- 服务端获取到用户请求的 header,拿到用户数据并且做签名校验,如果校验成功则说明数据没有被篡改,是有效的,确认 token 在有效期内,用户数据就是有效的。
jwt 是基于 token 的认证方式的一种。这里我们使用 jwt-go 在 Golang 项目中使用 jwt。以下代码均为示例代码,部分内容有所删减,仅供参考。
生成 Token
服务器端需要提供一个登录接口用于用户登录。客户端提供用户名和密码,服务器端进行校验,如果校验通过,则生成 Token 返回给客户端。
import ( jwt "github.com/dgrijalva/jwt-go" ) func GenerateToken(uid string, role int, expireDuration time.Duration) (string, error) { expire := time.Now().Add(expireDuration) // 将 uid,用户角色, 过期时间作为数据写入 token 中 token := jwt.NewWithClaims(jwt.SigningMethodHS256, util.LoginClaims{ Uid: uid, Role: role, StandardClaims: jwt.StandardClaims{ ExpiresAt: expire.Unix(), }, }) // SecretKey 用于对用户数据进行签名,不能暴露 return token.SignedString([]byte(util.SecretKey)) } func (ctl *LoginController) Login(rw http.ResponseWriter, req *http.Request) { var u loginRequest json.NewDecoder(req.Body).Decode(&u) // 将用户传入的用户名和密码和数据库中的进行比对 user, err := ctl.db.GetUserByName(req.Context(), u.User) if err != nil { log.Warn("get user from db by name error: %v", err) httputil.Error(rw, errors.ErrInternal) return } if common.EncodePassowrd(u.Password, u.User) != user.Password { log.Warn("name [%s] password incorrent", u.User) httputil.Error(rw, errors.ErrLoginFailed) return } // 生成返回给用户的 token token, err := GenerateToken(user.UID, user.Role, 3*24*time.Hour) if err != nil { log.Warn("name [%s] generateToken error: %v", u.User, err) httputil.Error(rw, errors.ErrInternal) return } res := struct { Token string `json:"token"` }{ Token: token, } httputil.Reply(rw, &res) }
校验 Token
这里要求客户端每次将通过登录接口获取到的 token 设置在发送请求的 Authorization
header 中。
func (a *AuthFilter) Filter(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { tokenStr := req.Header.Get("Authorization") token, err := jwt.ParseWithClaims(tokenStr, &util.LoginClaims{}, func(token *jwt.Token) (interface{}, error) { return []byte(util.SecretKey), nil } if err != nil { httputil.Error(rw, errors.ErrUnauthorized) return } if claims, ok := token.Claims.(*util.LoginClaims); ok && token.Valid { log.Infof("uid: %s, role: %v", claims.Uid, claims.Role) } else { httputil.Error(rw, errors.ErrUnauthorized) return } next.ServeHTTP(rw, req) } }
注意点
- 由于 jwt 返回的 Token 中的数据仅做了 Base64 处理,没有加密,所以不应放入重要的信息。
- jwt Token 由于是无状态的,任何获取到此 Token 的人都可以访问,所以为了减少盗用,可以将 Token 有效期设置短一些。对一些重要的操作,尽量再次进行认证。
- 网站尽量使用 HTTPS,可以减少 Token 的泄漏。
通往牛逼的路上,在意的只有远方!