这个Rails JSON身份validationAPI(使用Devise)是否安全?

我的Rails应用程序使用Devise进行身份validation。 它有一个姊妹iOS应用程序,用户可以使用它们用于Web应用程序的相同凭据login到iOS应用程序。 所以我需要一些API来进行身份validation。

这里有很多类似的问题指向本教程 ,但似乎已经过时了,因为token_authenticatable模块已经从Devise中移除,并且一些行会抛出错误。 (我正在使用Devise 3.2.2)。我试图根据那个教程(和这个教程)推出自己的教程,但是我不是100%自信的 – 我觉得我可能会有一些东西误解或错过。

首先,遵循这个要点的build议,我添加了一个authentication_token文本属性到我的users表中,以及以下到user.rb

 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.find_by(authentication_token: token) end end 

然后我有以下控制器:

api_controller.rb

 class ApiController < ApplicationController respond_to :json skip_before_filter :authenticate_user! protected def user_params params[:user].permit(:email, :password, :password_confirmation) end end 

(请注意,我的application_controller有一行before_filter :authenticate_user!

API / sessions_controller.rb

 class Api::SessionsController < Devise::RegistrationsController prepend_before_filter :require_no_authentication, :only => [:create ] before_filter :ensure_params_exist respond_to :json skip_before_filter :verify_authenticity_token def create build_resource resource = User.find_for_database_authentication( email: params[:user][:email] ) return invalid_login_attempt unless resource if resource.valid_password?(params[:user][:password]) sign_in("user", resource) render json: { success: true, auth_token: resource.authentication_token, email: resource.email } return end invalid_login_attempt end def destroy sign_out(resource_name) end protected def ensure_params_exist return unless params[:user].blank? render json: { success: false, message: "missing user parameter" }, status: 422 end def invalid_login_attempt warden.custom_failure! render json: { success: false, message: "Error with your login or password" }, status: 401 end end 

API / registrations_controller.rb

 class Api::RegistrationsController < ApiController skip_before_filter :verify_authenticity_token def create user = User.new(user_params) if user.save render( json: Jbuilder.encode do |j| j.success true j.email user.email j.auth_token user.authentication_token end, status: 201 ) return else warden.custom_failure! render json: user.errors, status: 422 end end end 

config / routes.rb中

  namespace :api, defaults: { format: "json" } do devise_for :users end 

我已经深入了一点,我确信这里有一些事情,我的未来的自我会回头看看,并且常常会这样。 一些可能的部分:

首先 ,你会注意到Api::SessionsControllerinheritance自Devise::RegistrationsControllerApi::RegistrationsControllerinheritance自ApiController (我也有一些其他控制器,如Api::EventsController < ApiController ,它处理更多的标准REST的东西为我的模型和与Devise没有太多的联系。)这是一个非常丑陋的安排,但我不知道另一种方法来获取我需要在Api::RegistrationsController 。 我链接到上面的教程有include Devise::Controllers::InternalHelpers ,但这个模块似乎已被删除在最近版本的devise。

其次 ,我已经使用skip_before_filter :verify_authentication_token行禁用了CSRF保护。 我怀疑这是否是一个好主意 – 我看到很多矛盾或难以理解的关于JSON API是否容易受到CSRF攻击的build议 – 但是增加这条线是我可以让这个死的东西起作用的唯一途径。

第三 ,我想确保我了解身份validation如何工作,一旦用户login。说我有一个API调用GET /api/friends ,返回当前用户的朋友列表。 据我了解,iOS应用程序将不得不从数据库中获取用户的authentication_token (这是每个永不改变的用户的固定值),然后将其作为参数提交,例如GET /api/friends?authentication_token=abcdefgh1234 ,那么我的Api::FriendsController可以做一些像User.find_by(authentication_token: params[:authentication_token])来获取current_user。 这真的很简单,还是我错过了什么?

所以对于那些设法读到这个庞然大物的人来说,谢谢你的时间! 总结:

  1. 这个login系统是否安全? 还是有什么我忽略或误解,例如,当涉及CSRF攻击?
  2. 一旦用户login正确后,我是否了解如何validation请求? (请参阅上面的“第三个…”。)
  3. 有什么办法可以清理这个代码或更好的? 特别是有一个控制器从Devise::RegistrationsController和其他ApiControllerinheritance的丑陋的devise。

谢谢!

你不想禁用CSRF,我已经阅读了人们认为它不适用于JSON API的原因,但这是一个误解。 要保持启用状态,您需要进行一些更改:

  • 在那里服务器端添加一个after_filter到你的会议控制器:

     after_filter :set_csrf_header, only: [:new, :create] protected def set_csrf_header response.headers['X-CSRF-Token'] = form_authenticity_token end 

    这将生成一个令牌,将其放入会话中,并将其复制到选定操作的响应头中。

  • 客户端(iOS)你需要确保两件事情到位。

    • 您的客户端需要扫描此头的所有服务器响应,并在传递时保留它。

       ... get ahold of response object // response may be a NSURLResponse object, so convert: NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; // grab token if present, make sure you have a config object to store it in NSString *token = [[httpResponse allHeaderFields] objectForKey:@"X-CSRF-Token"]; if (token) [yourConfig setCsrfToken:token]; 
    • 最后,你的客户端需要将这个令牌添加到它发出的所有“非GET”请求中:

       ... get ahold of your request object if (yourConfig.csrfToken && ![request.httpMethod isEqualToString:@"GET"]) [request setValue:yourConfig.csrfToken forHTTPHeaderField:@"X-CSRF-Token"]; 

难题的最后一部分是要明白,logindevise时,会使用两个后续会话/ csrf令牌。 loginstream程如下所示:

 GET /users/sign_in -> // new action is called, initial token is set // now send login form on callback: POST /users/sign_in <username, password> -> // create action called, token is reset // when login is successful, session and token are replaced // and you can send authenticated requests 

你的例子似乎模仿了Devise博客的代码 – https://gist.github.com/josevalim/fb706b1e933ef01e4fb6

正如在这篇文章中提到的,你正在做类似于选项1,他们说这是不安全的选项。 我认为关键是您不希望每次保存用户时都重置身份validation令牌。 我认为这个标记应该被明确地创build(通过API中的某种TokenController),并且应该定期地过期。

你会注意到我说'我想',因为(据我所知)没有人有关于此的更多信息。

Web应用程序中十大最常见的漏洞logging在OWASP Top 10中 。 这个问题提到跨站点请求伪造(CSRF)保护被禁用, CSRF在OWASDP Top 10上 。 简而言之,攻击者使用CSRF来执行authentication用户的操作。 禁用CSRF保护将导致应用程序中的高风险漏洞,并破坏具有安全authentication系统的目的。 它可能是CSRF保护失败,因为客户端无法通过CSRF同步令牌。

阅读整个OWASP前10名,如果不这样做是非常危险的 。 密切关注中断身份validation和会话pipe理 ,同时查看会话pipe理备忘单 。