1. SAML là gì?

Nếu bạn đã từng nghe OAuth hay OAuth2 thì SAML chính là một chuẩn khác để giải quyết bài toán tương tự với OAuth và OAuth2.

Bài toán mà các chuẩn trên giải quyết tên là SSO (Single Sign On). 

SSO nảy sinh từ vấn đề khi các nhà cung cấp dịch vụ muốn người dùng có thể sử dụng các dịch vụ khác nhau mà chỉ cần đăng nhập vào một chỗ duy nhất, giúp cho việc sử dụng được thuận tiện cũng như giúp cho người dùng quản lý các username và password được giảm đi tối thiểu, thì đòi hỏi cần có một cách nào đó có thể xác thực và ủy quyền thông tin người dùng mà không cần bắt họ tạo tài khoản khác nhau trên những dịch vụ khác nhau.

Chú ý: SAML tự động giải quyết cả 2 vấn đề xác thực (authentication) và ủy quyền (authorization) nhưng OAuth và OAuth2 chỉ tự động giải quyết được vấn đề uỷ quyền (link tham khảo tại đây).

SAML (Security Assertion Markup Language) là một "chuẩn mở" cho phép nhà cung cấp thực thể (Identity Provider - IdP) xác thực người dùng và ủy quyền cho người dùng sử dụng một dịch vụ nào đó của nhà cung cấp dịch vụ (Service Provider - SP) mà không bắt buộc người dùng phải tạo tài khoản đăng nhập vào dịch vụ đó.

Định nghĩa như trên đọc sẽ khó hiểu, nhưng nếu bạn biết nút "Đăng nhập bằng Facebook" ở một số website thì mục đích của cái nút đó chính là mục đích của SAML.

 

2. Cách hoạt động của SAML.

 

           cơ chế hoạt động của SAML

Ở hình trên thì: SP là nhà cung cấp dịch vụ (là ứng dụng có nút "Đăng nhập bằng Facebook" ấy), IdP là nhà cung cấp các thực thể (tài khoản người dùng - IdP ở đây là Facebook đấy).

  • Bước 1: User sẽ click vào nút "Đăng nhập bằng tài khoản của cái gì đó" từ browser, request này sẽ được gửi tới SP.
  • Bước 2: Phía SP sẽ tạo ra một SAML Request để gửi tới IdP, SAML Request này sẽ được chính SP ký điện tử (sign) bằng chữ ký của SP (chữ ký của SP ở đây chính là khóa bí mật của SP). 
  • Bước 3: Phía IdP khi nhận được SAML Request từ SP sẽ phải xác thực chữ ký có đúng là của SP hay không bằng cách dùng khóa công khai của SP để xác thực:

 Khóa công khai của SP này IdP lấy từ đâu?

Trước khi thực hiện giao dịch, SP và IdP phải bằng cách nào đó trao đổi được khóa công khai với nhau trước (không phải khóa bí  mật nhé). Thông thường, mỗi bên SP và IdP sẽ có một public url chứa metadata, metadata này chứa các thông tin công khai như là khóa công khai, ID thực thể và URL để điều hướng khi có request đến. Phía IdP sẽ biết được metadata url của SP để từ đó lấy ra khóa công khai của SP để xác thực chữ ký của SP. Trong trường hợp hệ thống của Idp đã có sẵn và không lấy public key của SP thông qua metadata url được thì SP phải gửi khóa công khai của mình cho Idp cài đặt trước khi thực hiện giao dịch.

  • Bước 4: Vẫn đang ở IdP, sau khi xác thực được chữ ký của SP rồi, IdP sẽ làm những thứ sau:
    • Lấy ra thông tin người dùng đang sử dụng browser (nếu người dùng đang đăng nhập vào IdP, còn nếu người dùng đang không đăng nhập thì bắt người dùng đăng nhập trước) để redirect (http post) về cho SP sử dụng (kết quả trả về này mình gọi là SAML Response). Trước khi gửi về cho SP thì IdP sẽ ký điện tử (sign) vào SAML Response bằng khóa bí mật của IdP.
    • Không những IdP ký vào SAML Response IdP cũng sẽ mã hóa các kết quả dữ liệu (SAML Assertions) có trong SAML Response bằng khóa công khai của SP.
  • Bước 5: Khi SP nhận được SAML Response, nó sẽ thực hiện những việc sau:
    • Dùng khóa công khai của IdP để xác thực xem có đúng là kết quả được gửi từ IdP hay không (đây chính là phần xác thực mà OAuth và OAuth2 không có). Khóa công khai của IdP cũng giống như nói ở trên, có thể lấy thông qua metadata url của IdP hoặc có thể được trao đổi trước.
    • Nếu xác thực đúng chữ ký, SP sẽ tiếp tục dùng khóa công khai của chính mình để giải mãi SAML Assertions đã được mã hóa từ phía IdP.
    • Lấy các thông tin dữ liệu người dùng trong SAML Assertions để đăng nhập người dùng vào hệ thống của chính mình, và trả về cho người dùng thông báo thành công (hay điều hướng người dùng tới các tài nguyên mong muốn).

 

