Skip to main content

One post tagged with "sso"

View All Tags

· 5 min read
junminhong(jasper)

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. IdPSAML 斷言發送回 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)的流程並不複雜, 基於上述實作方式可根據不同情境做不同的實作細節。