由于目前JWT在验证权限方面是主流,因此重点介绍JWT
session和JWT是干啥的?
由于http连接是无状态的,用户登录后,请求结束,立刻断开连接,登录状态并不会保留,用户想要进行操作,服务器就必须对用户进行权限验证,session和JWT的本质,都是服务器在收到用户登录请求的时候,颁发给客户端一个token(令牌)(其实就是一个字符串而已),这个令牌一定和userID有一一对应的关系,客户端下次发请求的时候,携带这个令牌,服务端通过对令牌进行验证,可以知道发请求的到底是哪一个用户。
session通过查token的映射表找到userID,jwt通过解密token找到userID。
session
session是一种以seesionID作为token,颁发给客户端的验证方式,缺点是服务器必须维护一张以sessionID作为key的表,每次客户端的请求都要查这张表
JWT
JWT有三个部分,头部,载荷和签名
示例:
这里的头部和载荷都是base64编码的,也就是不加密的,只有签名部分是加密的。
签名部分经常用的就是两种:
对称加密:HS256、HS512
非对称加密:RS256、RS512
这里的RS加密因为加密用私钥,解密用公钥,公钥泄漏也没问题,因此比HS的安全性更高。
HS加解密用一个公钥,不过如果对网站的安全性要求没那么高,使用公钥加解密也没什么问题
payload部分官方的字段如下,我们也可以自定义字段:
这里的exp字段是必须要设置的,否则token的过期时间将不可控,有极大的危险
iss (issuer):签发人
sub (subject):主题
aud (audience):受众
exp (expiration time):过期时间
nbf (Not Before):生效时间,在此之前是无效的
iat (Issued At):签发时间
jti (JWT ID):编号
JWT的演示网站:
jwt.io
JWT是需要生成密钥的,这里推荐一个RSA密钥生成网站:
https://cryptotools.net/
golang的JWT操作
使用的库为jwt-go
加密(签名)(生成token)部分
生成JWT的类:
package token
import (
"crypto/rsa"
"github.com/dgrijalva/jwt-go"
"time"
)
type JWTTokenGen struct {
privateKey *rsa.PrivateKey
issuer string
nowFunc func() time.Time
}
func NewJWTTokenGen(issuer string, privateKey *rsa.PrivateKey) *JWTTokenGen {
return &JWTTokenGen{
issuer: issuer,
nowFunc: time.Now,
privateKey: privateKey,
}
}
func (t *JWTTokenGen) GenerateToken(accountID string, expire time.Duration) (string,error){
nowSec := t.nowFunc().Unix()
tkn := jwt.NewWithClaims(jwt.SigningMethodRS512, jwt.StandardClaims{
ExpiresAt: nowSec + int64(expire.Seconds()),
IssuedAt: nowSec,
Issuer: t.issuer,
Subject: accountID,
})
return tkn.SignedString(t.privateKey)
}
生成rsa.privatekey
类,传入JWTTokenGen
pkBytes, err := ioutil.ReadAll(file)
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(pkBytes))
解密部分(验证token,解析出userID)
验证类
package token
import (
"crypto/rsa"
"fmt"
"github.com/dgrijalva/jwt-go"
)
// JWTTokenVerifier verifies jwt access tokens.
type JWTTokenVerifier struct {
PublicKey *rsa.PublicKey
}
// Verify verifies a token and returns account id.
func (v *JWTTokenVerifier) Verify(token string) (string, error) {
t, err := jwt.ParseWithClaims(token, &jwt.StandardClaims{},
func(token *jwt.Token) (interface{}, error) {
return v.PublicKey, nil
})
if err != nil {
return "", fmt.Errorf("cannot parse token: %v", err)
}
if !t.Valid {
return "", fmt.Errorf("token not valid")
}
clm, ok := t.Claims.(*jwt.StandardClaims)
if !ok {
return "", fmt.Errorf("token claim is not StandardClaims")
}
if err := clm.Valid(); err != nil {
return "", fmt.Errorf("claim not valid: %v", err)
}
return clm.Subject, nil
}
生成rsa.PublicKey
pubKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(publicKey))