Pages

搜尋此網誌

2014年1月7日 星期二

Spring Security: oauth2 運作原理解析筆記

Spring Security: oauth2 運作原理解析筆記

繼上一篇 Spring Security Basic Authentication with Ajax request 失敗處理,說明關於如何在 Spring Security 設定 Basic Authentication 以及透過 ajax 來進行資料請求後,這篇要來說明如何使用目前各大網站常用的 oauth 驗證機制。

關於 oauth 的特性網路上有很多不錯的文章,推薦大家先閱讀:

OAuth原理初探

這篇有很詳細的圖文解說,最後還有關於 OAuth 和 OpenID 的區別:

OAuth 關注的是授權,即:“用戶能做什麼”;而 OpenID 關注的是證明,即:“用戶是誰”。

OAuth2 - In a Simple Way

這篇是英文,但在解釋 oauth 使用時機有較精準的說明:

OAuth 2 provides several grant types for different use cases. The grant types defined are:

  1. Authorization Code: for apps running on a web server
  2. Implicit: for browser-based or mobile apps
  3. Password: for logging in with a username and password
  4. Client credentials: for application access

可以看到 oauth 提供了各種不同狀況的應用的驗證機制,算是很完整的安全機制,另外在文中還有針對每種驗證方式提供 curl 或是 網址的驗證流程,上述兩篇文章的閱讀可以充分了解 oauth 是什麼。

Pro Spring Security and OAUTH 2

這邊講的就是實作啦,沒有看過上面兩篇會不知道他在幹嘛,建議可以依序在看到這一篇,該作者為 Pro Spring Security (連結可下載原文 pdf) 的作者,對於 Spring Security 的實作細節有很詳盡的說明,在書中並未包含 oauth 的說明該文章算是補足 oauth 的說明,且說明的是 oauth2 的規範,簡單來說 oauth2 簡化了 oauth,在實作上更加簡單。

該文章範例程式碼:https://github.com/spring-projects/spring-security-oauth,下載之後取出 1.0.1 版git checkout 1.0.1.RELEASE,在專案根目錄使用 maven,在 command 執行 mvn clean install -P bootstrap,將會在該專案底下的 sample 各資料夾底下 target 資料夾找到各別的 war 檔,如此就可以將 war 檔放至於 tomcat 下運行。

整個文章讀下來幾個比較重要的步驟說明筆記如下

Tonr 之 OAuth2ClientContextFilter 過濾處理

  1. 當登入網頁需要驗證時
  2. 登入失敗時
  3. 已驗證通過會透過該 filter 到達 SparklrServiceImpl

SparklrServiceImpl 將實作存取 Sparklr 相關方法

  1. 呼叫後端 Sparklr 取得使用者特有的圖片
  2. 透過使用 RestOperations(OAuth2RestTemplate)取得 Sparklr 上的圖片

取得 Access Token

  1. 需要先取得 Access Token,一旦取得會存於 DefaultOAuth2ClientContext
  2. 一開始 DefaultOAuth2ClientContext 尚未有 Access Token 時,會從 DefaultOAuth2ClientContext 取得 AccessTokenRequest,兩者當 Spring 啟動時會自動設定,透過 bean 定義 <oauth:rest-template resource="sparklr" /> 傳給 RestTemplateBeanDefinitionParser。
  3. 接著在從呼叫 AccessTokenProvider 取的 Token
  4. 將會被 AccessTokenRequest 以及 AuthorizationCodeResourceDetails 呼叫 AccessTokenProviderChain 中的 obtainAccessToken 方法。
  5. 其中 AuthorizationCodeResourceDetails 定義如下,將在動時透過 ResourceBeanDefinitionParser 解析
<oauth:resource id="sparklr" type="authorization_code" client-id="tonr" client-secret="secret"
                access-token-uri="${accessTokenUri}" user-authorization-uri="${userAuthorizationUri}" scope="read,write" />
<oauth:rest-template resource="sparklr" />

取得 Authorization Code Access Token

  1. AccessTokenProviderChain 的 obtainAccessToken 方法將透過 設定 AccessTokenProvider 實體來取的 token,指的就是 AuthorizationCodeAccessTokenProvider
  2. AuthorizationCodeAccessTokenProvider 將透過 POST 呼叫 http://localhost:8080/sparklr2/oauth/authorize 取得驗證碼
  3. sparklr2 其中 /sparklr2/oauth/authorize 將定義於 FilterSecurityInterceptor
  4. 一旦 FilterSecurityInterceptor 發現沒有權限登入將會將頁面導入 http://localhost:8080/sparklr2/login.jsp,設定如下:

    <http access-denied-page="/login.jsp?authorization_error=true" disable-url-rewriting="true" xmlns="http://www.springframework.org/schema/security">
        <intercept-url pattern="/oauth/**" access="ROLE_USER" />
        <intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
        <form-login authentication-failure-url="/login.jsp?authentication_error=true" default-target-url="/index.jsp" login-page="/login.jsp" login-processing-url="/login.do" />
        <logout logout-success-url="/index.jsp" logout-url="/logout.do" />
        <anonymous />
    </http>
  5. redirect 到 login 頁面將會被 Tonr 的 AuthorizationCodeAccessTokenProvider 接收並且被 UserRedirectRequiredException 擷取並且丟出由 Oauth2ClientContextFilter 處理,加入額外的參數 http://localhost:8080/sparklr2/login.jsp?response_type=code&client_id=tonr&scope=read+write&state=dWby7l&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Ftonr2%2Fsparklr%2Fphotos,如此就可以開始進行登入

