
大多数情况下,你需要在用户访问API之前对其进行授权。这是一项非常常见的任务,使用Go非常容易实现。在本文中,我们将看到如何实现它。
问题
假设你有一个需要保护的API。你想确保只有经过身份验证的用户才能访问它。你还想确保用户有访问API的正确权限。
例如,只向高级用户授予高级内容的访问权限。
解决方案
为了解决这个问题,我们必须介绍两个概念:身份验证和授权。身份验证是验证用户是否是其所说的人的过程。授权是验证用户是否具有访问资源的正确权限的过程。
在本文中,我们将只看如何进行授权部分。我们将看到如何验证用户是否具有访问资源的正确权限。
在开始之前,让我们介绍一些我们在本文中将使用的工具。
工具
Open Policy Agent
在本教程中,我们将使用Open Policy Agent(OPA)来解决这个问题。OPA是一个提供策略引擎的开源项目。它可用于强制执行应用程序中的策略。它可以用于授权用户、验证数据等等。

OPA架构图
我知道你可以直接在你的应用程序中完成这个任务,但是这将影响你软件的可维护性。想象一下,你在你的组织中创建了一个新角色,你将不得不修改你的代码以处理这个角色的添加。通过使用OPA,策略是全局的,对组织的更改不会强制你修改你的代码。
Opa-middleware库
我们将使用opa-middleware库将OPA集成到我们的Go应用程序中。这个库提供了一个中间件,你可以用来授权你的用户。它非常容易使用和非常灵活。
例子
假设你的API有两个用户。Alice是一位文章消费者(只读访问),Bob是一位发布者(写访问)
我们假设你想保护以下CRUD端点:
type Handler struct {
}
func (h *Handler) GetArticle(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Get article"))
}
func (h *Handler) CreateArticle(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Create article"))
}
func (h *Handler) UpdateArticle(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Update article"))
}
func (h *Handler) DeleteArticle(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Delete article"))
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
h.GetArticle(w, r)
case http.MethodPost:
h.CreateArticle(w, r)
case http.MethodPut:
h.UpdateArticle(w, r)
case http.MethodDelete:
h.DeleteArticle(w, r)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
OPA策略
OPA策略是一个定义用户权限的JSON文档。它非常容易编写。你可以在几分钟内编写它。
在这个例子中,我们将使用以下数据:
{
"users": {
"alice": {
"permissions": [
"articles:read"
]
},
"bob": {
"permissions": [
"articles:read",
"articles:write"
]
}
}
}
这个数据定义了两个用户:alice和bob。alice拥有articles:read权限。bob拥有articles:read和articles:write权限。
下面的策略将在这个例子中用于授权用户:
package policy
default allow = false
allow {
input.method = "GET"
input.path = ["articles"]
has_permission("articles:read")
}
allow {
input.method = "POST"
input.path = ["articles"]
has_permission("articles:write")
}
allow {
input.method = "PUT"
input.path = ["articles"]
has_permission("articles:write")
}
allow {
input.method = "DELETE"
input.path = ["articles"]
has_permission("articles:write")
}
has_permission(permission) {
user := input.user
permissions := document.users[user].permissions
permissions[_] = permission
}
Go应用程序
现在我们有了OPA策略,我们可以编写我们的Go应用程序。我们将使用opa-middleware库将OPA集成到我们的应用程序中。
内嵌
package main
import (
opamiddleware "github.com/Joffref/opa-middleware"
"github.com/Joffref/opa-middleware/config"
"net/http"
"strings"
)
var policy = `
package policy
default allow = false
document := {
"users": {
"alice": {
"permissions": [
"articles:read"
]
},
"bob": {
"permissions": [
"articles:read",
"articles:write"
]
}
}
}
allow {
input.method = "GET"
input.path = ["articles"]
has_permission("articles:read")
}
allow {
input.method = "POST"
input.path = ["articles"]
has_permission("articles:write")
}
allow {
input.method = "PUT"
input.path = ["articles"]
has_permission("articles:write")
}
allow {
input.method = "DELETE"
input.path = ["articles"]
has_permission("articles:write")
}
has_permission(permission) {
user := input.user
permissions := document.users[user].permissions
permissions[_] = permission
}
`
type Handler struct {
}
func (h *Handler) GetArticle(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Get article"))
}
func (h *Handler) CreateArticle(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Create article"))
}
func (h *Handler) UpdateArticle(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Update article"))
}
func (h *Handler) DeleteArticle(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Delete article"))
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
h.GetArticle(w, r)
case http.MethodPost:
h.CreateArticle(w, r)
case http.MethodPut:
h.UpdateArticle(w, r)
case http.MethodDelete:
h.DeleteArticle(w, r)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func main() {
handler, err := opamiddleware.NewHTTPMiddleware(
&config.Config{
Policy: policy,
Query: "data.policy.allow",
InputCreationMethod: func(r *http.Request) (map[string]interface{}, error) {
return map[string]interface{}{
"path": strings.Split(r.URL.Path, "/")[1:],
"method": r.Method,
"user": r.Header.Get("X-User"),
}, nil
},
ExceptedResult: true,
DeniedStatusCode: http.StatusForbidden,
DeniedMessage: "Forbidden",
},
&Handler{},
)
if err != nil {
panic(err)
}
http.HandleFunc("/articles", handler.ServeHTTP)
err = http.ListenAndServe(":8080", nil)
if err != nil {
return
}
}
远程
或者,如果你想将OPA用作sidecar。
package main
import (
opamiddleware "github.com/Joffref/opa-middleware"
"github.com/Joffref/opa-middleware/config"
"net/http"
"strings"
)
type Handler struct {
}
func (h *Handler) GetArticle(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Get article"))
}
func (h *Handler) CreateArticle(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Create article"))
}
func (h *Handler) UpdateArticle(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Update article"))
}
func (h *Handler) DeleteArticle(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Delete article"))
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
h.GetArticle(w, r)
case http.MethodPost:
h.CreateArticle(w, r)
case http.MethodPut:
h.UpdateArticle(w, r)
case http.MethodDelete:
h.DeleteArticle(w, r)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func main() {
handler, err := opamiddleware.NewHTTPMiddleware(
&config.Config{
URL: "http://localhost:8181/",
Query: "data.policy.allow",
InputCreationMethod: func(r *http.Request) (map[string]interface{}, error) {
return map[string]interface{}{
"path": strings.Split(r.URL.Path, "/")[1:],
"method": r.Method,
"user": r.Header.Get("X-User"),
}, nil
},
ExceptedResult: true,
DeniedStatusCode: http.StatusForbidden,
DeniedMessage: "Forbidden",
},
&Handler{},
)
if err != nil {
panic(err)
}
http.HandleFunc("/articles", handler.ServeHTTP)
err = http.ListenAndServe(":8080", nil)
if err != nil {
return
}
}
让我们测试一下
首先,尝试使用Alice的帐户获取文章。

它确实可以工作,因为在OPA中,Alice被授权读取文章。现在,让我们看看Alice是否可以创建文章。

如上所示,Alice不能创建文章,因为规则中明确写着。让我们看看Bob。

Bob可以创建文章。我们的API的文章端点中授权被很好地执行。
感谢阅读!!
译自:https://medium.com/@mjoffre/go-api-authorization-made-simple-9ee65d23a2b3











评论(0)