问题:"锁定"Rails Web应用程序,以便用户必须输入凭据才能解锁它(但这样用户仍然登录到应用程序并且根本无法使用该应用程序而不解锁它(?
我在做什么
我正在构建一个任务管理应用程序,其行为类似于供多个用户使用的自助服务终端。用户可以使用其电子邮件地址和密码(完整凭据(登录应用程序,但一旦他们进入,他们可以通过从列表中选择其姓名并输入四位数 PIN(简单凭据(来"交换"用户。
为什么有两种类型的凭据?
这是一个多租户应用,用户使用"完整凭据"只需进入应用并选择租户即可。一旦他们进入,随着新员工轮班和其他员工离开,他们会经常更换,因为多个员工将共享一个工作站。
我没有强迫用户重复输入他们的"完整"凭据(通过执行完全注销/登录(,而是创建了一个名称/PIN 凭据集,他们可以用来轻松交换。使用完整凭据登录并选择租户后,应用会提供用户名的自动完成列表,因此很容易从列表中选择一个人的姓名,输入 PIN 并交换用户。
请注意,此"交换"功能已经构建并正常工作。
为什么我需要锁定应用程序?
当用户登录到应用和租户时,他们可能需要离开应用才能执行其他工作。如果没有锁定屏幕,他们只有两个选择:完全注销,以便下一个用户必须输入"完整"凭据才能使用该应用程序,或者只是让他们的帐户登录,让任何人搞砸。
我想允许他们在离开时单击按钮以"锁定"应用程序,以便用户可以将应用程序"登录",而不允许其他用户访问其他人的帐户。一旦他们单击"锁定",他们所能做的就是输入名称/PIN以解锁它,或单击"注销"以完全注销。
下面是一个典型的用例
:- 弗兰克使用他的电子邮件地址和密码登录应用程序。
- 他完成了一些任务。
- Jane 想要使用该应用程序,因此她通过从列表中选择她的名字并输入她的四位数 PIN 来"交换"。
- 现在,Jane 是登录用户,这意味着她可以完成任务、编辑个人资料和其他活动。
- Jane 完成了一些任务,需要去商店做其他事情,所以她点击了屏幕上的"锁定"按钮。
- 应用现已锁定,需要身份验证才能使用(名称和 PIN(,或者用户可以单击"注销"以完全退出应用。屏幕只是简单地说"应用程序已锁定。输入您的姓名和 PIN 码以解锁它。
我所理解的选项
似乎有两种方法可以去这里:
- 某种形式的JavaScript
- 使用会话变量或 cookie 的某种控制器逻辑
JavaScript 让我感到紧张,因为它很容易被黑客入侵。我想确保该应用程序实际上是安全的。
所以我一直在搞乱使用会话变量来做到这一点的方法。这是我一直在尝试的:
- 用户已通过特定租户的应用身份验证
- 用户点击"锁定"按钮
- 在会话控制器中调用"lock_ui"操作
- 该操作设置了一些会话变量(会话[:锁定]、会话[:locked_by]、会话[:locked_at](
- 然后,应用程序始终重定向到会话#锁定,其中显示"这已锁定。输入名称和 PIN 以解锁"表单,除非它已解锁或用户单击"注销"。
- 用户输入有效名称/PIN 后,这些会话变量将被删除,应用将正常运行。
关于我如何做到这一点以确保它安全的任何建议?
我挂断的事情:
- 我是否应该使用某种应用程序控制器过滤器(之前或周围(来检查应用程序是否已锁定?如果应用被锁定,应用程序控制器会怎么做?(它是调用其他操作,直接呈现"锁定"页面,还是其他操作?
- 或者这是应该在视图层中处理的事情吗?例如,我可以更新我的应用程序布局,使其具有"如果locked_ui渲染'锁定'屏幕,否则会产生"。
关于我目前如何设置的一些说明:
- 这是一个使用 Ruby 1.9.3 托管在 heroku 上的 Rails 4.1.0 应用程序。
- 我正在使用CanCan进行授权
- 我正在使用带有范围的多租户(并且我没有使用子域(
- 每个用户只有一个帐户,该帐户可能有权访问多个租户
- 我看过JQuery BlockUI,但它似乎更像是一个虚荣的东西,而不是一个功能性的安全设备。
这是我最终构建的内容。
回答我自己的问题:
我是否应该使用某种应用程序控制器过滤器(之前或周围(来检查应用程序是否已锁定?我在应用程序控制器中使用before_filter来检查 UI 是否已锁定。如果它被锁定,我会重定向到"锁定"屏幕。然后,我在会话控制器中的相关操作中添加了一个skip_before_filter(当用户导航到"锁定"屏幕以及会话控制器中的相关锁定/解锁操作时,基本上忽略了该before_filter。
或者这是应该在视图层中处理的事情吗?在视图层中,我主要只需要创建实际的"锁定"屏幕(用户在其中输入凭据以解锁应用程序(,并且我确保在UI锁定时隐藏导航元素(因此用户不会对为什么他们单击"编辑配置文件"而不离开锁定屏幕感到困惑(。
我正在使用 cookie 来记录 UI 的当前状态(锁定与否(。以下是一些代码片段:
application_controller.rb
before_filter :confirm_unlocked_ui
private
def confirm_unlocked_ui
if signed_in? && locked_ui?
redirect_to locked_path
end
end
sessions_helper.rb
def locked_ui?
session[:locked] == '1'
end
def lock_ui
session[:locked] = '1'
session[:locked_by] = current_user.id
session[:locked_at] = Time.zone.now
end
def unlock_ui
session.delete(:locked)
session.delete(:locked_by)
session.delete(:locked_at)
end
sessions_controller.rb
def lock
session[:return_to] = params[:return_to] if params[:return_to]
lock_ui
redirect_to locked_path
end
def locked
#essentially just a view
end
def unlock
user = User.find_by_id(params[:session][:unlock_user_id])
if user && user.authenticate_with_pin(params[:session][:pin])
cookies[:auth_token] = user.auth_token
unlock_ui
redirect_back_or root_path
else
flash.now[:error] = "Invalid PIN."
render 'locked'
end
end
def destroy
sign_out
unlock_ui
redirect_to root_path
end
我真的不知道这个解决方案有多"好",我在这里发布它的一个原因是看看其他人是否想出更好的想法或看到我如何做到这一点的任何问题。
如果您尝试这样做,您可能需要考虑一些更微妙的事情:
- 我
- 正在使用CanCan进行授权,我在这里省略了它。如果您有基于角色的授权或类似的东西,请务必检查这是否适用于每个角色。
- 请注意,如果用户不想"解锁",我允许用户"注销"。我使用会话控制器的"销毁"操作执行此操作,并确保在注销后重定向它们之前"unlock_ui"。如果我不这样做,那么下次他们从该浏览器登录时,该应用程序仍将被"锁定"。
- locked_path 是响应 get 请求的 Sessions#lock 的别名(基本上只是一个视图/表单(。
- 会话#锁定也会响应获取请求。
- 会话#解锁是从"锁定"视图提交表单时所调用的内容。