咱們知道SSO的兩個經常使用的協議分別是SAML和OpenID Connect,咱們在前一篇文章已經講過了怎麼在wildfly中使用OpenID Connect鏈接keycloak,今天咱們會繼續講解怎麼使用SAML協議鏈接keycloak。前端
OpenID Connect簡稱OIDC,是一個基於OAuth2協議的認證框架。爲何要基於OAuth2框架呢?由於OAuth2協議只是一個受權協議,它是不完備的,而且也沒有指明具體的實現方式。因此這一切都由 OpenID Connect來填補。java
OpenID Connect同時包含了認證和受權,而且使用Json Web Token(JWT)來進行消息的傳遞。git
通常來講OpenID Connect有兩種使用場景,第一種場景是某個應用程序請求keycloak來幫它認證一個用戶。該應用程序並不存儲這個用戶的認證信息。因此用戶須要在keycloak中進行登陸,登陸成功以後keycloak會返回應用程序一個identity token 和 access token。github
identity token主要包含用戶的基本信息,包括用戶名,郵箱和一些其餘的信息。access token主要包含的是用戶的訪問權限信息,好比說用戶的角色等。應用程序能夠經過使用access token來判斷用戶到底能夠訪問應用程序的哪些資源。web
還有一種場景就是client想去訪問遠程服務的資源,這種狀況下client能夠先從keycloak中獲取到access token,而後使用這個access token去遠程服務中請求資源。遠程服務器收到了這個請求以後,會去驗證這個access token,而後根據token去獲取相應的信息。shell
SAML 2.0是基於XML的認證協議,它是在OIDC以前產生的,因此會比OIDC成熟,可是相應的也會比OIDC複雜。瀏覽器
SAML使用XML在應用程序和認證服務器中交換數據,一樣的SAML也有兩種使用場景。安全
第一種場景是某個應用程序請求keycloak來幫它認證一個用戶。該應用程序並不存儲這個用戶的認證信息。因此用戶須要在keycloak中進行登陸,登陸成功以後keycloak會返回應用程序一個XML文件,這個文件裏面包含了一個叫作SAML assertion的東西,裏面存的是用戶的信息,同時這個XML文件中還包含了用戶的權限信息,應用程序能夠根據這個信息來對程序進行訪問工做。服務器
還有一種場景就是client想去訪問遠程服務的資源,這種狀況下client能夠先從keycloak中獲取到SAML assertion,而後使用這個SAML assertion去遠程服務中請求資源。app
因此總結起來,通常狀況下是推薦是用OIDC的,由於它比較簡單和多平臺支持性更強。使用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協議是怎麼處理的。
http://sp.flydean.com/myresource
SP將會對該資源進行相應的安全檢查,若是發現已經有一個有效的安全上下文的話,SP將會跳過2-7步,直接進入第8步。
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來進行簽名。
GET /SAML2/SSO/Redirect?SAMLRequest=request&RelayState=token HTTP/1.1 Host: idp.flydean.com
IdP收到這個AuthnRequest請求以後,將會進行安全驗證,若是是合法的AuthnRequest,那麼將會展現登陸界面。
<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協議。
咱們經過./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 進行下載。
在下一節,咱們將會詳細講解這個應用程序的功能和結構。
咱們從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的博客
歡迎關注個人公衆號:「程序那些事」最通俗的解讀,最深入的乾貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!