我正在编写一个应用程序,它使用普通的旧式 Ruby 对象 (PORO) 从控制器中抽象出授权逻辑。
目前,我有一个名为
NotAuthorized
的自定义异常类,我在控制器级别 rescue_from
,但我很想知道:Rails 4 是否已经带有一个异常来指示操作未经授权? 我我通过实现这个异常来重新发明轮子?
澄清:
raise AuthorizationException
不会发生在控制器内部的任何地方,它发生在控制器外部完全解耦的 PORO 内部。该对象不了解 HTTP、路由或控制器。
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
。
我猜测 Rails 没有引入此异常的原因是因为授权和身份验证不是 Rails 本机行为(当然不考虑 basicauth)。
通常这些是其他库的职责 Devise for NotAuthenticated; Pundit、Dude Policy、CanCanCan、Rollify for NotAuthorized)我实际上认为用
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
这清楚地定义了您期望引发和捕获此异常的层是您的应用程序层,而不是第 3 方库。
问题是,库可以更改(是的,这也意味着 Rails),在第 3 方库类上定义异常并在应用程序层中拯救它们确实很危险,就好像异常类的含义发生变化一样,它会阻止您的
rescue_from
你可以读到很多文章,人们都在警告 Rails
raise
- rescue_from
是现代的 goto
(现在考虑一些专家的反模式),在某种程度上这是正确的,但前提是你要拯救您无法完全控制的异常!!
这意味着第 3 方例外(在某种程度上包括 Devise 和 Rails)。如果您在应用程序中定义异常类,则您不会中继第 3 方库 => 您拥有完全控制权 => 您可以
rescue_from
而不会成为反模式。