golang學習筆記10 beego api 用jwt驗證auth2 token 獲取解碼信息
JSON Web Tokens - jwt.io
https://jwt.io/ 這個網站可以驗證token是不是正確的
基於token的鑒權機制類似於http協議也是無狀態的,它不需要在服務端去保留用戶的認證信息或者會話信息。這就意味着基於token認證機制的應用不需要去考慮用戶在哪一台服務器登錄了,這就為應用的擴展提供了便利。
流程上是這樣的:
- 用戶使用用戶名密碼來請求服務器
- 服務器進行驗證用戶的信息
- 服務器通過驗證發送給用戶一個token
- 客戶端存儲token,並在每次請求時附送上這個token值
- 服務端驗證token值,並返回數據
這個token必須要在每次請求時傳遞給服務端,它應該保存在請求頭里, 另外,服務端要支持CORS(跨來源資源共享)
策略,一般我們在服務端這么做就可以了Access-Control-Allow-Origin: *
。
那么我們現在回到JWT的主題上。
JWT長什么樣?
JWT是由三段信息構成的,將這三段信息文本用.
鏈接一起就構成了Jwt字符串。就像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
JWT的構成
第一部分我們稱它為頭部(header),第二部分我們稱其為載荷(payload, 類似於飛機上承載的物品),第三部分是簽證(signature).
header
jwt的頭部承載兩部分信息:
- 聲明類型,這里是jwt
- 聲明加密的算法 通常直接使用 HMAC SHA256
完整的頭部就像下面這樣的JSON:
{
'typ': 'JWT', 'alg': 'HS256' }
然后將頭部進行base64加密(該加密是可以對稱解密的),構成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
playload
載荷就是存放有效信息的地方。這個名字像是特指飛機上承載的貨品,這些有效信息包含三個部分
- 標准中注冊的聲明
- 公共的聲明
- 私有的聲明
標准中注冊的聲明 (建議但不強制使用) :
- iss: jwt簽發者
- sub: jwt所面向的用戶
- aud: 接收jwt的一方
- exp: jwt的過期時間,這個過期時間必須要大於簽發時間
- nbf: 定義在什么時間之前,該jwt都是不可用的.
- iat: jwt的簽發時間
- jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。
公共的聲明 :
公共的聲明可以添加任何的信息,一般添加用戶的相關信息或其他業務需要的必要信息.但不建議添加敏感信息,因為該部分在客戶端可解密.
私有的聲明 :
私有聲明是提供者和消費者所共同定義的聲明,一般不建議存放敏感信息,因為base64是對稱解密的,意味着該部分信息可以歸類為明文信息。
定義一個payload:
{
"sub": "1234567890", "name": "John Doe", "admin": true }
然后將其進行base64加密,得到Jwt的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
signature
jwt的第三部分是一個簽證信息,這個簽證信息由三部分組成:
- header (base64后的)
- payload (base64后的)
- secret
這個部分需要base64加密后的header和base64加密后的payload使用.
連接組成的字符串,然后通過header中聲明的加密方式進行加鹽secret
組合加密,然后就構成了jwt的第三部分。
// javascript var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload); var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
將這三部分用.
連接成一個完整的字符串,構成了最終的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:secret是保存在服務器端的,jwt的簽發生成也是在服務器端的,secret就是用來進行jwt的簽發和jwt的驗證,所以,它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味着客戶端是可以自我簽發jwt了。
如何應用
一般是在請求頭里加入Authorization
,並加上Bearer
標注:
fetch('api/user/1', { headers: { 'Authorization': 'Bearer ' + token } })
服務端會驗證token,如果驗證通過就會返回相應的資源。整個流程就是這樣的:
// ParseToken parse JWT token in http header. func (base *BaseController) ParseToken() (t *jwt.Token, e *ControllerError) { authString := base.Ctx.Input.Header("Authorization") beego.Debug("AuthString:", authString) kv := strings.Split(authString, " ") if len(kv) != 2 || kv[0] != "Bearer" { beego.Error("AuthString invalid:", authString) return nil, errInputData } tokenString := kv[1] // Parse token token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { // 必要的驗證 RS256 if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } //// 可選項驗證 'aud' claim //aud := "https://api.cn.atomintl.com" //checkAud := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false) //if !checkAud { // return token, errors.New("Invalid audience.") //} // 必要的驗證 'iss' claim iss := "https://atomintl.auth0.com/" checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false) if !checkIss { return token, errors.New("Invalid issuer.") } // 我們的公鑰,可以在https://manage.auth0.com/ 上下載到對應的封裝好的json,里面包括了簽名 k5c := "xxxx" cert := "-----BEGIN CERTIFICATE-----\n" + k5c + "\n-----END CERTIFICATE-----" result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert)) //result := []byte(cert) // 不是正確的 PUBKEY 格式 都會 報 key is of invalid type return result, nil }) if err != nil { beego.Error("Parse token:", err) if ve, ok := err.(*jwt.ValidationError); ok { if ve.Errors&jwt.ValidationErrorMalformed != 0 { // That's not even a token return nil, errInputData } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { // Token is either expired or not active yet return nil, errExpired } else { // Couldn't handle this token return nil, errInputData } } else { // Couldn't handle this token return nil, errInputData } } if !token.Valid { beego.Error("Token invalid:", tokenString) return nil, errInputData } beego.Debug("Token:", token) return token, nil }
func (c *xxxController) GetMy() { var fields []string var query = make(map[string]string) //get token token, e := c.ParseToken() if e != nil { c.RetError(e) beego.Debug("ParseToken error") //return } claims, ok := token.Claims.(jwt.MapClaims) if !ok { beego.Debug("get ParseToken claims error") //return } beego.Debug("claims:", claims) var Email string = claims["Email"].(string) beego.Debug("Email:", Email) query["Uid"] = Email // fields: col1,col2,entity.col3 if v := c.GetString("fields"); v != "" { fields = strings.Split(v, ",") } l, err := models.GetMyPayment(query, fields) if err != nil { c.Data["json"] = err.Error() } else { c.Data["json"] = l } c.ServeJSON() }
---------------------------
