为了处理这个特殊的场景,我们将这个customkey值添加到RefreshToken的有效负载中。在验证RefreshToken期间,我们将再次使用用户ID和HashToken创建这个customKey,并将其与RefreshToken有效负载中的customKey进行比较。所以现在如果这个HashToken改变了,那么RefreshToken就会失效。每次用户更改密码时,我们都会用一个新的随机字符串来更改这个HashToken。因此,我们为用户提供了一种通过修改密码来使RequestToken无效的方法。希望我对引入HashToken的原因进行冗长的解释是有意义的,我们将继续讨论下一部分,即创建JWT有效负载。
RefreshTokenCustomClaims构成了我们的有效负载。它由诸如用户id、customkey、tokenttype、颁发者(属于JWT标准声明)等信息组成。注意,我们没有向有效负载添加任何过期时间戳信息。现在我们有了有效负载,头文件将由JWT库生成,接下来我们需要的是签名。我们这里使用的签名方法是RS256。
简而言之,RS256方法遵循公钥密码学,其中我们有两个密钥,公钥和私钥。私钥将用于对令牌进行签名,公钥将用于验证令牌,这与HSA方法不同,在HSA方法中,一个密钥将用于签名和验证。用私钥加密的数据只能用相应的公钥解密,反之亦然。
(注意:在解释JWT时,我们使用HSA方法进行签名。不同的是HSA是基于哈希的方法,RSA是基于加密的方法。为了进一步了解每种方法的优缺点,请阅读下面的参考资料。
linux系统中生成rsa密钥的命令:
- 生成rsa私钥
openssl genrsa -out auth-private.pem 2048
- 导出rsa公钥
openssl rsa -in auth-private.pem -outform PEM -pubout -out auth-public.pem
因此,我们从服务器文件系统读取privatekey。现在我们有了创建token所需的所有组件——有效负载、头(指定的签名方法)和签名秘密。我们只需要创建我们的令牌并使用秘密对其进行签名。是的! !我们有了刷新token。
接下来,我们使用类似的方法来创建AccessToken,只需很少的修改,比如使用不同的私钥(不包括customKey的需要)。这里需要注意的重要一点是,我们正在为Accesstoken添加一个过期时间戳。
// GenerateAccessToken generates a new access token for the given user
func (auth *AuthService) GenerateAccessToken(user *data.User) (string, error) {
userID := user.ID
tokenType := "access"
claims := AccessTokenCustomClaims{
userID,
tokenType,
jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Minute * time.Duration(auth.configs.JwtExpiration)).Unix(),
Issuer: "bookite.auth.service",
},
}
signBytes, err := ioutil.ReadFile(auth.configs.AccessTokenPrivateKeyPath)
if err != nil {
auth.logger.Error("unable to read private key", "error", err)
return "", errors.New("could not generate access token. please try again later")
}
signKey, err := jwt.ParseRSAPrivateKeyFromPEM(signBytes)
if err != nil {
auth.logger.Error("unable to parse private key", "error", err)
return "", errors.New("could not generate access token. please try again later")
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
return token.SignedString(signKey)
}
至此,我们已经介绍了与注册和登录处理函数有关的所有内容。
现在让我们看看如何处理刷新令牌请求。如前所述,我们使用refresh-token在AccessToken过期时获得一个新的AccessToken。因此,我们需要在这里验证给定的RefreshToken并提供一个新的AccessToken。验证部分在在refresh-token子外部注册的MiddlewareValidateRefreshToken中间件函数中执行。
// MiddlewareValidateRefreshToken validates whether the request contains a bearer token
// it also decodes and authenticates the given token
func (uh *UserHandler) MiddlewareValidateRefreshToken(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
uh.logger.Debug("validating refresh token")
uh.logger.Debug("auth header", r.Header.Get("Authorization"))
token, err := extractToken(r)
if err != nil {
uh.logger.Error("token not provided or malformed")
w.WriteHeader(http.StatusBadRequest)
data.ToJSON(&GenericError{Error: err.Error()}, w)
return
}
uh.logger.Debug("token present in header", token)
userID, customKey, err := uh.authService.ValidateRefreshToken(token)
if err != nil {
uh.logger.Error("token validation failed", "error", err)
w.WriteHeader(http.StatusBadRequest)
data.ToJSON(&GenericError{Error: err.Error()}, w)
return
}
uh.logger.Debug("refresh token validated")
user, err := uh.repo.GetUserByID(context.Background(), userID)
if err != nil {
uh.logger.Error("invalid token: wrong userID while parsing", err)
w.WriteHeader(http.StatusBadRequest)
data.ToJSON(&GenericError{Error: "invalid token: authentication failed"}, w)
return
}
actualCustomKey := uh.authService.GenerateCustomKey(user.ID, user.TokenHash)
if customKey != actualCustomKey {
uh.logger.Debug("wrong token: authetincation failed")
w.WriteHeader(http.StatusBadRequest)
data.ToJSON(&GenericError{Error: "invalid token: authentication failed"}, w)
return
}
ctx := context.WithValue(r.Context(), UserKey{}, *user)
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
func extractToken(r *http.Request) (string, error) {
authHeader := r.Header.Get("Authorization")
authHeaderContent := strings.Split(authHeader, " ")
if len(authHeaderContent) != 2 {
return "", errors.New("Token not provided or malformed")
}
return authHeaderContent[1], nil
}
本文来自:简书
感谢作者:汪明军_3145
查看原文:Go使用JWT构建用户认证系统
相关阅读 >>
[系列] - Go-gin-api 规划目录和参数验证(二)
更多相关阅读请进入《Go》频道 >>

Go语言101
一个与时俱进的Go编程知识库。