这是Rails的JSON API认证(使用设计)安全吗? [英] Is this Rails JSON authentication API (using Devise) secure?

查看:168
本文介绍了这是Rails的JSON API认证(使用设计)安全吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的Rails应用程序使用设计进行验证。它有一个妹妹iOS应用,用户可以使用他们使用的Web应用程序相同的凭据登录到iOS应用。所以,我需要某种形式的API进行身份验证。

在这里类似的问题很多指向的本教程,但它似乎是外的日期,因为 token_authenticatable 模块已被从设计删除,某些行抛出错误。 (我使用的设计3.2.2),我试图根据该教程推出自己的(和这个 ),但我在这没有100%的信心 - 我觉得有可能是一些我误解或错过

首先,以下这个主旨的意见,我添加了一个 authentication_token 文本属性到我的用户表,以下为 user.rb

  before_save:ensure_authentication_token高清ensure_authentication_token
  如果authentication_token.blank?
    self.authentication_token = generate_authentication_token
  结束
结束私人的  高清generate_authentication_token
    环办
      令牌= Devise.friendly_token
      打破令牌除非User.find_by(authentication_token:令牌)
    结束
  结束

然后,我有以下控制器:

api_controller.rb

 类ApiController< ApplicationController中
  respond_to代码:JSON
  skip_before_filter:的authenticate_user!  保护  高清user_params
    PARAMS [:用户] .permit(:电子邮件,:密码:password_confirmation)
  结束
结束

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

API / sessions_controller.rb

 类API :: SessionsController<设计:: RegistrationsController
  prepend_before_filter:require_no_authentication,:只=> [:创建 ]  的before_filter:ensure_params_exist  respond_to代码:JSON  skip_before_filter:verify_authenticity_token  打造高清
    build_resource
    资源= User.find_for_database_authentication(
      电子邮件:PARAMS [:用户] [:邮箱]
    )
    返回invalid_login_attempt除非资源    如果resource.valid_password?(PARAMS [:用户] [:密码])
      sign_in(用户,资源)
      渲染JSON:{
        成功:真实,
        AUTH_TOKEN:resource.authentication_token,
        电子邮件:resource.email
      }
      返回
    结束
    invalid_login_attempt
  结束  DEF破坏
    SIGN_OUT(RESOURCE_NAME)
  结束  保护    高清ensure_params_exist
      返回除非PARAMS [:用户] .blank?
      渲染JSON:{
        成功:假,
        消息:缺少的用户参数
      },状态:422
    结束    高清invalid_login_attempt
      warden.custom_failure!
      渲染JSON:{
        成功:假,
        消息:错误与您的登录名或密码
      },状态:401
    结束
结束

API / registrations_controller.rb

 类API :: RegistrationsController< ApiController
  skip_before_filter:verify_authenticity_token  打造高清
    用户= User.new(user_params)
    如果user.save
      渲染(
        JSON:Jbuilder.en code做|百灵|
          j.success真
          j.email user.email
          j.auth_token user.authentication_token
        结束,
        状态:201
      )
      返回
    其他
      warden.custom_failure!
      渲染JSON:user.errors,状态:422
    结束
  结束
结束

而在的config / routes.rb中

 命名空间:API,默认值为:{格式:JSON}做
    devise_for:用户
  结束

我不喜欢深一点,我敢肯定有东西在这里,我的未来的自己会回顾和畏缩(通常有)。有些玄乎部分:

首先后,你会发现, API :: SessionsController 继承制定:: RegistrationsController ,而 API :: RegistrationsController ApiController (我也有一些其他的控制器,如 API :: EventsController< ApiController 这涉及更标准的REST的东西为我的其他车型不具备与设计多触点)这是一个pretty丑陋的安排,但我想不出让我的访问需要方法API :: RegistrationsController 的另一种方式。我联系到上面的教程有行包括制定::控制器:: InternalHelpers ,但此模块似乎已经在最近版本的设计被删除。

其次,我已经与行 skip_before_filter禁用CSRF保护:verify_authentication_token 。我有我的疑虑,这是否是一个好主意 -​​ 我看到了很多的<一个href=\"http://stackoverflow.com/questions/7600347/rails-api-design-without-disabling-csrf-protection\">conflicting或<一个href=\"http://stackoverflow.com/questions/11008469/are-json-web-services-vulnerable-to-csrf-attacks\">hard要了解有关JSON API的是否容易受到CSRF攻击建议 - 但补充说,行了,我能得到这该死的东西的工作的唯一办法<​​/ P>

第三,我要确保我的理解验证的工作一旦用户在已签署的。说我有一个API调用 GET / API /朋友返回当前用户的好友列表。据我了解,iOS应用必须得到用户的 authentication_token 从数据库中(这是每一个永远不会改变?用户一个固定值),然后提交与每一项要求,如沿参数 GET / API /朋友?authentication_token = abcdefgh1234 ,那么我的 API :: FriendsController 可以做类似 User.find_by(authentication_token:PARAMS [:authentication_token])来得到CURRENT_USER。难道真是这个简单的,还是我失去了一些东西?