Phía trên mình đã trình bày lý thuyết phần hoạt động của SAML, dưới đây là phần thực hành sử dụng SP là server Rails và IdP là Okta.

3. Cấu hình SAML trong Rails sử dụng IdP là Okta.

Trong trường hợp này: Ứng dụng Rails của bạn chính là SP, và ứng dụng bạn tạo trên Okta chính là Idp.

3.1.  Cấu hình trong Rails.

# /config/routes.rb
  scope module: :mrs, controller: "saml_sessions" do
    get :new, path: "saml/sign_in", as: :new_mr_sso_session
    post :create, path: "saml/auth", as: :mr_sso_session
    get :metadata, path: "saml/metadata", as: :metadata_mr_sso_session
  end
# /controllers/mrs/saml_sessions_controller.rb
class Mrs::SamlSessionsController < ApplicationController
  def new
    request = OneLogin::RubySaml::Authrequest.new
    action = request.create(saml_settings)
    redirect_to action
  end

  def create
    response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], settings: saml_settings)
    if !response.is_valid?
      redirect_to root_url
      return
    end

    last_name = response.attributes["LastName"]
    mr = Mr.find_by last_name: last_name
    # do anything with mr here
    redirect_to "your_resource_url"
  end

  def metadata
    meta = OneLogin::RubySaml::Metadata.new
    render xml: meta.generate(saml_settings)
  end

  private

  def saml_settings
    idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
    # Returns OneLogin::RubySaml::Settings prepopulated with idp metadata
    # NOTE: It is IdP's metadata, it should include assertion_cosumer_service_url, idp_cert and IdP's entityID
    settings = idp_metadata_parser.parse_remote("https://dev-879121.okta.com/app/abcefasdfasfafa/sso/saml/metadata")

    # NOTE: This is entityID of SP in SAML request, it should be metadata url of SP
    # The metadata of SP should include idp_sso_target_url, SP certificate and SP's entityID
    settings.issuer = "http://localhost:3001/mrs/saml/metadata"

    settings.assertion_consumer_service_url = "http://localhost:3001/mrs/saml/auth"

    # NOTE: These are public and private keys of SP
    # the public key is used to authenticate IdP's signed response
    # the private key is used to sign the request to IdP, IdP should roll SP's public key in SP'metadata url to authenticate this signature
    settings.certificate = "SP's public_key"
    settings.private_key = "SP's private_key"
    settings.security[:authn_requests_signed] = true  # Enable signature on request from SP
    settings.security[:want_assertions_signed] = true # Enable the requirement of signed assertion from IdP

    settings
  end
end

 

Cơ bản source code trong rails chỉ có như trên thôi. Phần cài đặt của SAML nằm trong method saml_settings:

  • Url của metadata của IdP chính là url nằm trong câu lệnh idp_metadata_parser.parse_remote(...).
  • settings.issuer chính là SP identity, là tên của thưc thể SP, thường nó sẽ được gán cho giá trị là url của metadata của SP. Sau khi cài đặt xong bạn truy cập vào đường link http://localhost/mrs/saml/metadata thì nó sẽ tự động render ra dữ liệu metadata cho bạn.
  • assertion_cosumer_service_url là url để IdP trả kết quả lại cho SP. Khi SP nhận kết quả từ IdP, nó sẽ so sánh url có trong SAML Response với url này để xác thực xem có đúng kết quả được trả về đúng địa chỉ không, nếu không đúng sẽ báo lỗi.
  • settings.certificate là khóa công khai của SP, SP phải khai báo nó ở đây để xác thực chữ ký của IdP.
  • Với method metadata ở trên thì khi bạn truy cập vào http://localhost/mrs/saml/metadata, mọi thông tin cần thiết khai báo trong saml_settings sẽ được public ra internet (những thông tin bí mật sẽ không được công khai).
  • settings.private_key là khóa bí mật của SP, SP phải khai báo nó ở đây để ký vào vào SAML request muốn gửi tới cho IdP.
  • authn_requests_signed là có muốn  vào SAML request gửi tới IdP hay không? Ở bên phía IdP có thể không cần xác thực chữ ký của SP cho dù bên SP có ký hay không.
  • want_assertions_signed là có yêu cầu SAML Assertions được IdP ký không? Bên phía IdP có thể không cần ký vào SAML Assertions cho dù bên SP có yêu cầu hay không. Sẽ không có lỗi gì được báo dù bên SP yêu cầu IdP ký nhưng IdP không ký.

