homeASCIIcasts

188: Declarative Authorization 

(view original Railscast)

Other translations: En Es

Other formats:

Written by beta

这一集将详细介绍授权,特别是基于角色的授权机制。通过它,用户能够被指派给一个角色,角色将给予他们某些贯穿整个应用的特定的权限。全面的描述授权是一个棘手的问题。不仅仅是因为存在太多的授权plugins和gems,另一个问题是所谓的最佳的授权方案实际上取决于你所编写的应用程序类型。

本集的焦点是declarative authorization,更先进的的解决方案之一。一开始它可能看起来颇为复杂,但只要付出少许努力,你就会发现它提供了许多强大的功能,并能处理范围广泛的授权需求。

给我们的应用程序加入授权

在本集中我们将使用一个博客应用程序。这个应用的主页显示了一个文章列表,每篇文章能够拥有许多评论,并显示在文章本身的页面上。

The home page of our blogging application.

当前状态下,任何一个访问该网站的用户都能够编辑或删除任意的文章或评论,这显然是我们不愿看到的,因此我们将修改这个应用程序,根据每个用户的角色来授予不同的功能级别。我们已经在站点上做好了授权的准备工作,每个页面的顶部都有链接以允许用户注册或登录,为此我们使用了Authlogic,它在160集里被详细介绍过 [观看, 阅读]。

 为了增加角色,我们将创建一个名为Role的新模型,它有一个名为name的字符串属性。RoleUser模型经由一个名为Assignment的连接表,通过has_many :through关系连接起来。

 为了给用户指定角色,我们要修改注册表单,以便让用户选择他们角色。这里使用了曾在17集 [观看, 阅读]里描述过的技术,用一系列的复选框来实现。很显然,在一个真实世界里的应用中,我们不能让用户自己选择角色,但为了保持示例应用的简单,这里我们允许这么做。

The signup page showing the roles checkboxes.

我们的三个角色将拥有不同的权限:admin角色的成员将被允许创建、编辑和删除整个应用中的任何东西。moderators 将被允许编辑任何人的评论,但文章不行。authors 将被允许创建文章和编辑他们自己的文章。最后,没有被指定任何角色的用户将被允许创建评论和编辑自己的评论。

Declarative Authorization入门

现在我们知道了的目标,让我们开始进入正题吧。Declarative Authorization可以从GemCutter以gem的形式获得,所以第一步我们需要在应用程序的 /config/environment.rb 文件中加入一个的引用以获取它。

config.gem "declarative_authorization", :source => "http://gemcutter.org"

然后,为了确保gem被安装,我们要运行

sudo rake gems:install

 我们为应用程序定义的角色的权限将被定义在/config目录下的名为authorization_rules.rb的新文件中。Declarative Authorization提供它自己的DSL来定义角色和权限,这里我们就用它来定义admin用户的权限。

authorization do
  role :admin do
    has_permission_on [:articles, :comments], :to => [:index, :show, :new, :create, :edit, :update, :destroy]
  end
end

角色被定义在一个authorization代码块里。每个角色有一个名称,同时接受一个代码块,在里面列出了角色的权限。 我们使用has_permission_on方法来定义权限,它接受两个参数,第一个是角色能够访问的模型列表,后跟一个:to参数,表明在这些模型上允许的action清单。

 我们将在稍候定义其它的角色并且测试我们到目前为止的成果。但在前进之前,我们需要修改另一个地方,因为declarative authorization不知道我们的角色是怎样被定义的。我们需要修改 User模型,增加一个role_symbols方法,以符号数组的形式返回用户所属的角色列表。举例来说,假设我们的 User模型有一个admin属性,我们就可以这样写这个方法:如果用户是一个admin,那么他就属于:admin角色。

def role_symbols
  [:admin] if admin?
end

 我们的应用程序的特别之处在于UserRole之间是多对多的关系,并且用户的角色是在他们注册时通过我们早先看到的复选框指定的。所以在role_symbols 中我们要把当前用户的角色转换为符号数组,像这样做:

def role_symbols
  roles.map do |role|
    role.name.underscore.to_sym
  end
end

Declarative authorization现在能够将一个用户所属的角色映射到我们定义在authorization_rules文件中的角色上了。

我们还有几个步骤要做。一是在我们的ApplicationController里创建一个before_filter来告诉declarative authorization当前用户是哪个。

class ApplicationController < ActionController::Base
  helper :all
  protect_from_forgery
  before_filter { |c| Authorization.current_user = c.current_user }
end

 最后我们必须对每个控制器增加before_filter 来限制访问。我们通过调用由declarative authorization提供的名为filter_resource_access的方法来做这件事。这个方法在给 RESTful风格的控制器增加前置过滤器的同时还做了另外一件事情:当filter_resource_access被调用时会加载单个资源模型以便它能检查对此模型的访问。这意味着在show, new, create, edit, updatedelete 这些action中我们可以移除加载模型的代码行。我们必须在应用程序中的ArticlesControllerCommentsController里都这么做。如果你有任何控制器没有遵循RESTful惯例,文档7中包含了怎样处理的细节。

测试我们的成果

至此我们已经做了相当多的工作,终于到了能测试我们目前所取得的成果的阶段了。如果我们作为一个admin角色的用户登录,我们可以查看所有文章,并且编辑和删除文章与评论的链接是可见的。目前为止一切顺利,但当我们注销时,我们看到了一些意料之外的东西:

The error page shown when trying to access an unauthorized action.

 发生这种情况是因为应用程序被重定向到了主页,即文章索引页,当我们注销后,作为guest我们没有权限查看这个action。为了修复这个错误,我们需要在authorization_rules文件中为guest增加权限。guest角色是一个为尚未注册或登录的,或者没有指定角色的用户保留的特殊角色。

 在guest角色中我们赋予来访者的权限使他们可以访问文章的index和show action以及评论的new和create action。做了这些更改后,当我们刷新页面时,我们又可以看到文章列表了。

We can now see the home page again.

隐藏禁止访问的Action的链接

 虽然guest用户不能编辑和删除评论,但这些action的链接还显示在那里。因此我们下一步要做的是移除角色中的用户没有权限执行的action所对应的链接。为此我们必须修改视图代码,Declarative authorization 提供了一个有用的方法permitted_to? 来帮助我们做这件事。

 在 ArticleController的 show action 对应的视图代码中,我们将使用permitted_to?来隐藏编辑和删除文章的链接,除非当前用户有相应的权限。

<p>
  <% if permitted_to? :edit, @article %>
    <%= link_to "Edit", edit_article_path(@article) %> |
  <% end %>

  <% if permitted_to? :destroy, @article %>
    <%= link_to "Destroy", @article, :method => :delete, :confirm => "Are you sure?" %> |
  <% end %>

  <%= link_to "Back to Articles", articles_path %>
</p>

 permitted_to?方法接受两个参数:一个action 和 一个model的名称。在同一个文件更下面一点的地方,我们将做类似的修改使得除非当前用户能够编辑或删除评论,否则隐藏这些链接。

<p>
  <% if permitted_to? :edit, comment %>
    <%= link_to "Edit", edit_comment_path(comment) %> |
  <% end %>

  <% if permitted_to? :destroy, comment %>
    <%= link_to "Destroy", comment, :method => :delete, :confirm => "Are you sure?" %>
  <% end %>

</p>

我们还必须在index视图中做出类似修改来隐藏新建文章的链接。

<% if permitted_to? :create, Article.new %>
  <p><%= link_to "New Article", new_article_path %></p>
<% end %>

注意,由于在index action中没有文章的实例对象,我们使用了Article.new来代替。

现在当我们刷新面页时,除非用户有相应的权限,否则编辑和删除的链接将被隐藏起来。

The edit and destroy links are now hidden.

增加更多的角色和规则

现在万事俱备,我们只需要修改authorization_rules 文件来更改网站其他部分的访问控制。它提供的DSL允许我们设置一些相当高级的规则。例如,如果我们想允许没有指定角色的登录用户能修改他们自己的评论,我们可以给:guest角色增加下列规则:

role :guest do
  has_permission_on :articles, :to => [:index, :show]
  has_permission_on :comments, :to => [:new, :create]
  has_permission_on :comments, :to => [:edit, :update] do
    if_attribute :user => is { user }
  end
end

 Declarative authorization  提供了一个if_attribute方法,它能够为权限增加条件。在上面的规则中,权限只有在 :user 属性,即评论所属的用户就是当前登录用户的情况下,才会被授予。

现在如果我们以一个没有角色的用户登录并给一篇文章添加一条评论后,我们将看到在那条评论上有一个"edit"链接,但在其他人创建的评论上则没有。

The guest user can now edit the comments they have made.

 为了实现这个功能我们所做的一切就只要在authorization_rules文件中做一处修改,这显示了创建规则是多么简单。

为其它角色增加权限

我们为我们的应用程序创建了3个角色,但目前为止我们只给其中的一个定义了权限(除了guest角色之外)。现在我们将给其他角色增加权限。

role :moderator do
  includes :guest
  has_permission_on :comments, :to => [:edit,:update]
end

role :author do
  includes :guest
  has_permission_on :articles, :to => [:new, :create]
  has_permission_on :articles, :to => [:edit, :update] do
    if_attribute :user => is { user }
  end
end

注意我们在这里使用了includes方法来给一个角色授予来自另一个角色的权限。任何一个属于moderator角色的用户可以做guest用户能够做的任何事情并且可以编辑或更新任何一个人的评论。类似的,authors可以做guest能做所有事情而且还可以创建文章和更新任何一篇他自己写的文章。

更友好的错误消息

 在结束本集前我们将给应用做最后一个修改。如果某些正在查看我们网站人试图访问他们不能使用的action,他们将看到一个最简单的错误页面,上面有一条消息告知他们没有被允许那样做。

我们可以通过修改 ApplicationController来改进它。如果我们增加一个名为permission_denied的方法,它会在试图调用未经授权的action时被执行。

def permission_denied
  flash[:error] = "Sorry, you not allowed to access that page."

  redirect_to root_url
end

 现在当我们试图访问一个未被允许的action时,我们将被重定向到主页并显示一个更友好的错误信息。

We now see a custom error when trying to access an unauthorized action.

本集到此为止。 Declarative authorization 为Rails应用中的授权部分提供了一个简洁有力的解决方法,我们实际上仅是介绍了一点皮毛。如果您想深入研究的话,文档页提供了更多的细节,值得一读。