因此​​,对于任何人谁是设法一路读到这个庞大问题的结束,感谢您的时间!总结:


  1. 这个登录系统安全吗?或有件事情我已经忽视或误解,例如当谈到CSRF攻击?

  2. 是我如何验证请求理解一旦用户在正确的签名吗?(见三......以上。)

  3. 有什么办法这个code,可清洗或取得更好?特别是具有一个控制器的设计难看从继承制定:: RegistrationsController ApiController 其他。

谢谢!


解决方案

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


  • 在有服务器端after_filter添加到您的会话控制器:

      after_filter:set_csrf_header,只有:[:新:创建]保护高清set_csrf_header
       response.headers ['X-CSRF令牌'] = form_authenticity_token
    结束

    这将产生一个道理,把它放在你的会话,并拷贝在选定的行动响应头。


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


    • 您的客户端需要扫描所有服务器响应此头并保留它,当它被传承下去。

        ...阿霍德响应对象
      //响应可以是NSURLResponse对象,因此转换:
      NSHTTPURLResponse * HTT presponse =(NSHTTPURLResponse *)响应;
      //抢令牌如果present,确保你有一个配置对象,将其存储在
      * NSString的记号= [[HTT presponse allHeaderFields] objectForKey:@X-CSRF令牌];
      如果(标记)
         [yourConfig setCsrfToken:令牌];


    • 最后,您的客户端需要此令牌添加到所有非GET要求它发出的:

        ...阿霍德您的请求对象
      如果(yourConfig.csrfToken&安培;&安培;![request.httpMethod isEqualToString:@GET])
        [申请的setValue:yourConfig.csrfToken forHTTPHeaderField:@X-CSRF令牌];



最后一块拼图是了解登录时制定,正在使用后两届会议/ CSRF令牌。一个登录流程是这样的:

  GET /用户/ sign_in  - &GT;
  //新动作被调用时,最初的标记设置
  //现在回调发送登录表单:
  POST /用户/ sign_in&lt;用户名,密码&GT; - &GT;
    //创建一个名为动作,令牌复位
    //登录成功后,会话和令牌将被替换
    //你可以发送验证请求,

My Rails app uses Devise for authentication. It has a sister iOS app, and users can log in to the iOS app using the same credentials that they use for the web app. So I need some kind of API for authentication.

Lots of similar questions on here point to this tutorial, but it seems to be out-of-date, as the token_authenticatable module has since been removed from Devise and some of the lines throw errors. (I'm using Devise 3.2.2.) I've attempted to roll my own based on that tutorial (and this one), but I'm not 100% confident in it - I feel like there may be something I've misunderstood or missed.

Firstly, following the advice of this gist, I added an authentication_token text attribute to my users table, and the following to 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

Then I have the following controllers:

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

(Note that my application_controller has the line 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

And in config/routes.rb:

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

I'm out of my depth a bit and I'm sure there's something here that my future self will look back on and cringe (there usually is). Some iffy parts:

Firstly, you'll notice that Api::SessionsController inherits from Devise::RegistrationsController whereas Api::RegistrationsController inherits from ApiController (I also have some other controllers such as Api::EventsController < ApiController which deal with more standard REST stuff for my other models and don't have much contact with Devise.) This is a pretty ugly arrangement, but I couldn't figure out another way of getting access the methods I need in Api::RegistrationsController. The tutorial I linked to above has the line include Devise::Controllers::InternalHelpers, but this module seems to have been removed in more recent versions of Devise.

Secondly, I've disabled CSRF protection with the line skip_before_filter :verify_authentication_token. I have my doubts about whether this is a good idea - I see a lot of conflicting or hard to understand advice about whether JSON APIs are vulnerable to CSRF attacks - but adding that line was the only way I could get the damn thing to work.

Thirdly, I want to make sure I understand how authentication works once a user has signed in. Say I have an API call GET /api/friends which returns a list of the current user's friends. As I understand it, the iOS app would have to get the user's authentication_token from the database (which is a fixed value for each user that never changes??), then submit it as a param along with every request, e.g. GET /api/friends?authentication_token=abcdefgh1234, then my Api::FriendsController could do something like User.find_by(authentication_token: params[:authentication_token]) to get the current_user. Is it really this simple, or am I missing something?

So for anyone who's managed to read all the way to the end of this mammoth question, thanks for your time! To summarise:

  1. Is this login system secure? Or is there something I've overlooked or misunderstood, e.g. when it comes to CSRF attacks?
  2. Is my understanding of how to authenticate requests once users are signed in correct? (See "thirdly..." above.)
  3. Is there any way this code can be cleaned up or made nicer? Particularly the ugly design of having one controller inherit from Devise::RegistrationsController and the others from ApiController.

Thanks!

解决方案

You don't want to disable CSRF, I have read that people think it doesn't apply to JSON APIs for some reason, but this is a misunderstanding. To keep it enabled, you want to make a few changes:

  • on there server side add a after_filter to your sessions controller:

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

    This will generate a token, put it in your session and copy it in the response header for selected actions.

  • client side (iOS) you need to make sure two things are in place.

    • your client needs to scan all server responses for this header and retain it when it is passed along.

      ... 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];
      

    • finally, your client needs to add this token to all 'non GET' requests it sends out:

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

Final piece of the puzzle is to understand that when logging in to devise, two subsequent sessions/csrf tokens are being used. A login flow would look like this:

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

这篇关于这是Rails的JSON API认证(使用设计)安全吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