簡介
我們知道SSO的兩個常用的協議分別是SAML和OpenID Connect,我們在前一篇文章已經講過了怎麼在wildfly中使用OpenID Connect連線keycloak,今天我們會繼續講解怎麼使用SAML協議連線keycloak。
OpenID Connect和SAML
OpenID Connect簡稱OIDC,是一個基於OAuth2協議的認證框架。為什麼要基於OAuth2框架呢?因為OAuth2協議只是一個授權協議,它是不完備的,並且也沒有指明具體的實現方式。所以這一切都由 OpenID Connect來填補。
OpenID Connect同時包含了認證和授權,並且使用Json Web Token(JWT)來進行訊息的傳遞。
一般來說OpenID Connect有兩種使用場景,第一種場景是某個應用程式請求keycloak來幫它認證一個使用者。該應用程式並不儲存這個使用者的認證資訊。所以使用者需要在keycloak中進行登入,登入成功之後keycloak會返回應用程式一個identity token 和 access token。
identity token主要包含使用者的基本資訊,包括使用者名稱,郵箱和一些其他的資訊。access token主要包含的是使用者的訪問許可權資訊,比如說使用者的角色等。應用程式可以通過使用access token來判斷使用者到底可以訪問應用程式的哪些資源。
還有一種場景就是client想去訪問遠端服務的資源,這種情況下client可以先從keycloak中獲取到access token,然後使用這個access token去遠端服務中請求資源。遠端伺服器收到了這個請求之後,會去驗證這個access token,然後根據token去獲取相應的資訊。
SAML 2.0是基於XML的認證協議,它是在OIDC之前產生的,所以會比OIDC成熟,但是相應的也會比OIDC複雜。
SAML使用XML在應用程式和認證伺服器中交換資料,同樣的SAML也有兩種使用場景。
第一種場景是某個應用程式請求keycloak來幫它認證一個使用者。該應用程式並不儲存這個使用者的認證資訊。所以使用者需要在keycloak中進行登入,登入成功之後keycloak會返回應用程式一個XML檔案,這個檔案裡面包含了一個叫做SAML assertion的東西,裡面存的是使用者的資訊,同時這個XML檔案中還包含了使用者的許可權資訊,應用程式可以根據這個資訊來對程式進行訪問工作。
還有一種場景就是client想去訪問遠端服務的資源,這種情況下client可以先從keycloak中獲取到SAML assertion,然後使用這個SAML assertion去遠端服務中請求資源。
所以總結起來,一般情況下是推薦是用OIDC的,因為它比較簡單和多平臺支援性更強。使用SAML的場景主要考慮的是SAML的成熟性,或者說公司中已經在使用了SAML了。
SAML的工作流程
在SAML協議中定義了三個角色,分別是principal:代表主體通常表示人類使用者。identity provider (IdP)身份提供者和service provider (SP)服務提供者。
IdP的作用就是進行身份認證,並且將使用者的認證資訊和授權資訊傳遞給服務提供者。
SP的作用就是進行使用者認證資訊的驗證,並且授權使用者訪問指定的資源資訊。
根據請求方式有redirect和post的不同,使用SAML來進行SSO認證有通常有三種方式,我們這裡介紹最簡單的一種叫做SP redirect request; IdP POST response:
上圖中User Agent就是web瀏覽器,我們看一下如果使用者想請求Service Provider的資源的時候,SAML協議是怎麼處理的。
- 使用者通過User Agent請求Service Provider,比如:
http://sp.flydean.com/myresource
SP將會對該資源進行相應的安全檢查,如果發現已經有一個有效的安全上下文的話,SP將會跳過2-7步,直接進入第8步。
- 如果在第一步的時候,SP並沒有找到相應的有效安全上下文的話,則會生成對應的SAMLRequest,並將User Agent重定向到IdP:
302 Redirect
Location: https://idp.flydean.com/SAML2/SSO/Redirect?SAMLRequest=request&RelayState=token
RelayState是SP維護的一個狀態資訊,主要用來防止CSRF攻擊。
其中這個SAMLRequest是用Base64編碼的samlp:AuthnRequest,下面是一個samlp:AuthnRequest的例子:
<samlp:AuthnRequest
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="aaf23196-1773-2113-474a-fe114412ab72"
Version="2.0"
IssueInstant="2020-09-05T09:21:59Z"
AssertionConsumerServiceIndex="0"
AttributeConsumingServiceIndex="0">
<saml:Issuer>https://sp.flydean.com/SAML2</saml:Issuer>
<samlp:NameIDPolicy
AllowCreate="true"
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"/>
</samlp:AuthnRequest>
為了安全起見,SAMLRequest還可以使用SP提供的簽名key來進行簽名。
- User agent將會傳送一個get請求到IdP的SSO server :
GET /SAML2/SSO/Redirect?SAMLRequest=request&RelayState=token HTTP/1.1
Host: idp.flydean.com
IdP收到這個AuthnRequest請求之後,將會進行安全驗證,如果是合法的AuthnRequest,那麼將會展示登入介面。
- 使用者可以輸入使用者名稱密碼進行登入。登入成功之後,IdP將會返回一個XHTML form:
<form method="post" action="https://sp.flydean.com/SAML2/SSO/POST" ...>
<input type="hidden" name="SAMLResponse" value="response" />
<input type="hidden" name="RelayState" value="token" />
...
<input type="submit" value="Submit" />
</form>
這個form中包含了SAMLResponse資訊,SAMLResponse中包含了使用者相關的資訊。
同樣的SAMLResponse也是使用Base64進行編碼過的samlp:Response。
<samlp:Response
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="identifier_2"
InResponseTo="identifier_1"
Version="2.0"
IssueInstant="2020-09-05T09:22:05Z"
Destination="https://sp.flydean.com/SAML2/SSO/POST">
<saml:Issuer>https://idp.flydean.com/SAML2</saml:Issuer>
<samlp:Status>
<samlp:StatusCode
Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="identifier_3"
Version="2.0"
IssueInstant="2020-09-05T09:22:05Z">
<saml:Issuer>https://idp.flydean.com/SAML2</saml:Issuer>
<!-- a POSTed assertion MUST be signed -->
<ds:Signature
xmlns:ds="http://www.w3.org/2000/09/xmldsig#">...</ds:Signature>
<saml:Subject>
<saml:NameID
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">
3f7b3dcf-1674-4ecd-92c8-1544f346baf8
</saml:NameID>
<saml:SubjectConfirmation
Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData
InResponseTo="identifier_1"
Recipient="https://sp.flydean.com/SAML2/SSO/POST"
NotOnOrAfter="2020-09-05T09:27:05Z"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions
NotBefore="2020-09-05T09:17:05Z"
NotOnOrAfter="2020-09-05T09:27:05Z">
<saml:AudienceRestriction>
<saml:Audience>https://sp.flydean.com/SAML2</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="2020-09-05T09:22:00Z"
SessionIndex="identifier_3">
<saml:AuthnContext>
<saml:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
</saml:Assertion>
</samlp:Response>
我們可以看到samlp:Response中包含有saml:Assertion資訊。
-
user agent 收到XHTML form之後將會提交該form給SP。
-
SP中的assertion consumer service將會處理這個請求,建立相關的安全上下文,並將user agent重定向到要訪問的資源頁面。
-
user agent再次請求SP資源。
-
因為安全上下文已經建立完畢,SP可以直接返回相應的資源,不用再次到IdP進行認證。
我們可以看到上面的所有的資訊交換都是由前端瀏覽器來完成的,在SP和IdP之間不存在直接的通訊。
這種全部由前端來完成資訊交換的方式好處就是協議流非常簡單,所有的訊息都是簡單的GET或者POST請求。
如果為了提高安全性,也可以使用引用訊息。也就是說IdP返回的不是直接的SAML assertion,而是一個SAML assertion的引用。SP收到這個引用之後,可以從後臺再去查詢真實的SAML assertion,從而提高了安全性。
在keycloak中使用SAML
接下來,我們看下怎麼在keycloak中配置使用SAML協議。
我們通過./standalone.sh -Djboss.socket.binding.port-offset=100啟動keycloak伺服器。訪問 http://localhost:8180/auth/admin 可以進入到admin console介面。
注意,這裡為了和本地應用程式的預設埠8080區別,我們新增了一個-Djboss.socket.binding.port-offset=100引數,讓keycloak的埠從8080變成了8180。
輸入我們建立的admin使用者名稱和密碼,就可以登入到keycloak的admin介面。
這裡需要為SAML應用建立一個新的client。
點選clients-> create 輸入Client ID和Client Protocol: saml,點選save即可建立新的client。
成功建立client之後,假設我們要部署的應用程式名叫做app-profile-saml,則需要新增下面的資訊:
Valid Redirect URIs: http://localhost:8080/app-profile-saml/*
Base URL: http://localhost:8080/app-profile-saml/
Master SAML Processing URL: http://localhost:8080/app-profile-saml/saml
Force Name ID Format: ON
點儲存即可。
接下來我們需要點選mappers,建立一些使用者資訊和token claims的對映資訊,從而能夠在saml的請求中包含這些使用者資訊。
為了簡單起見,我們選擇預設的Protocol Mapper:
最後一步,我們需要配置adapter。
點選installation,選擇Keycloak SAML Adapter keycloak-saml.xml, 點選下載。
將下載下來的keycloak-saml.xml進行修改:
將 logoutPage="SPECIFY YOUR LOGOUT PAGE!" 修改為 /index.jsp
將 entityID="saml-test" 中的entityID修改為我們設定的entityID
將keycloak-saml.xml拷貝到我們應用程式的config/目錄下。這裡我們使用官方的應用程式,大家可以在 https://github.com/keycloak/keycloak-quickstarts/tree/latest/app-profile-saml-jee-jsp 進行下載。
在下一節,我們將會詳細講解這個應用程式的功能和結構。
準備wildfy和應用程式
我們從wildfly官網下載wildfly應用程式之後,還需要到keycloak中下載wildfly Client Adapters。
這裡因為我們使用的是SAML,所以需要下載 keycloak-saml-wildfly-adapter-dist-11.0.2.zip。
下載完畢之後,將其拷貝到wildfly根目錄,解壓即可。
解壓adapter,解壓之後,進入wildfly/bin目錄,執行:
./jboss-cli.sh --file=adapter-elytron-install-offline.cli
即可安裝完畢。
安裝完畢之後,記得啟動wildfly應用程式。
接下來可以編譯我們的應用程式了:
cd app-profile-saml-jee-jsp
mvn clean wildfly:deploy
即可將我們的應用程式部署到wildfly中。
先看下應用的執行情況,訪問 http://localhost:8080/app-profile-saml/
點選login,可以看到跳轉到了keycloak的登入頁面:
輸入使用者命名密碼之後就會跳轉到profile.jsp頁面,從而展示使用者的profile資訊。
簡單講解一下應用程式的工作流程。
應用程式主要有兩個頁面,一個是index,一個是profile。在index頁面會去檢測使用者是否登入。如果未登入,可以點選登入按鈕,跳轉到登入頁面。
輸入使用者名稱和密碼進行校驗之後,keycloak會返回一個SAMLResponse給應用程式,應用程式通過assertion consumer service將會處理這個請求,建立相關的安全上下文,並將user agent重定向到要訪問的資源頁面。
本文作者:flydean程式那些事
本文連結:http://www.flydean.com/keycloak-saml-wildfly/
本文來源:flydean的部落格
歡迎關注我的公眾號:「程式那些事」最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!