Bạn để ý trong hàm create không lấy user bằng email được gửi về từ okta (IdP) mà ta muốn IdP gửi về một thuộc tính khác (LastName trong đoạn code trên).

 

Để tạo certificate và private_key ở trên, bạn chạy câu lệnh dưới đây trong terminal:

openssl req -new -x509 -days 365 -nodes -sha256 -out key.crt -keyout p_key.pem

Nó sẽ tạo ra cho bạn một cặp khóa public key key.crt và private key p_key.pem. Khóa sẽ có hạn là 365 ngày như mình chỉ định trong câu lệnh trên.

 

3.2. Tạo ứng dụng IdP trên okta.

Đầu tiên bạn cần làm là thay đổi giao diện trên okta từ Developer Console thành Classic UI thì mới tạo được app:

                 tạo app trên okta

 

Chọn tab Applications và chọn Add Application:

                tạo app trên okta

 

Khi tạo App thì chọn Platform là Web và SAML 2.0:

                 tạo SAML app trên okta

 

Trong phần setting của App bạn chọn như sau:

            cấu hình saml trên okta

Single sign on URL: là assertion_cosumer_service_url trong cấu hình rails (xem giải thích ở phần trên).

Audience URI (SP Entity ID): là issuer trong cấu hình rails.

 

Trong phần Attribute Statements (Optional), bạn điền Name là LastName và Value là user.lastName, đây chính là tên và giá trị mà bạn mong muốn IdP sẽ trả về cho SP ngoài giá trị mặc định là email (xem method create trong Rails ở phần trên).

Chú ý là giá trị của Value phải luôn là user.{field}, và chỉ lấy được các thuộc tính field có sẵn của user trong okta.

 

Click vào Show Advanced Settings và thiết lập như sau:

             cấu hình SAML trong okta

Để cài đặt được phần này bạn phải hiểu được phần Cách hoạt động của SAML mình giải thích ở đầu. Một số cấu hình cần chú ý là:

Response: Bạn có muốn IdP ký điện tử vào response trả về cho SP không?

Assertion Signature: Bạn có muốn IdP ký điện tử vào SAML assertions nằm trong SAML response không?

Assertion Encryption: Bạn có muốn IdP mã hóa SAML assertions trả về cho SP không?

Encryption Certificate: Nếu bạn muốn IdP mã hóa SAML Assertion thì bạn phải chỉ định SP public key ở phía IdP. Đây chính là file public key key.cer của SP bạn tạo ở trên, bạn upload file này lên okta.

 

Sau khi kết thúc cài đặt settings, bạn cần phải gán app vừa tạo cho một tài khoản thì mới request được từ SP của bạn tới IdP. Click vào tab Assignments rồi chọn Assign to People:

         assign okta app cho user

 

Ở tab Sign On, bạn sẽ thấy các thông tin cần thiết của IdP để cấu hình trong SP của bạn:

           cấu hình saml trên okta

Nếu bạn muốn các thông tin riêng biệt của IdP thì click vào View Setup Instructions, còn nếu bạn chỉ muốn metadata url của IdP để SP của bạn có thể tự động lấy thông tin (như mình làm trong phần saml_settings trong rails ở trên) thì click vào Identity Provider metadata để hiển thị thông tin metadata của IdP và lấy url của nó, phần cấu hình tự động lấy thông tin của IdP từ metadata chính là câu lệnh này trong rails:

settings = idp_metadata_parser.parse_remote("https://dev-879121.okta.com/app/abcefasdfasfafa/sso/saml/metadata")

 

Sau khi đã cài đặt SP và IdP xong thì bạn truy cập vào url http://localhost:3001/mrs/saml/sign_in để test (mình dùng cổng 3001 cho rails app).

 

Link tham khảo:

https://en.wikipedia.org/wiki/SAML_Metadata

https://developer.okta.com/standards/SAML/setting_up_a_saml_application_in_okta/

https://security.stackexchange.com/questions/133065/why-is-it-a-bad-idea-to-use-plain-oauth2-for-authentication

https://stackoverflow.com/questions/12779532/differences-between-sp-initiated-sso-and-idp-initiated-sso