在 Rails 应用程序中,我的应用程序控制器中有两个方法。其中一个对用户进行身份验证(即检查用户是否已登录,如果没有,则重定向他们登录)。另一个执行该任务的前半部分(即检查用户是否已登录)。
检查完成后,如果用户已登录,这两种方法都应该使用 User.find
为当前登录的用户的用户对象设置一个 @current_user
变量。
前者:
protected
def authenticate_user
if session[:user_id]
# set current user object to @current_user object variable
@current_user = User.find session[:user_id]
return true
else
flash[:notice] = "You must log in first."
flash[:color] = "invalid"
redirect_to(:controller => 'sessions', :action => 'login')
return false
end
end
后者:
def check_login_status
if session[:user_id]
@current_user = User.find session[:user_id]
return true
end
end
如您所见,每个方法的前半部分的逻辑是相同的。但是,authenticate_user
正确设置了@current_user
变量; check_login_status
根本没有设置它(例如,布局文件中的检查显示@current_user.nil? == true
.
这是布局文件的相关部分:
<% if not @current_user.nil? %>
Logged in as <%= @current_user.username %> —
<a href="/logout">log out</a> —
<a href="/dashboard">dashboard</a> —
<a href="/contacts">contacts</a> —
<a href="/help">help</a>
<% if @current_user.is_admin %>
— <a href="/admin">admin</a>
<% end %>
<% else %>
<a href="/login">log in</a> —
<a href="/sign-up">sign up</a> —
<a href="/help">help</a>
<% end %>
我看到显示的第二组链接,表明我已注销。
那么,为什么后者没有正确设置@current_user
变量呢?它与protected
标记有什么关系吗(尽管我不知何故对此表示怀疑)?
说最可能的解释是根本没有调用check_login_status
,因为两者在逻辑上是等价的。但是,两者都复制相同的身份验证逻辑!
如果您坚持重新发明授权轮(除非出于学习目的,否则不要这样做),则应避免将身份验证逻辑散布到控制器和视图中。
而是使用帮助程序模块来创建用于身份验证的简单 API。此模块应该是应用中唯一知道用户在会话中的存储方式的部分:
module AuthorizationHelper
def current_user
return nil unless session[:user_id]
# conditional assignment so DB is only queried once!
@current_user ||= User.find(session[:user_id])
end
def sign_in!(user)
reset_session
session[:user_id] = user.id
@current_user = user
end
def sign_out!(user)
reset_session
@current_user = nil
end
def signed_in?
current_user.present?
end
end
现在我们只在 ApplicationController
中包含帮助程序。
class ApplicationController
include AuthorizationHelper
# ...
end
我们还希望以可重复使用和可扩展的方式确保授权。执行此操作的一个好方法是引发异常并使用 rescue_from
进行缓存。
让我们创建我们自己的错误类:
class User < ActiveRecord::Base
class AuthorizationError < StandardError; end
end
让我们添加一个授权方法:
module authorizationHelper
# ..
def authorize!
raise User::AuthorizationError unless signed_in?
end
end
现在我们可以在控制器中使用它:
class ThingsController < ApplicationController
before_action :authorize!
end
但是它不是很有用,因为它只会导致应用程序崩溃!让我们拯救异常:
class ApplicationController
include AuthorizationHelper
rescue_from User::AuthorizationError, with: :deny_access
def deny_access
redirect_to(controller: 'sessions', action: 'login') and return
end
end