用户授权
简介
除了提供内置的 身份验证(authentication) 服务外,Goravel 还提供了一种可以很简单就进行使用的方法,来对用户与资源的授权关系进行管理。即使用户已经通过了「身份验证(authentication)」, 用户也可能无权对应用程序中的模型或数据库记录进行删除或更改。
Goravel 主要提供了两种授权操作的方法: 拦截器 和 策略。可以把拦截器(gates)和策略(policies)想象成路由和控制器。拦截器(Gates)提供了一种轻便的基于闭包函数的授权方法,像是路由。而策略(policies),就像是一个控制器,对特定模型或资源进行管理。在本文档中,我们将首先探讨拦截器(gates),然后是策略(policies)。
您在构建应用程序时,不用为是使用拦截器(gates)或是使用策略(policies)而担心,并不需要在两者中进行唯一选择。大多数的应用程序都同时包含两种方法,并且同时使用两者。
拦截器(Gates)
编写拦截器(Gates)
拦截器(Gates)是用来确定用户是否有权执行给定操作的闭包函数。默认条件下,拦截器(Gates)的使用,是在 app/providers/auth_service_provider.go
文件中的 Boot
方法里来规定 Gate
规则。
在下面的例子中,我们将定义一个拦截器(Gates),通过比较用户的 id 来判断是否有对 post 数据操作的权限:
package providers
import (
"context"
contractsaccess "github.com/goravel/framework/contracts/auth/access"
"github.com/goravel/framework/auth/access"
"github.com/goravel/framework/facades"
)
type AuthServiceProvider struct {
}
func (receiver *AuthServiceProvider) Register(app foundation.Application) {
}
func (receiver *AuthServiceProvider) Boot(app foundation.Application) {
facades.Gate().Define("update-post", func(ctx context.Context, arguments map[string]any) contractsaccess.Response {
user := ctx.Value("user").(models.User)
post := arguments["post"].(models.Post)
if user.ID == post.UserID {
return access.NewAllowResponse()
} else {
return access.NewDenyResponse("error")
}
})
}
行为授权控制
如果需要通过拦截器(Gates)来对行为进行授权控制,您可以通过调用 Gate
中的 Allows
或 Denies
方法。
package controllers
import (
"github.com/goravel/framework/facades"
)
type UserController struct {
func (r *UserController) Show(ctx http.Context) http.Response {
var post models.Post
if facades.Gate().Allows("update-post", map[string]any{
"post": post,
}) {
}
}
您还可以通过 any
或 none
方法来一次性授权多个行为:
if facades.Gate().Any([]string{"update-post", "delete-post"}, map[string]any{
"post": post,
}) {
// 用户可以提交update或delete...
}
if facades.Gate().None([]string{"update-post", "delete-post"}, map[string]any{
"post": post,
}) {
// 用户不可以提交update和delete...
}
拦截器(Gates)返回(Responses)
使用 Allows
方法,将仅返回一个简单的布尔值,您也还可以使用 Inspect
方法来返回拦截器(Gates)中的所有响应值:
response := facades.Gate().Inspect("edit-settings", nil);
if (response.Allowed()) {
// 行为进行授权...
} else {
fmt.Println(response.Message());
}
拦截器(Gates)优先级
有时,您可能希望将所有权限授予特定用户。您可以使用 Before
方法。该方法将定义该授权拦截规则,优先于所有其他授权拦截规则前执行:
facades.Gate().Before(func(ctx context.Context, ability string, arguments map[string]any) contractsaccess.Response {
user := ctx.Value("user").(models.User)
if isAdministrator(user) {
return access.NewAllowResponse()
}
return nil
})
如果 Before
返回的是非 nil 结果,则该返回将会被视为最终的检查结果。
您还可以使用 After
方法,来定义在所有授权拦截规则执行后,再次进行授权拦截规则判定:
facades.Gate().After(func(ctx context.Context, ability string, arguments map[string]any, result contractsaccess.Response) contractsaccess.Response {
user := ctx.Value("user").(models.User)
if isAdministrator(user) {
return access.NewAllowResponse()
}
return nil
})
注意:只有当
facades.Gate().Define
返回 nil 时,才会应用After
的返回结果。
注入 Context
context
将被传入到 Before
, After
, Define
方法中。
facades.Gate().WithContext(ctx).Allows("update-post", map[string]any{
"post": post,
})
策略(Policies)
生成策略
您可以使用 make:policy
Artisan 命令生成策略。生成的策略将放置在 app/policies
目录中。如果应用程序中不存在此目录,Goravel 将自动创建:
go run . artisan make:policy PostPolicy
go run . artisan make:policy user/PostPolicy
编写策略
可以为策略添加具体的方法,例如,让我们在 PostPolicy
上定义一个 Update
方法,该方法判断 models.User
是否可以更新 models.Post
。
package policies
import (
"context"
"github.com/goravel/framework/contracts/auth/access"
contractsaccess "github.com/goravel/framework/contracts/auth/access"
)
type PostPolicy struct {
}
func NewPostPolicy() *PostPolicy {
return &PostPolicy{}
}
func (r *PostPolicy) Update(ctx context.Context, arguments map[string]any) contractsaccess.Response {
user := ctx.Value("user").(models.User)
post := arguments["post"].(models.Post)
if user.ID == post.UserID {
return access.NewAllowResponse()
} else {
return access.NewDenyResponse("You do not own this post.")
}
}
然后我们就可以在 app/providers/auth_service_provider.go
中注册策略:
facades.Gate().Define("update-post", policies.NewPostPolicy().Update)
您可以继续根据需要为策略授权的各种操作定义其他方法。例如,您可以定义 View
或 Delete
方法来授权各种与 models.Post
相关的操作,但请记住,您可以自由地为策略方法命名任何您喜欢的名称。