蓝天,小湖,湖水中一方小筑

Use Different Auth Method In Rails API Controller

开发的时候碰到一个问题,Rails的controller是用devise来提供认证的,如果用户在访问时没有有效的cookie就会被转到登录界面。但是在API的时候不能用cookie,所以需要分开做验证。

基本想法是对于网页端的请求,继续使用devise进行认证,而对于某些同时提供API接口的Action,则使用token来验证。除此之外,对于POST请求,还需要跳过CSRF token的检查。基本上代码就是这样的(为了测试方便,使用了自己定义的before_action ,同时设置所有的检查都基于请求中的 token 字段。)

class WelcomeController < ApplicationController
  before_action :auth_user!, only: [:foo], unless: -> { request.format.json? }
  before_action :auth_by_token!, if: -> { request.format.json? }

  def foo
    render json: params
  end

  def bar
    render json: params
  end

  private

  def auth_user!
    head :forbidden if params[:token] != '24'
  end

  def auth_by_token!
    head :forbidden if params[:token] != '42'
  end
end

此时,如果使用浏览器访问 foo , request.formattext/html ,此时就会调用 auth_user! ,而访问 bar 时,即使是使用浏览器,也不会调用 auth_user! 。而对于API请求,统一都会调用 auth_by_token! 进行邓处理。

下面是一个简单的测试用例

require "rails_helper"

RSpec.describe WelcomeController, :type => :controller do
  describe 'GET #foo.html' do
    it 'responds forbidden if not given valid token' do
      get :foo
      expect(response).to have_http_status(:forbidden)
    end

    it 'responds forbidden if given wrong token' do
      get :foo, {token: 42}
      expect(response).to have_http_status(:forbidden)
    end

    # auth_user! is invoked
    it 'responds success if given correct token' do
      get :foo, {token: 24}
      expect(response).to have_http_status(:ok)
    end
  end

  describe 'GET #foo.json' do
    before :each do
      request.env["HTTP_ACCEPT"] = 'application/json'
    end

    it 'responds forbidden if not given valid token' do
      get :foo
      expect(response).to have_http_status(:forbidden)
    end

    it 'responds forbidden if given wrong token' do
      get :foo, {token: 24}
      expect(response).to have_http_status(:forbidden)
    end

    # auth_by_token! is invoked
    it 'responds success if given correct token' do
      get :foo, {token: 42}
      expect(response).to have_http_status(:ok)
    end
  end
end

注意

在老版本的Rails上,有一个bug#9703,就是它的判断会把 if/unless 和 only/except 分开处理。在这种情况下需要根据 此条评论 中的内容对代码做相应改动。