2021SC@SDUSC
目录
一.引言
我们使用Jwt来保卫我们的系统。因为在SDU信息门户项目中,我们有三种角色:管理员、教师、学生。如果其他用户知道了项目的管理员接口,可能导致软件的失败。如果仅仅是通过调用的接口路径的不同来区分这些角色的话,还是无法完全避免软件缺陷。
二.代码分析
1.基本概念
JWT 由三部分构成,第一部分是 header,第二部分为 payload,第三部分是 signature。
在 header 中存放着令牌类型和令牌使用的加密算法。
在 payload 存放有效信息,这些有效信息包含三个部分:标准中注册的声明、共有的声明和私有的声明。jwt.StandardClaims定义的就是标准中注册的声明。
在signature存放签证信息,用于校验消息在整个过程中有没有被篡改。
jwt.SandardClaims结构体中,Audience是受众,即接受 JWT 的一方,ExpiresAt是所签发的 JWT 过期时间,Id是 JWT 的唯一标识,IssueAt是签发时间,Issuer是 JWT 的签发者,NotBefore是 JWT 的生效时间,Subject是主题。
2.生成jwtToken
GenerateToken
方法用于生成 JWT Token,它利用参数中传入的appKey
、appSecret
,以及配置文件中的Issuer
(签发者)、Expire
(有效时间),根据指定的算法生成签名后的 Token。time.Now
可以获取当前时间,用这个时间加上 Token 的有效时间Expire
,得到过期时间expireTime
,再利用Unix
方法,得到一个int64
类型的、从时间点 January 1, 1970 UTC 到时间点t所经过的时间(单位 s)。参数中的appKey
和appSecret
并没有直接传入Claims
结构体,而是经过了 MD5 加密。NewWithClaims
会根据加密算法和Claims
对象来创建Token
实例,这个实例中的Header
就是之前提到的 JWT 三部分之一。signedString
方法会利用传入的密钥生成签名字符串。它利用t.Signing String
与t.Method.Sign
返回的字符串以.
为分隔符拼装在一起并返回。
func GenerateToken(appKey, appSecret string) (string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(global.JWTSetting.Expire)
claims := Claims{
AppKey: util.EncodeMD5(appKey),
AppSecret: util.EncodeMD5(appSecret),
StandardClaims: jwt.StandardClaims{
ExpiresAt: expireTime.Unix(),
Issuer: global.JWTSetting.Issuer,
},
}
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token, err := tokenClaims.SignedString(GetJWTSecret())
return token, err
}
func (t Time) Unix() int64 {
return t.unixSec()
}
func (t Time) Unix() int64 {
return t.unixSec()
}
func EncodeMD5(value string) string {
m := md5.New()
m.Write([]byte(value))
return hex.EncodeToString(m.Sum(nil))
}
func NewWithClaims(method SigningMethod, claims Claims) *Token {
return &Token{
Header: map[string]interface{}{
"typ": "JWT",
"alg": method.Alg(),
},
Claims: claims,
Method: method,
}
}
func (t *Token) SignedString(key interface{}) (string, error) {
var sig, sstr string
var err error
if sstr, err = t.SigningString(); err != nil {
return "", err
}
if sig, err = t.Method.Sign(sstr, key); err != nil {
return "", err
}
return strings.Join([]string{sstr, sig}, "."), nil
}
SigningString
会将 header(头部)和 payload(荷载)部分做一次 base64Url 编码,在下面的代码中,parts
用于盛放编码后的字符串,最后利用strings.Join
将这两个字符串以.
为分隔符连接在一起。 t.Method.Sign利用它的签名算法(这里是jwt.SigningMethodHS256)、t.SigningString得到的字符串、密钥secret,生成一个签名字符串,即 JWT 三部分之一的 signature(签名)。由此可以看出,签名是由头部、荷载、密钥、加密算法共同生成的,因此可以用来校验消息是否被篡改,一旦被篡改,签名就无法对上。
func (t *Token) SigningString() (string, error) {
var err error
parts := make([]string, 2)
for i, _ := range parts {
var jsonValue []byte
if i == 0 {
if jsonValue, err = json.Marshal(t.Header); err != nil {
return "", err
}
} else {
if jsonValue, err = json.Marshal(t.Claims); err != nil {
return "", err
}
}
parts[i] = EncodeSegment(jsonValue)
}
return strings.Join(parts, "."), nil
}
func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, error) {
if keyBytes, ok := key.([]byte); ok {
if !m.Hash.Available() {
return "", ErrHashUnavailable
}
hasher := hmac.New(m.Hash.New, keyBytes)
hasher.Write([]byte(signingString))
return EncodeSegment(hasher.Sum(nil)), nil
}
return "", ErrInvalidKeyType
}
3.解析校验Token
ParseToken
是GenerateToken
的反过程,它用来解析和校验 Token。该方法调用jwt.ParseWithClaims
获取tokenClaims
,然后对它进行格式的校验,并检查它是否有效,最终将Claims
返回。ParseWithClaims
用于解析鉴权的声明,它调用Parser.ParseWithClaims
进行解码和校验,并返回*Token。Token
结构体如下图所示,其中的Valid
用于表示该 Token 是否有效,它的值与ExpiresAt
、Issuer
、Not Before
有关。
func ParseToken(token string) (*Claims, error) {
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return GetJWTSecret(), nil
})
if tokenClaims != nil {
claims, ok := tokenClaims.Claims.(*Claims)
if ok && tokenClaims.Valid {
return claims, nil
}
}
return nil, err
}
func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
return new(Parser).ParseWithClaims(tokenString, claims, keyFunc)
}
type Token struct {
Raw string
Method SigningMethod
Header map[string]interface{}
Claims Claims
Signature string
Valid bool
}
三.总结
经过这次的代码分析,我基本上掌握了Jwt的基本用法,Jwt可以让我们的访问变得更加安全,而且 Jwt的优点有很多,比如Jwt简洁,Token数据量小,传输速度快;因为JWT Token是以JSON加密形式保存在客户端的,所以原则上任何web形式都支持,不需要在服务端保存会话信息,也就是说不依赖于cookie和session,所以没有了传统session认证的弊端,特别适用于分布式微服务。本次主要分析的语言是go语言。