跳至主要内容

1 篇文章 含有標籤「saml」

檢視所有標籤

串接saml格式的single sign on

· 閱讀時間約 5 分鐘
junminhong(jasper)
Product Developer

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 流程是什麼呢?​

google saml 流程

從這張流程圖中來解釋

  1. user嘗試訪問一個需要身份驗證的 SP。
  2. SP 接收到user的訪問請求後, 會將user重定向到 IdP,進行相關的身份驗證。
  3. user在 IdP 上驗證身份。
  4. IdP 創建一個包含user information的 SAML 斷言,並將其簽名。
  5. IdP 將 SAML 斷言發送回 SP。
  6. SP 驗證 SAML 斷言的簽名,然後根據斷言中的information決定是否允許user訪問應用程序。

SAML 是企業和組織中實現單點登錄和安全身份管理的重要工具,特別是當存在多個應用程序和服務時,並希望用戶無需多次輸入他們的登錄資訊。

它提供了一個標準化的方法,以確保安全地共享身份信息。

實作​

筆者這邊以ROR專案為例, 並以可支援多個IdP進行實作

  1. 首先先安裝 ruby-saml gem
  2. 搭配 devise 管理user登入流程
# 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)的流程並不複雜, 基於上述實作方式可根據不同情境做不同的實作細節。