跳到內容 跳到搜尋

HTTP Token 驗證

簡單 Token 範例

class PostsController < ApplicationController
  TOKEN = "secret"

  before_action :authenticate, except: [ :index ]

  def index
    render plain: "Everyone can see me!"
  end

  def edit
    render plain: "I'm only accessible if you know the password"
  end

  private
    def authenticate
      authenticate_or_request_with_http_token do |token, options|
        # Compare the tokens in a time-constant manner, to mitigate
        # timing attacks.
        ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
      end
    end
end

以下是更進階的 Token 範例,其中只有 Atom feed 和 XML API 受到 HTTP token 驗證保護。一般 HTML 介面則由 session 方法保護。

class ApplicationController < ActionController::Base
  before_action :set_account, :authenticate

  private
    def set_account
      @account = Account.find_by(url_name: request.subdomains.first)
    end

    def authenticate
      case request.format
      when Mime[:xml], Mime[:atom]
        if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) }
          @current_user = user
        else
          request_http_token_authentication
        end
      else
        if session_authenticated?
          @current_user = @account.users.find(session[:authenticated][:user_id])
        else
          redirect_to(login_url) and return false
        end
      end
    end
end

您可以在整合測試中執行以下操作

def test_access_granted_from_xml
  authorization = ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)

  get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }

  assert_equal 200, status
end

在共用主機上,Apache 有時候不會將驗證標頭傳遞給 FCGI 個體。如果您的環境符合此說明,而且無法驗證,請在您的 Apache 設定中嘗試此規則

RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
名稱空間
方法
A
E
P
R
T

常數

AUTHN_PAIR_DELIMITERS = /(?:,|;|\t)/
 
TOKEN_KEY = "token="
 
TOKEN_REGEX = /^(Token|Bearer)\s+/
 

實例公開方法

authenticate(controller, &login_procedure)

如果 token Authorization 標頭存在,則以目前的 token 和選項呼叫登入程序。

如果找到 token,則傳回 login_procedure 的傳回值。如果找不到 token,則傳回 nil

參數

  • controller - 目前要求的 ActionController::Base 個體。

  • login_procedure - 如果有 token,則呼叫的程序。程序應使用兩個引數

    authenticate(controller) { |token, options| ... }
    
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 472
def authenticate(controller, &login_procedure)
  token, options = token_and_options(controller.request)
  unless token.blank?
    login_procedure.call(token, options)
  end
end

authentication_request(controller, realm, message = nil)

設定 WWW-Authenticate 標頭,讓客戶端知道需要 token。

不傳回任何內容。

參數

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 555
def authentication_request(controller, realm, message = nil)
  message ||= "HTTP Token: Access denied.\n"
  controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"', "")}")
  controller.__send__ :render, plain: message, status: :unauthorized
end

encode_credentials(token, options = {})

將傳入的 token 和選項編碼成 Authorization 標頭值。

傳回 String

參數

  • token - String token。

  • options - 選項的選擇性 Hash

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 539
def encode_credentials(token, options = {})
  values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value|
    "#{key}=#{value.to_s.inspect}"
  end
  "Token #{values * ", "}"
end

params_array_from(raw_params)

取得 raw_params 並將其轉換成參數陣列。

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 507
def params_array_from(raw_params)
  raw_params.map { |param| param.split %r/=(.+)?/ }
end

raw_params(auth)

這個方法會接收授權主體並將其關鍵值組依據在 AUTHN_PAIR_DELIMITERS 所定義的標準分隔符號 :;\t 來進行切割。

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 519
def raw_params(auth)
  _raw_params = auth.sub(TOKEN_REGEX, "").split(AUTHN_PAIR_DELIMITERS).map(&:strip)
  _raw_params.reject!(&:empty?)

  if !_raw_params.first&.start_with?(TOKEN_KEY)
    _raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}"
  end

  _raw_params
end

rewrite_param_values(array_params)

這會移除包裹值的 " 字元。

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 512
def rewrite_param_values(array_params)
  array_params.each { |param| (param[1] || +"").gsub! %r/^"|"$/, "" }
end

token_and_options(request)

從授權標頭中分析出 token 和選項。授權標頭的值預計會以 "Token""Bearer" 為前綴。標頭若長的像這樣

Authorization: Token token="abc", nonce="def"

那麼回傳的 token 會是 "abc" 而選項會是 {nonce: "def}"}

若存在 token,則回傳 [字串,雜湊]陣列。若找不到 token 則回傳 nil

參數

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 494
def token_and_options(request)
  authorization_request = request.authorization.to_s
  if authorization_request[TOKEN_REGEX]
    params = token_params_from authorization_request
    [params.shift[1], Hash[params].with_indifferent_access]
  end
end

token_params_from(auth)

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 502
def token_params_from(auth)
  rewrite_param_values params_array_from raw_params auth
end