這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。身份認證和授權對 web 應用的安全至關重要。最近,我用 Go 完成了我的第一個正式的 web 應用,這篇文章是在這個過程中我所學到的部分內容。本文中,我們的關注點在於如何在 web 應用中使用開源的 casbin 庫進行 HTTP 許可權控制。同時,在範例程式碼中我們使用了 scs 庫進行 session 管理。下面的例子十分基礎,希望它儘可能的展示了如何在 Go web 應用中實現許可權控制。為了更側重於展示 casbin 的使用,我們盡量簡化商務邏輯(例如:不需密碼的登陸操作)。我們一起來看一下!注意:請不要在生產環境中使用所示的用例代碼,該例子側重於描述清晰,而不是安全性。## 建立首先,我們建立一個 User 模型,並實現了相應方法:```gotype User struct {ID intName stringRole string}type Users []Userfunc (u Users) Exists(id int) bool {...}func (u Users) FindByName(name string) (User, error) {...}```接著配置 casbin 所需檔案。這裡我們需要一個設定檔和一個策略檔案。設定檔使用 PERM 元模型。PERM 表示策略(Policy)、效果(Effect)、請求(Request)和匹配器(Matchers)。在 auth_model.conf 設定檔中有如下內容:```[request_definition]r = sub, obj, act[policy_definition]p = sub, obj, act[policy_effect]e = some(where (p.eft == allow))[matchers]m = r.sub == p.sub && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")```其中定義了請求和策略來表示主體,客體和動作。在本例中,主體表示使用者角色,客體表示訪問路徑,action 表示要求方法(例:GET, POST 等)。匹配器定義了策略是如何匹配的,可以通過直接定義主體,或者使用像 keyMatch 這樣的協助方法,它也可以匹配萬用字元。casbin 實際比這個簡單的例子要強大得多,你可以用聲明的方式定義各種自訂功能來達到輕鬆切換和維護鑒權配置的效果。在安全性方面,我通常會選擇最簡單的解決方案,因為當系統開始變複雜和難以維護時,錯誤就開始發生。在這個例子中,策略檔案就是一個簡單的 csv 檔案,描述了哪些角色可以訪問哪些路徑等。policy.csv 檔案格式如下:```p, admin, /*, *p, anonymous, /login, *p, member, /logout, *p, member, /member/*, *```這個設定檔十分簡單。在這個例子中,我們簡單的定義了 admin 角色可以訪問所有內容,member 角色可以訪問以 /member/ 開頭的路徑和 logout 路徑,未認證使用者可以登陸。這種形式的好處在於即使應用具有許多規則和使用者角色,它仍然是可維護的。## 執行讓我們從 main 函數開始,將所有的東西都配置好,並啟動 http 伺服器:```gofunc main() {// setup casbin auth rulesauthEnforcer, err := casbin.NewEnforcerSafe("./auth_model.conf", "./policy.csv")if err != nil {log.Fatal(err)}// setup session storeengine := memstore.New(30 * time.Minute)sessionManager := session.Manage(engine, session.IdleTimeout(30*time.Minute), session.Persist(true), session.Secure(true))// setup usersusers := createUsers()// setup routesmux := http.NewServeMux()mux.HandleFunc("/login", loginHandler(users))mux.HandleFunc("/logout", logoutHandler())mux.HandleFunc("/member/current", currentMemberHandler())mux.HandleFunc("/member/role", memberRoleHandler())mux.HandleFunc("/admin/stuff", adminHandler())log.Print("Server started on localhost:8080")log.Fatal(http.ListenAndServe(":8080", sessionManager(authorization.Authorizer(authEnforcer, users)(mux))))}```這裡有幾點需要注意的,通常,我們需要配置鑒權規則,session 管理,使用者,http 處理方法,啟動 http 伺服器,並且用鑒權中介軟體和 session 管理器封裝路由。我們逐一分析上面的過程。首先,我們用上面的 auth_model.conf 和 policy.csv 建立了一個 casbin 執行器。如果出錯了,則關閉服務,因為有可能鑒權規則出錯了。第二步是設定會話管理器。我們建立了一個具有 30 分鐘逾時的記憶體 session 儲存和和一個具備安全 cookie 儲存的會話管理器。CreateUsers 函數建立了三個不同的使用者,其使用者角色如下所示:```gofunc createUsers() model.Users {users := model.Users{}users = append(users, model.User{ID: 1, Name: "Admin", Role: "admin"})users = append(users, model.User{ID: 2, Name: "Sabine", Role: "member"})users = append(users, model.User{ID: 3, Name: "Sepp", Role: "member"})return users}```在實際應用中,我們會使用資料庫來儲存使用者資料,在這個例子中,為了方便起見我們使用上面的列表。接下來是登陸和登出的處理方法:```gofunc loginHandler(users model.Users) http.HandlerFunc {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {name := r.PostFormValue("name")user, err := users.FindByName(name)if err != nil {writeError(http.StatusBadRequest, "WRONG_CREDENTIALS", w, err)return}// setup sessionif err := session.RegenerateToken(r); err != nil {writeError(http.StatusInternalServerError, "ERROR", w, err)return}session.PutInt(r, "userID", user.ID)session.PutString(r, "role", user.Role)writeSuccess("SUCCESS", w)})}func logoutHandler() http.HandlerFunc {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {if err := session.Renew(r); err != nil {writeError(http.StatusInternalServerError, "ERROR", w, err)return}writeSuccess("SUCCESS", w)})}```對於登陸,我們從請求中擷取到使用者名稱,檢查該使用者是否存在,若存在,則建立一個新的 session,並將使用者角色和 ID 存入 session 中。對於登出,我們建立一個新的空的 session,並從 session 儲存中刪除舊的 session,登出該使用者。接著,我們定義了幾個處理函數,通過返回使用者識別碼 和角色來測試應用的實現。這些處理函數的端點由上面的 policy.csv 檔案定義的 casbin 保護。```gofunc currentMemberHandler() http.HandlerFunc {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {uid, err := session.GetInt(r, "userID")if err != nil {writeError(http.StatusInternalServerError, "ERROR", w, err)return}writeSuccess(fmt.Sprintf("User with ID: %d", uid), w)})}func memberRoleHandler() http.HandlerFunc {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {role, err := session.GetString(r, "role")if err != nil {writeError(http.StatusInternalServerError, "ERROR", w, err)return}writeSuccess(fmt.Sprintf("User with Role: %s", role), w)})}func adminHandler() http.HandlerFunc {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {writeSuccess("I'm an Admin!", w)})}```我們可以通過 session.GetInt 和 session.GetString 來擷取當前 session 中的值。為了讓鑒權機制真正的保護到處理函數,我們需要實現一個用來封裝路由的鑒權中介軟體。```gofunc Authorizer(e *casbin.Enforcer, users model.Users) func(next http.Handler) http.Handler {return func(next http.Handler) http.Handler {fn := func(w http.ResponseWriter, r *http.Request) {role, err := session.GetString(r, "role")if err != nil {writeError(http.StatusInternalServerError, "ERROR", w, err)return}if role == "" {role = "anonymous"}// if it's a member, check if the user still existsif role == "member" {uid, err := session.GetInt(r, "userID")if err != nil {writeError(http.StatusInternalServerError, "ERROR", w, err)return}exists := users.Exists(uid)if !exists {writeError(http.StatusForbidden, "FORBIDDEN", w, errors.New("user does not exist"))return}}// casbin rule enforcingres, err := e.EnforceSafe(role, r.URL.Path, r.Method)if err != nil {writeError(http.StatusInternalServerError, "ERROR", w, err)return}if res {next.ServeHTTP(w, r)} else {writeError(http.StatusForbidden, "FORBIDDEN", w, errors.New("unauthorized"))return}}return http.HandlerFunc(fn)}}```鑒權中介軟體以 casbin 規則執行器和使用者作為參數。首先,它從 session 中擷取到請求使用者的角色。若使用者沒有角色,設定為 anonymous 角色,否則,若使用者角色為 member,我們將 session 中的 useID 和使用者列表相比對,來判斷使用者是否合法。在這些初步的檢查之後,我們可以將使用者角色,請求路徑和要求方法傳給 casbin 執行器,執行器決定了具有該角色( subject )的使用者是否允許訪問由該要求方法( action )和路徑( object )指定的資源。若校正失敗,則返回 403 ,若通過,則調用封裝的 http 處理函數,允許使用者訪問請求資源。正如主函數中提及的,session 管理器和鑒權器對路由進行了封裝,所以每個請求都需要通過這個中介軟體,確保了安全性。我們可以通過登陸不同的使用者,用 curl 或 postman 訪問上述的處理函數來測試效果。## 結論我已經在一個中型 web 應用生產環境中使用了 casbin,並且對它的可維護性和穩定性感到十分滿意。可以看看它的文檔,casbin 是一個非常強大的鑒權工具,以聲明的方式提供了大量的存取控制模型。本文旨在展示 casbin 和 scs 的強大之處,並且展示 go web 應用的簡潔清晰之處。資源:- [代碼](https://github.com/zupzup/casbin-http-role-example)- [casbin](https://github.com/casbin/casbin)- [scs](https://github.com/alexedwards/scs)
via: https://zupzup.org/casbin-http-role-auth/
作者:Mario 譯者:linyy1991 校對:rxcai
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
3565 次點擊 ∙ 2 贊