Rails是否带有“未授权”异常?

40

我正在编写一个应用程序,使用纯粹的 Ruby 对象(POROs)将授权逻辑抽象出控制器。

目前,我有一个名为NotAuthorized的自定义异常类,在控制器级别上使用rescue_from对其进行捕获,但我很好奇: Rails 4中是否已经提供了表示未经授权的操作的异常?通过实现此异常,我是否正在重新发明轮子?

澄清: raise AuthorizationException并不会在任何控制器内部发生,它会在完全解耦的 PORO 控制器外部发生。这个对象对 HTTP、路由或控制器一无所知。


HTTP错误代码401表示未经授权,您可以告诉Rails返回401状态代码,并呈现任何您想要的视图。 - Mohammad AbuShady
1
@MohammadAbuShady - 我认为他正在寻找类似于 raise ActionController::RoutingError.new('Not Found') 的东西,它会自动强制应用程序呈现404而不需要任何救援语句。 - BroiSatse
2
我通常只使用devise + cancan,这是一种很好的身份验证和授权组合。 - Mohammad AbuShady
3
同意 @MohammadAbuShady 的观点,但如果你选择这条路,请确保使用 CanCanCan,因为 CanCan 已经被废弃了 :)! - craig.kaminsky
我不知道,我会检查,谢谢你提供的信息。 - Mohammad AbuShady
2个回答

38

Rails似乎没有将异常映射到:unauthorized

默认映射在activerecord/lib/active_record/railtie.rb中定义:

config.action_dispatch.rescue_responses.merge!(
  'ActiveRecord::RecordNotFound'   => :not_found,
  'ActiveRecord::StaleObjectError' => :conflict,
  'ActiveRecord::RecordInvalid'    => :unprocessable_entity,
  'ActiveRecord::RecordNotSaved'   => :unprocessable_entity
)

并且在 actionpack/lib/action_dispatch/middleware/exception_wrapper.rb 中:

@@rescue_responses.merge!(
  'ActionController::RoutingError'             => :not_found,
  'AbstractController::ActionNotFound'         => :not_found,
  'ActionController::MethodNotAllowed'         => :method_not_allowed,
  'ActionController::UnknownHttpMethod'        => :method_not_allowed,
  'ActionController::NotImplemented'           => :not_implemented,
  'ActionController::UnknownFormat'            => :not_acceptable,
  'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
  'ActionDispatch::ParamsParser::ParseError'   => :bad_request,
  'ActionController::BadRequest'               => :bad_request,
  'ActionController::ParameterMissing'         => :bad_request
)

您可以从应用程序配置(或自定义Railtie)中添加自定义异常:

Your::Application.configure do

  config.action_dispatch.rescue_responses.merge!(
    'AuthorizationException' => :unauthorized
  )

  # ...

end

或者直接使用rescue_from


1
感谢您回答我的问题。我很好奇在使用Mongoid作为ORM时如何映射异常。 - Rick
1
@Stefan的链接引用了master中特定的行号,该行号会随时间变化而改变。这里是一个更加“永久”的代码链接:https://github.com/mongoid/mongoid/blob/v4.0.2/lib/mongoid/railtie.rb#L24 - Luke Griffiths

30

我猜Rails没有引入这个异常的原因是因为授权和认证不是Rails的本机行为(当然,不考虑基本身份验证)。

通常这些是其他库的职责 Devise 用于未经身份验证; Pundit, Dude Policy, CanCanCan, Rollify 用于未经授权)。实际上,我认为将 ActionController 扩展为自定义异常如 ActionController::NotAuthorized 可能是一件坏事(因为就像我说的那样,它不是其职责)。

因此,我通常解决这个问题的方法是在 ApplicationController 上引入自定义异常。

class ApplicationController  < ActionController::Base
  NotAuthorized = Class.new(StandardError)
  # ...or if you really want it to be ActionController
  # NotAuthorized = Class.new(ActionController::RoutingError)

  rescue_from ActiveRecord::RecordNotFound do |exception|
    render_error_page(status: 404, text: 'Not found')
  end

  rescue_from ApplicationController::NotAuthorized do |exception|
    render_error_page(status: 403, text: 'Forbidden')
  end

  private

  def render_error_page(status:, text:, template: 'errors/routing')
    respond_to do |format|
      format.json { render json: {errors: [message: "#{status} #{text}"]}, status: status }
      format.html { render template: template, status: status, layout: false }
      format.any  { head status }
    end
  end
end

因此,在我的控制器中,我可以这样做。
class MyStuff < ApplicationController
  def index
    if current_user.admin?
      # ....
    else 
      raise ApplicationController::NotAuthorized
    end
  end
end

这清楚地定义了你期望引发和捕获异常的层是你的应用程序层,而不是第三方库。
问题在于库可能会发生变化(是的,这也包括Rails),在第三方库类上定义异常并在应用程序层中进行救援真的很危险,因为如果异常类的含义发生变化,它会破坏你的rescue_from
你可以阅读许多文章,人们警告Rails raise-rescue_from成为现代goto(现在在某些专家中被认为是反模式),在某种程度上是正确的,但只有当你救援你无法完全控制的异常时才是如此!!
这意味着第三方异常(包括Devise和Rails到一定程度)。如果你在应用程序中定义异常类,你就不依赖第三方库,你就有完全的控制权,你可以rescue_from而不会成为反模式。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接