串接saml格式的single sign on
· 5 min read
What is SAML
SAML是 Security Assertion Markup Language(安全斷言標記語言)的縮寫,它是一個用於在網絡上進行身份驗證和授權的 XML 標準。
SAML是一種用於單點登錄(Single Sign-On, SSO)和身份提供者(Identity Provider, IdP)之間通信的協議。
它的主要目的是允許不同的網絡安全域之間共享身份信息,以實現用戶在多個應用程序和服務之間無需重複登錄的功能。
SAML主要有一些關鍵概念和角色:
身份提供者(IdP): 身份提供者是負責驗證和管理用戶身份的服務。當用戶嘗試訪問一個需要驗證的應用程序時,他們首先被重定向到 IdP,以驗證其身份。
服務提供者(SP): 服務提供者是需要驗證用戶身份的應用程序或服務。SP 不直接處理身份驗證,而是依賴於 IdP 驗證用戶並提供對應用程序的訪問權限。
SAML斷言: SAML 斷言是包含用戶身份信息的 XML 文件,由 IdP 簽名,然後發送給 SP。斷言通常包括用戶身份信息、有效期限、用戶角色等信息。
SSO(單點登錄):SSO 是指用戶只需一次登錄,然後就能夠訪問多個 SP,而不需要在每個 SP 上都進行單獨的登錄。SAML 是實現 SSO 的一種常見方法。
SAML 流程是什麼呢?
從這張流程圖中來解釋
- user嘗試訪問一個需要身份驗證的 SP。
- SP 接收到user的訪問請求後, 會將user重定向到 IdP,進行相關的身份驗證。
- user在 IdP 上驗證身份。
- IdP 創建一個包含user information的 SAML 斷言,並將其簽名。
- IdP 將 SAML 斷言發送回 SP。
- SP 驗證 SAML 斷言的簽名,然後根據斷言中的information決定是否允許user訪問應用程序。
SAML 是企業和組織中實現單點登錄和安全身份管理的重要工具,特別是當存在多個應用程序和服務時,並希望用戶無需多次輸入他們的登錄資訊。
它提供了一個標準化的方法,以確保安全地共享身份信息。
實作
筆者這邊以ROR專案為例, 並以可支援多個IdP進行實作
# routes.rb
# 這邊搭配devise提供的user scope
devise_scope :user do
post '/users/sso/sign_in' => 'users/sso_sessions#new'
post 'users/sso/auth/:idp_id' => 'users/sso_sessions#auth', as: 'users_sso_auth'
end
# models/account.rb
# frozen_string_literal: true
class Account < ApplicationRecord
class << self
def get_saml_settings(idp_provider)
OneLogin::RubySaml::Logging.logger = Logger.new(Rails.root.join('log/ruby-saml.log'))
settings = OneLogin::RubySaml::Settings.new
# When disabled, saml validation errors will raise an exception.
settings.soft = true
# 設置你server提供的callback url
settings.assertion_consumer_service_url = "http://localhost:3000/users/sso/auth/#{idp_provider.id}"
# 設置你server提供的sp url
settings.sp_entity_id = 'http://localhost:3000/sp'
# idp 實體ID
settings.idp_entity_id =
# idp sso登入網址
settings.idp_sso_service_url =
# idp 憑證
settings.idp_cert =
settings.name_identifier_format = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
settings.idp_sso_service_binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
settings.idp_sso_service_binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
settings.idp_cert_fingerprint = OpenSSL::Digest::SHA1.new(Base64.decode64(idp_provider.certificate)).to_s
settings
rescue
nil
end
end
end
# sso_session_controller.rb
def new
# 你可以先透過某些key(ex: user_email etc...) 抓到idp, 再去拿設定檔
idp_provider = IdentityProvider.find()
settings = Account.saml_settings(idp_provider)
request = OneLogin::RubySaml::Authrequest.new
sso_login_url = request.create(settings)
# 跳轉至IdP登入網址, 動態取得setting就可支援多IdP
redirect_to(sso_login_url)
rescue
head(:bad_request)
end
# saml callback
def auth
# 我這邊還蠻建議帶個idp_id(這裡可以用uuid之類的), 在IdP驗證完的時候, 可以直接透過這個id直接拿到對應的provider
idp_provider = IdentityProvider.find_by(idp_id: params[:idp_id])
settings = Account.saml_settings(idp_provider)
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], settings: settings)
# response.is_valid? 沒有通過驗證可以考慮直接回傳401
unless response.is_valid?
# 如果response過驗證後, 就可以走devise的流程即可
user = User.find_by(email: response.name_id)
sign_in(:user, user)
yield user if block_given?
redirect_to(after_sign_in_path_for(user))
end
結語
其實使用saml來實作SSO(Single sign on)的流程並不複雜, 基於上述實作方式可根據不同情境做不同的實作細節。