進行登入取得 USER ROLE

  1. 登入步驟請參考原文
  2. 一旦取得 role ROLE_USER 將會再次導入之前的網址 /sparklr2/oauth/authorize
  3. 該網址一旦安裝 core Spring Security OAuth project,透過下述設定就會產生該 endpoint

    <oauth:authorization-server client-details-service-ref="clientDetails" token-services-ref="tokenServices"
        user-approval-handler-ref="userApprovalHandler">
        <oauth:authorization-code />
        <oauth:implicit />
        <oauth:refresh-token />
        <oauth:client-credentials />
        <oauth:password />
    </oauth:authorization-server>
  4. 其中 AuthorizationEndpoint 對應 /oauth/authorize

透過 AuthorizationEndpoint 取得 CLIENT ROLE

關於登入者可執行的操作將透過 InMemoryClientDetailsService 取得,該物件將讀取設定如下:

<oauth:client-details-service id="clientDetails">

    <oauth:client client-id="my-trusted-client" authorized-grant-types="password,authorization_code,refresh_token,implicit" authorities="ROLE_CLIENT, ROLE_TRUSTED_CLIENT" scope="read,write,trust" access-token-validity="60" />
    ...    
    <oauth:client client-id="my-untrusted-client-with-registered-redirect" authorized-grant-types="authorization_code" authorities="ROLE_CLIENT" redirect-uri="http://anywhere" scope="read" />

    <oauth:client client-id="tonr" resource-ids="sparklr" authorized-grant-types="authorization_code,implicit" authorities="ROLE_CLIENT" scope="read,write" secret="secret" />
</oauth:client-details-service>

這裡就是 oauth 與一般使用者驗證的差別,定義的是可對 resource 的操作有哪些如 read,write,trust,這邊的權限設定沒有 user 的概念,只有 server 對不同 client-id 與有無 secret 的差別對 client 提供的功能有哪些,一旦確認有存在於設定檔將會取得 ClientDetails 物件。

sparklr 請使用者確認是否允許該存取

一旦確認 AuthorizationRequest 有取得 ClientDetails 物件,將會在被導入 /oauth/confirm_access 該網址的實作必須由我們自己開發的應用程式來實作,也就是 Sparklr 中的 AccessConfirmationController 進而顯示 access_confirmation.jsp,讓使用者確認是否允許存取

確認允許,透過 PhotoController 取得照片

一旦點選允許將發生下面的事情:

  1. 確認 AuthorizationRequest 是有效可進行存取的。
  2. 透過呼叫 AuthorizationRequest 的 getResponseTypes 取得 code,不是 token
  3. framework 會產生新的 authorization code,交由 approveOrDeny 方法完成之後的事情,將會從 Sparklr 導回 tonr,實際的範例網址為 http://localhost:8080/tonr2/sparklr/photos;jsessionid=03B2E814391E010B3D1210241ECF6C0A?code=vqMbuf&state=aTSlVl
  4. OAuth2RestTemplate 與 AuthorizationCodeAccessTokenProvider 結合,從 Sparklr 取得 token,實際上,將會 request /sparklr/oauth/token包含下列參數:

    {
        grant_type=’authorization_code’,
        redirect_uri=’http://localhost:8080/tonr2/sparklr/photos’,
        code=xxxx
    }
  5. 該次呼叫會被 org.springframework.security.oauth2.provider.endpoint.TokenEndpoint 處理產生 OAuth2AccessToken 並且 response Bearer token。
  6. 一旦取得 token,Tonr 會再一次呼叫 Sparklr,這次將會傳入取得的 Bearer token 於該次請求的 header
  7. 透過 OAuth2AuthenticationProcessingFilter 解開 token 在由 Oauth2AuthenticationManager 確認該請求是否合法。
  8. 一旦確認無誤,終於可以到達 PhotoController 取得照片的資源。

步驟很多,也許有說明不清楚的地方,不過確實把 oauth 的運作詳細走過一遍,希望可以幫助到有需要的人,上述範例是使用 xml 來定義安全性相關的設定,下一篇將進一步說明使用 spring-boot 透過 java config(java 物件)來定義 oauth 過程會精簡很多,筆者也是先瞭解最基本得 xml 設定,才能完成 java config 型式的應用,若有需要,可以先參考下列專案,利用 java config 完成 Spring Security 設定:

張貼留言