使用 JWT 保護 API 訪問

來源:互聯網
上載者:User
APIs 的一個常見用例是提供一個授權中介軟體,允許用戶端向 APIs 發送授權請求。通常來說,用戶端會執行一些授權邏輯,產生一個「會話標識」。最近比較流行的 JWT ( JSON Web Tokens ) 提供了一個帶逾時時間的「會話標識」,使用它不需要額外的空間來執行驗證邏輯。本文是接著上一篇文章寫的,在閱讀下面內容之前建議先看一下之前的那篇文章 [用 go-chi 處理 HTTP 要求](https://scene-si.org/2018/03/12/handling-http-requests-with-go-chi/)接下來我們要用 [go-chi/jwtauth](https://github.com/go-chi/jwtauth) 在 APIs 上增加一個授權層。它是基於 [go-chi/chi](https://github.com/go-chi/chi) 實現的。授權可以是任意的(針對登入和沒有登入的使用者)或者有針對性的(只針對已經登入的使用者)。這樣就可以對兩種使用者實現不同的授權邏輯,根據 JWT 參數的合法性返回額外的授權驗證資訊。我用了 [titpetric/factory/resputil](https://github.com/titpetric/factory/tree/master/resputil) 來簡化錯誤處理和 JSON 資料的格式化。## JWT 到底是什麼> JSON Web Token ( JWT ) 是一個開放的標準 ( [RFC 7513](https://tools.ietf.org/html/rfc7519) ),定義如何在各部分之間安全的傳輸 JSON 對象,標準簡潔而且自包含,另外還對其加了數位簽章,所以可以對其合法性進行驗證「JWT」由三部分構成1. 資訊頭:指定了使用的簽名演算法2. 聲明部分:其中也可以包含逾時時間3. 基於指定的演算法產生的簽名通過這三部分資訊,API 服務端可以根據「JWT」資訊頭和聲明部分的資訊重建簽名。之所以可以這樣做,是因為產生簽名需要的秘鑰存放在伺服器端。```gojwtauth.New("HS256", []byte("K8UeMDPyb9AwFkzS"), nil)```如果這個簽名秘鑰比較簡單,建議立刻換一個複雜一些的,更改以後會使所有已經產生的「JWT」 失效,強制用戶端重新從伺服器擷取授權。## 聲明部分通過「JWT」的聲明,可以用像 "user_id": "1337" 這樣的格式來標識使用了 API 服務的用戶端,可以把它想象成 map[string]interface{} 這樣的 Go 資料結構,加上一些適當的轉換。當用戶端向 API 服務發起授權請求時,會發送用戶端識別碼 和一些其它資料來執行登入操作。伺服器端接收到請求後會產生一個相應的 「JWT」,並儲存在資料庫中,這樣用戶端隨後的請求就不需要再進行授權請求了,直到這個「JWT」逾時。應用最好產生一個調試「JWT」, 並輸出到記錄檔中。可以通過這個合法的「JWT」來調試應用。```gotype JWT struct { tokenClaim string tokenAuth *jwtauth.JWTAuth}func (JWT) new() *JWT { jwt := &JWT{ tokenClaim: "user_id", tokenAuth: jwtauth.New("HS256", []byte("K8UeMDPyb9AwFkzS"), nil), } log.Println("DEBUG JWT:", jwt.Encode("1")) return jwt}func (jwt *JWT) Encode(id string) string { claims := jwtauth.Claims{}. Set(jwt.tokenClaim, id). SetExpiryIn(30 * time.Second). SetIssuedNow() _, tokenString, _ := jwt.tokenAuth.Encode(claims) return tokenString}```每當通過 JWT{}.new() 產生新的「JWT」對象時,就會在日誌中輸出調試「JWT」的資訊```2018/04/19 11:35:18 DEBUG JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMSJ9.ZEBtFVPPLaT1YxsNpIzVGSnM4Vo7ZrEvp77jKgfN66s```你可以通過 URL 查詢參數的方式傳遞這個「JWT」來測試 GET 請求,或者在測試代碼中測試更加複雜的 API 請求,這時可以使用「授權頭」或者 Cookie 進行傳遞> **Note**: 產生「JWT」時一定要記得指定到期時間,否則產生的「JWT」會一直有效,直到更換了簽名秘鑰。另一個方案是在伺服器端使個別「JWT」失效,這需要一些代碼對它們進行記錄和喚醒。比如,不使用使用者識別碼,而是用會話 ID 來標識「JWT」,這樣就可以對 到期/登出 進行額外的驗證> 上面的例子中已經設定好了必要的參數,讓我們可以對帶有到期時間的「JWT」進行驗證。如果請求一個受保護的 API ,並且「JWT」已經逾時了,伺服器會返回一個錯誤資訊,提示你在調用這些介面時需要重新請求授權。## 使用 JWT 保護 API 訪問每一個對 API 的請求都可以包含一個「JWT 檢驗器」。它的工作方式和 CORS 類似 - 從「HTTP 要求參數」、cookie 或者「授權 HTTP 頭」中檢測「JWT」是否存在。「檢驗器」返回一個關於「JWT」 的上下文變數和一個可能的解析錯誤,即使沒有發現「JWT」,「檢驗器」也不會中斷正常的請求,它只是向「授權器」提供一些資訊。[go-chi/jwtauth](https://github.com/go-chi/jwtauth) 提供了一個預設的「檢驗器」,我們可以直接使用,讓我們為之前的「JWT」類型添加一個輔助方法,返回這個預設的「檢驗器」```gofunc (jwt *JWT) Verifier() func(http.Handler) http.Handler { return jwtauth.Verifier(jwt.tokenAuth)}```我們在每一個請求中都添加了這個「檢驗器」,這樣做以後,即使一個 API 不需要授權,也可以收到這些標識。提取並處理其中的聲明,沒有發現「JWT」時仍然可以返回一個合法的響應```gologin := JWT{}.new()mux := chi.NewRouter()mux.Use(cors.Handler)mux.Use(middleware.Logger)mux.Use(login.Verifier())```之前我們使用的是 mux.Route,為了把請求分成需要授權和不需要授權兩個部分,我們需要使用 [mux.Group()](https://godoc.org/github.com/go-chi/chi#Mux.Group)。使用 Group() 可以給全域處理器添加新的處理器,這樣我們在請求時就可以省略像 "/api/private/*" 這樣的首碼> Group 會建立一個新的內聯 Mux,它有一個空的中介軟體棧。如果一些請求的首碼部分是相同的,並且需要執行一些相同的中介軟體,就特別適合使用 Group 。```go// Protected API endpointsmux.Group(func(mux chi.Router) { // Error out on invalid/empty JWT here mux.Use(login.Authenticator()) { mux.Get("/time", requestTime) mux.Route("/say", func(mux chi.Router) { mux.Get("/{name}", requestSay) mux.Get("/", requestSay) }) }})// Public API endpointsmux.Group(func(mux chi.Router) { // Print info about claim mux.Get("/api/info", func(w http.ResponseWriter, r *http.Request) { owner := login.Decode(r) resputil.JSON(w, owner, errors.New("Not logged in")) })})```現在 /time 和 /say 必需有一個合法的「JWT」才能訪問,/time 不直接檢驗「JWT」,而是把檢驗工作交給了「授權器」。比如,我們用一個到期了的「JWT」訪問 /time ,會得到如下的資訊:```json{ "error": { "message": "Error validating JWT: jwtauth: token is expired" }}```但是如果我們請求的是 /info,我們會收到如下的資訊:```json{ "response": "1"}```用一個到期的「JWT」訪問 /info,則會返回:```json{ "error": { "message": "Not logged in" }}```兩個請求返回不同的資訊是因為我們在 Decode 函數中實現了完整的驗證邏輯。如果「JWT」 是非法或者到期的,會返回一個自訂的錯誤資訊,而不是用來保護 /time 的 Authenticate 方法中的傳回值。```gofunc (jwt *JWT) Decode(r *http.Request) string { val, _ := jwt.Authenticate(r) return val}func (jwt *JWT) Authenticate(r *http.Request) (string, error) { token, claims, err := jwtauth.FromContext(r.Context()) if err != nil || token == nil { return "", errors.Wrap(err, "Empty or invalid JWT") } if !token.Valid { return "", errors.New("Invalid JWT") } return claims[jwt.tokenClaim].(string), nil}```我們用同樣的方法讓授權中介軟體使用「JWT」來保護對私人 API 的訪問 。Decode() 方法中的錯誤被忽略了,因為被調用的方法預設返回了一個Null 字元串。授權中介軟體返回完整的錯誤資訊:```gofunc (jwt *JWT) Authenticator() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, err := jwt.Authenticate(r) if err != nil { resputil.JSON(w, err) return } next.ServeHTTP(w, r) }) }}```以上授權微服務的完整代碼可以從 [GitHub](https://github.com/titpetric/books/tree/master/api-foundations/chapter4b-jwt) 上擷取,可以免費下載和體驗。

via: https://scene-si.org/2018/05/08/protecting-api-access-with-jwt/

作者:Tit Petric 譯者:jettyhan 校對:polaris1119

本文由 GCTT 原創編譯,Go語言中文網 榮譽推出

本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽

395 次點擊  
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.