设计Json身份验证

时间:2014-12-17 02:38:09

标签: ajax ruby-on-rails-4 devise

我在Rails中编写应用程序的后端。当我在后端工作时,我需要给前端开发人员一个REST API来开始构建前端。最终,前端和后端将一起驻留在一个应用程序中,但现在它们是分开的。

暂时我在我的应用中启用了跨源资源共享,方法是在ApplicationController添加以下内容:

config.action_dispatch.default_headers.merge!({
                                                  'Access-Control-Allow-Origin' => '*',
                                                  'Access-Control-Request-Method' => '*'
                                              });

目前,我还通过在application.rb添加以下内容来关闭CSRF令牌:

skip_before_filter :verify_authenticity_token

我正在使用Devise来验证用户身份。为了使Devise能够处理JSON请求,我已经完成了以下操作:

devise.rb

config.navigational_formats = ['*/*', :html, :json]

routes.rb

devise_for :users, :controllers => {:omniauth_callbacks => "omniauth_callbacks", :sessions => 'sessions', :registrations => 'registrations' }

我的SessionsController

class SessionsController < Devise::SessionsController
  #todo had to do following to support logging in through ajax. need to add logic to send back error response when login fails.
  #todo see  http://stackoverflow.com/questions/5973327/using-devise-1-3-to-authenticate-json-login-requests/8402035#8402035 and
  #todo https://web.archive.org/web/20130928040249/http://jessehowarth.com/devise
  #todo see http://stackoverflow.com/questions/11277300/devise-failure-authentication-via-json-sends-back-html-instead-of-json
  def create
    respond_to do |format|
      format.html { super }
      format.json {
        resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#failure")
        sign_in(resource_name, resource)
        return render :json => {:success => true, :user => resource}
      }
    end
  end

  def destroy
    respond_to do |format|
      format.html { super }
      format.json {
        Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
        render :json => {}
      }
    end
  end

  def failure
    render :json => {:success => false, :errors => ["Login Failed"]}, :status => 422
  end
end

我有一个扩展的Devise的RegistrationsController以及routes.rb中的指示,但我没有在这里发布它的内容,因为我不认为这与这个问题有关。

通过上述设置,我可以使用用户[email]和用户[密码]参数向'/ users / sign_in'发送ajax请求,并让用户登录。响应如下所示:

{
 success: true 
 user: { 
  authentication_token: "SNa2kPqkm5ENsZMx7yEi"
  created_at: "2014-12-16T02:40:39.179Z"
  email: "xyz@xyz.com"
  id: 99999
  name: null
  provider: null
  uid: null
  updated_at: "2014-12-17T02:29:31.537Z"
 }
}

现在我如何使用sign_in响应中收到的authentication_token将请求发送到需要用户进行身份验证的其他控制器操作?我是否需要在请求标头中设置此标记?我无法找到有关如何使用此令牌的信息。请帮忙。

1 个答案:

答案 0 :(得分:0)

似乎按照这里的要点所述,答案是你向后端发送suer的电子邮件和authentictication_token以及每个请求。您可以选择在请求标头中发送它或仅作为参数发送它。您只需修改检查电子邮件和令牌的方法,并相应地在ApplicationController中签名用户。这是我的ApplicationController(我现在在请求中发送电子邮件和令牌作为参数):

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception
  #todo remove this once ui is integrated. following turns off the csrf token:
  skip_before_filter :verify_authenticity_token

  #todo begin code to support authentication using token
  # This is our new function that comes before Devise's one
  before_filter :authenticate_user_from_token!
  # This is Devise's authentication
  before_filter :authenticate_user!

  private

  def authenticate_user_from_token!
    user_email = params[:user_email].presence
    user       = user_email && User.find_by_email(user_email)

    # Notice how we use Devise.secure_compare to compare the token
    # in the database with the token given in the params, mitigating
    # timing attacks.
    if user && Devise.secure_compare(user.authentication_token, params[:user_token])
      sign_in user, store: false
    end
  end
  #todo end code to support authentication using token
end

我忘了在帖子中提到我已经添加了迁移以向用户模型添加authentication_token列。此外,我必须在User模型中添加以下内容(如gist中所述),以便每次创建/更新用户时都会生成身份验证令牌:

#todo begin code to support ajax authentication of users
  #todo see https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
  # You likely have this before callback set up for the token.
  before_save :ensure_authentication_token

  def ensure_authentication_token
    if authentication_token.blank?
      self.authentication_token = generate_authentication_token
    end
  end

  private

  def generate_authentication_token
    loop do
      token = Devise.friendly_token
      break token unless User.where(authentication_token: token).first
    end
  end
  #todo end code to support  ajax authentication of users