设计者:canopas
我们在应用程序中使用两种类型的令牌进行身份验证:
- 访问令牌(Access Token):用于在客户端上访问受保护的资源的字符串。每个令牌都具有特定的范围、生命周期和其他有效属性。我们可以根据需要设置生命周期(1小时或1天)。
- 刷新令牌(Refresh Token):用于获取访问令牌的字符串。在授权时,它将在颁发访问令牌时创建。其生命周期应大于访问令牌(1个月或1年)。
请记住,这些令牌不仅适用于JWT。它们提供了一种授权资源的方法。我们可以使用JWT或任何其他格式创建这些令牌。
为什么需要刷新令牌?
在第一个访问令牌失效后,刷新令牌用于对用户进行身份验证。在不损害安全性的情况下,这一过程在后台进行,提高了用户体验。刷新令牌不会授予用户任何超出最初允许的访问权限。
如果你想了解更多信息,可以参考这个线程。
赞助
安全大多是人类行为的问题,行为可以通过习惯来定义。通过justly来改善你的习惯吧!
Justly - Systems, Habits, Goals
目录
1. Introduction
2. Terminologies by a real-life example
3. Generate tokens
4. Validate tokens
5. Refresh access token using the refresh token
6. Conclusion
简介
JWT(JSON Web Token)是用于在任何应用程序中进行身份验证的令牌格式。jwt.io提供了一个Web界面,用于检查JWT令牌,以及一个用户指南,以了解令牌结构。
JWT令牌包含三个部分:
- 标头(Header) -(包含令牌类型和令牌的签名算法)
- 签名(Signature) -(用于验证消息沿途未被更改的base64密钥)
- 有效载荷(Payload) -(包含实际数据,即用于创建令牌的声明(claims))
我们将使用go-jwt包来实现Golang JWT身份验证。
以真实生活例子为例的术语
我们可以将JWT术语简单地与住宅锁定系统联系起来,如下所示:
- 刷新令牌:房门
- 访问令牌:门锁
- 标头:用于制作锁和钥匙的物品和材料
- 密钥(签名):锁的钥匙
- 范围:家庭和所有者
- 声明:锁制造商提供的有效期(锁的寿命)
- 生成令牌:所有者购买了一把锁
- 验证令牌:只有使用匹配的钥匙才能打开锁。
- 更新访问令牌:当锁被损坏时,所有者更换锁。门及其功能将被视为购买新锁。
生成令牌
要生成令牌,我们需要为两种令牌(或可以同时使用相同的密钥)分别定义一个单独的密钥。你可以使用HSA 256或RS256加密生成它。它至少应为32个字符长,但越长越好。
接下来,定义声明。go-jwt有其JWT的标准声明。但是我们可以创建自定义声明,如下所示:
type JWTData struct {
jwt.StandardClaims
CustomClaims map[string]string `json:"custom_claims"`
}
让我们使用这些声明生成令牌:
import "github.com/golang-jwt/jwt/v4"
func GenerateJWTAccessToken(userId string, email string) (string, error) {
// prepare claims for token
claims := JWTData{
StandardClaims: jwt.StandardClaims{
// set token lifetime in timestamp
ExpiresAt: time.Now().Add(time.Duration(tokenLifeTime)).Unix(),
},
// add custom claims like user_id or email,
// it can vary according to requirements
CustomClaims: map[string]string{
"user_id": userId,
"email": email,
},
}
// generate a string using claims and HS256 algorithm
tokenString := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// sign the generated key using secretKey
token, err := tokenString.SignedString(secretKey)
return token, err
}
token
将是包含标头、签名和有效载荷的JWT令牌。你可以在jwt.io上检查令牌。
相同的方法可以用于生成具有更长寿命的刷新令牌。
验证令牌
要验证授权的资源,我们需要首先验证令牌。
下面的代码将用于验证令牌:
claims := &JWTData{}
_, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
return secretKey, nil
})
- ParseWithClaims的最后一个参数是KeyFunc,它将从给定的令牌返回secretKey,
func(token *jwt.Token) (interface{}, error) {
return secretKey, nil
}
- parseWithClaims将数据分配给我们之前在JWTData中定义的claims。
我们可以通过声明ExpiresAt
来验证令牌。它还具有自定义声明user_id
和email
。你可以检查它们是否存在于你的数据库中,并使用给定的令牌授权用户。
以下是如果令牌无效的错误代码:
if err != nil {
// check whether error is validation error or not
if validationErr, ok := err.(*jwt.ValidationError); ok {
// Token is malformed
if validationErr.Errors & jwt.ValidationErrorMalformed != 0 {
return "Token is malformed"
} else if validationErr.Errors & (jwt.ValidationErrorExpired | jwt.ValidationErrorNotValidYet) != 0 {
// Token is either expired or not active yet
return "Token is either expired or not active yet"
} else {
return "Other err"
}
} else {
return "Other err"
}
}
使用刷新令牌更新访问令牌
正如我们所知道的那样,刷新令牌将用于更新访问令牌。
以下是使用刷新令牌获取访问令牌的流程:
- 使用上面的方法验证刷新令牌。
注意:你可以将刷新令牌传递给标题或表单数据中,具体取决于哪种更可取。
_, err := jwt.ParseWithClaims(refreshToken, claims, func(token *jwt.Token) (interface{}, error) {
return secretKey, nil
})
- 使用自定义声明检查用户是否存在于数据库中。
- 如果刷新令牌有效且用户存在,则使用相同范围和定义的寿命生成新的访问令牌和刷新令牌。
考虑一种情况,其中刷新令牌的寿命为1个月,而用户已经2个月没有使用该应用程序。如果在给定时间内未使用刷新令牌,则刷新令牌将过期,此后需要重新请求新的刷新令牌和访问令牌。否则,它将刷新访问令牌。
结论
JWT身份验证使用签名的令牌在两个参与方之间提供了安全性。
它是用于身份验证的广泛使用格式。我已经解释了它在Golang中的使用,但是流程对于其他语言(如JavaScript或PHP)也是相同的。
译自:https://blog.canopas.com/jwt-in-golang-how-to-implement-token-based-authentication-298c89a26ffd
评论(0)