HTTP Digest
驗證
簡單 Digest
範例
require "openssl"
class PostsController < ApplicationController
REALM = "SuperSecret"
USERS = {"dhh" => "secret", #plain text password
"dap" => OpenSSL::Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
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_digest(REALM) do |username|
USERS[username]
end
end
end
附註
authenticate_or_request_with_http_digest
區塊必須傳回應使用者的密碼或 ha1 摘要雜湊,以便架構可以適當地雜湊以檢查使用者的憑證。傳回 nil
會導致驗證失敗。
儲存 ha1 雜湊 (MD5(使用者名稱:領域:密碼)) 比儲存純文字密碼更好。如果密碼檔案或資料庫遭到入侵,攻擊者將能夠使用 ha1 雜湊來驗證使用此 domain
的身分,但不會有使用者的密碼可在其他網站上嘗試使用。
在罕見情況下,網際網路伺服器或前端代理會在授權標頭到達您的應用程式之前移除它們。您可以透過記錄所有環境變數並從中查看 HTTP_AUTHORIZATION 來偵錯這個狀況。
- A
- D
- E
- H
- N
- O
- S
- V
執行個體公開方法
authenticate(request, realm, &password_procedure) 連結
對於有效回應傳回 true,否則傳回 false。
來源:顯示 | 在 GitHub 上
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 215 def authenticate(request, realm, &password_procedure) request.authorization && validate_digest_response(request, realm, &password_procedure) end
authentication_header(controller, realm) 連結
來源:顯示 | 在 GitHub 上
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 274 def authentication_header(controller, realm) secret_key = secret_token(controller.request) nonce = self.nonce(secret_key) opaque = opaque(secret_key) controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}") end
authentication_request(controller, realm, message = nil) 連結
來源:顯示 | 在 GitHub 上
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 281 def authentication_request(controller, realm, message = nil) message ||= "HTTP Digest: Access denied.\n" authentication_header(controller, realm) controller.status = 401 controller.response_body = message end
decode_credentials(header) 連結
來源:顯示 | 在 GitHub 上
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 267 def decode_credentials(header) ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, "").split(",").map do |pair| key, value = pair.split("=", 2) [key.strip, value.to_s.gsub(/^"|"$/, "").delete("'")] end] end
decode_credentials_header(request) 連結
來源:顯示 | 在 GitHub 上
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 263 def decode_credentials_header(request) decode_credentials(request.authorization) end
encode_credentials(http_method, credentials, password, password_is_ha1) 連結
來源:顯示 | 在 GitHub 上
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 258 def encode_credentials(http_method, credentials, password, password_is_ha1) credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1) "Digest " + credentials.sort_by { |x| x[0].to_s }.map { |v| "#{v[0]}='#{v[1]}'" }.join(", ") end
expected_response(http_method, uri, credentials, password, password_is_ha1 = true) 連結
傳回對使用已解碼的 credentials
以及預期的 password
對 uri
進行的 http_method
要求的預期回應。因為建議慣例是儲存 ha1 消化碼而非純文字密碼,所以選用參數 password_is_ha1
預設設定為 true
。
來源:顯示 | 在 GitHub 上
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 248 def expected_response(http_method, uri, credentials, password, password_is_ha1 = true) ha1 = password_is_ha1 ? password : ha1(credentials, password) ha2 = OpenSSL::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":")) OpenSSL::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":")) end
ha1(credentials, password) 連結
來源:顯示 | 在 GitHub 上
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 254 def ha1(credentials, password) OpenSSL::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":")) end
nonce(secret_key, time = Time.now) 連結
使用基於時間的 MD5 消化碼來產生僅能使用一次的值。
由伺服器指定,每次進行 401 回應時都應唯一產生的資料字串。建議這個字串是 Base64 或十六進位資料。特別是,由於字串是在標頭列中作為一個引號字串傳遞,因此不允許使用雙引號字元。
nonce 的內容取決於實作。實作的品質取決於良好的選擇。例如,nonce 可能建構為
time-stamp H(time-stamp ":" ETag ":" private-key)
的時間戳記的 Base64 編碼,其中時間戳記是伺服器產生的時間或其他不會重複的值,ETag 是與所要求實體相關的 HTTP ETag 標頭值,而私密金鑰是僅伺服器知道的資料。透過這個形式的 nonce,伺服器會在收到客戶端驗證標題後重新計算雜湊部分,並且如果它與該標題中的 nonce 不相符或時間戳記值不新近,即拒絕要求。透過這種方式,伺服器可以限制 nonce 有效的時間。包含 ETag 可防止對資源已更新版本的重播要求。(注意:在 nonce 中包含客戶端的 IP 位址似乎可讓伺服器有能力將 nonce 的重複使用限制給最初取得它的同一個客戶端。然而,這樣會破壞代理伺服器農場,其中來自單一使用者的要求通常會經過農場中的不同代理伺服器。此外,IP 位址變造也沒那麼難。)
實作可能會選擇不接受先前使用過的 nonce 或先前使用過的摘要,以防止重播攻擊。或者,實作可能會選擇對 POST、PUT 或 PATCH 要求使用一次性 nonce 或摘要,並對 GET 要求使用時間戳記。有關所涉問題的更多詳細資訊,請參閱此文件第 4 節。
用戶端無法理解隨機數。由Time
和 Time
與專案創建後產生的Rails
會話密鑰進行雜湊組成。確保時間無法由用戶端修改。
來源:顯示 | 在 GitHub 上
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 330 def nonce(secret_key, time = Time.now) t = time.to_i hashed = [t, secret_key] digest = OpenSSL::Digest::MD5.hexdigest(hashed.join(":")) ::Base64.strict_encode64("#{t}:#{digest}") end
opaque(secret_key) 連結
基於密鑰摘要的不透明
來源:顯示 | 在 GitHub 上
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 348 def opaque(secret_key) OpenSSL::Digest::MD5.hexdigest(secret_key) end
secret_token(request) 連結
來源:顯示 | 在 GitHub 上
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 288 def secret_token(request) key_generator = request.key_generator http_auth_salt = request.http_auth_salt key_generator.generate_key(http_auth_salt) end
validate_digest_response(request, realm, &password_procedure) 連結
要在請求憑證回應值與預期值相符的情況下,才傳回 false。首先嘗試將密碼設為 ha1 摘要密碼。如果失敗,再嘗試將其設為純文字密碼。
來源:顯示 | 在 GitHub 上
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 222 def validate_digest_response(request, realm, &password_procedure) secret_key = secret_token(request) credentials = decode_credentials_header(request) valid_nonce = validate_nonce(secret_key, request, credentials[:nonce]) if valid_nonce && realm == credentials[:realm] && opaque(secret_key) == credentials[:opaque] password = password_procedure.call(credentials[:username]) return false unless password method = request.get_header("rack.methodoverride.original_method") || request.get_header("REQUEST_METHOD") uri = credentials[:uri] [true, false].any? do |trailing_question_mark| [true, false].any? do |password_is_ha1| _uri = trailing_question_mark ? uri + "?" : uri expected = expected_response(method, _uri, credentials, password, password_is_ha1) expected == credentials[:response] end end end end
validate_nonce(secret_key, request, value, seconds_to_timeout = 5 * 60) 連結
可能會想要更短暫的逾時,這取決於請求是 PATCH、PUT 或 POST,以及用戶端是瀏覽器還是網路服務。如果實作過時指令,可能會更短暫。這將允許使用者使用新的隨機數,而不用使用者再次輸入使用者名稱和密碼。
來源:顯示 | 在 GitHub 上
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 341 def validate_nonce(secret_key, request, value, seconds_to_timeout = 5 * 60) return false if value.nil? t = ::Base64.decode64(value).split(":").first.to_i nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout end