首页
Preview

简单实现 Go API 授权

大多数情况下,你需要在用户访问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"
            ]
        }
    }
}

这个数据定义了两个用户:alicebobalice拥有articles:read权限。bob拥有articles:readarticles: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

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
菜鸟一只
你就是个黄焖鸡,又黄又闷又垃圾。

评论(0)

添加评论