為了賬號安全,請及時綁定郵箱和手機立即綁定

基于OAUTH2的統一認證的實例解析

2016.07.23 17:43 58125瀏覽

在一個單位中,可能是存在多個不同的應用,比如學校會有財務的系統會有學生工作的系統,還有圖書館的系統等等,如果每個系統都用獨立的賬號認證體系,會給用戶帶來很大困擾,也給管理帶來很大不便。所以需要設計一種統一登錄的解決方案。比如我登陸了百度賬號,進貼吧時發現已經登錄了,進糯米發現也自動登錄了。常見的有兩種情況,一種是SSO(單點登錄)效果是一次輸入密碼多個網站可以識別在線狀態;還有一種是多平臺登錄,效果是可以用一個賬號(比如QQ賬號)登錄多個不同的網站。

SSO與多平臺登錄

SSO一般用于同一單位的多個站點的登陸狀態保持,技術上一般參考CAS協議;多平臺登錄一般是Oauth體系的協議,有多種認證模式但是不具備會話管理和狀態保持。
不過從本質上講,我覺得兩者都是通過可信的第三方進行身份驗證,如果說同一單位的多個子系統共同只圍繞一個第三方賬戶(可以稱為認證中心)進行多平臺登錄驗證,那么在第三方平臺登錄后再訪問其他網站,效果和統一登錄是差不多的。此外,Oauth2還有個好處就是可以實現跨平臺的登錄管理,因為他的認證過程不依賴于session和cookie,比如對于移動端設備,以及在前后端分離后這種登錄認證方式也可以起到很大作用。
這篇文章里我就著結合之前項目中整合過的OAUTH2來講一講這種登錄認證的過程。項目是基于Shiro+ALTU實現,參考方案mkk/oauth2-shiro - 碼云 - 開源中國

oauth2的基本概念

在Oauth中至少是有用戶,應用服務器,認證服務器這幾個角色在交互。OAuth的作用就是讓"客戶端"安全可控地獲取"用戶"的授權,與"應用服務器"進行互動。

OAuth2的基本流程

用戶通過瀏覽器訪問一個應用,比如我要上慕課網學習。

  1. 網站要求我登錄,我選擇使用QQ登錄,這里的QQ登錄就是那個認證服務器。
  2. 這個時候慕課提供的QQ登錄鏈接會把我帶到QQ登錄頁面
  3. 在QQ的登錄頁面完成登錄后,選擇授權,也就是允許慕課網獲取我的資料。
  4. 這個時候我們看到瀏覽器經過幾次跳轉后返回慕課網,這個時候我們已經完成了登錄。

重點在于幾次跳轉的過程中,慕課網和QQ登錄的服務之間還有過幾次交互。

  1. 我們選擇了授權的時候QQ登錄服務器會根據慕課跳轉到QQ時候給出的重定向鏈接返回給慕課網一個code,這個code代表QQ的登錄服務器認可慕課網這個應用服務器的這個請求是合法的予以放行.
  2. 慕課這個時候就會用這個code再次向QQ登錄服務發起請求服務令牌(token)。
  3. 拿到這個令牌之后,接下來慕課需要用戶的一些基本信息時就可以通過在向QQ服務提交的請求頭里帶上這個令牌,令牌驗證通過就可以拿到用戶資源。

這一部分的操作是應用服務器和驗證服務器之間的交互,這個過程對用戶是透明的。這個過程中慕課網是不需要知道用戶的賬號密碼也可以完成對用戶身份的認證,這個token就可以用來標識用戶資源。
官方的運行流程圖是這樣的:

OAuth的幾種認證模式

上述講的是OAuth2中支持的授權碼(CODE)方式的認證流程,也是其支持的四種認證方式里最復雜的,其他的三種種包括:

  1. 簡化模式(implicit),(在redirect_uri 的Hash傳遞token; Auth客戶端運行在瀏覽器中,如JS,Flash)
  2. 密碼模式(resource owner password credentials),將用戶名,密碼傳過去,直接獲取token;
  3. 客戶端模式(client credentials),無用戶,用戶向客戶端注冊,然后客戶端以自己的名義向'服務端'獲取資源;
    詳細的OAuth2資料參考理解OAuth 2.0|阮一峰的網絡日志
    分別適用不同場景,復雜度也比授權碼模式要低,所以這里就只說說授權碼模式的具體過程。
CODE方式認證實例

假設現在有一個應用服務器跑在我本機8000端口,認證服務器在8090端口。在需要用戶登錄時候把用戶帶到以下的一個URL.

http://localhost:8090/oauth/authorize?response_type=code&scope=read write&client_id=test&redirect_uri=http://localhost:8000/login&state=09876999

我們注意到幾個重要的參數:

  • response_type:表示授權類型,就是上面講的那四種類型,這里用的是code方式。
  • client_id:表示客戶端的ID,代表哪個應用請求驗證
  • redirect_uri:表示重定向URI,驗證以后的回調地址,一般用來接收返回的code,以及做下一步處理。
  • scope:表示申請的權限范圍,
  • state:表示客戶端的當前狀態,可以指定任意值,認證服務器會原封不動地返回這個值。作為安全校驗。

下面是驗證服務器接受這個請求的控制器關鍵代碼:

@RequestMapping("authorize")
public void authorize(HttpServletRequest request, HttpServletResponse response) throws Exception {
    try {
         OAuthAuthxRequest oauthRequest = new OAuthAuthxRequest(request);
         if (oauthRequest.isCode()) {
            CodeAuthorizeHandler codeAuthorizeHandler = new CodeAuthorizeHandler(oauthRequest, response);
             LOG.debug("Go to  response_type = 'code' handler: {}", codeAuthorizeHandler);
             codeAuthorizeHandler.handle();
         } else if (oauthRequest.isToken()) {
             TokenAuthorizeHandler tokenAuthorizeHandler = new TokenAuthorizeHandler(oauthRequest, response);
             LOG.debug("Go to response_type = 'token' handler: {}", tokenAuthorizeHandler);
             tokenAuthorizeHandler.handle();
        } else {
             unsupportResponseType(oauthRequest, response);
            }
        } 
    }

首先拿到這個請求以后根據請求的參數將其封裝成一個OAuthAuthxRequest,基本就是把請求過來的參數,方法綁定便于使用。這是由oltu提供的OAuthRequest的進一步封裝。
然后判斷這個請求的授權的類型是否是code,也就是判斷下請求參數的response_type是否為code,可以看到目前制作了兩種類型的授權。
然后根據對應的授權類型,構造對應的方法處理器。下面是handle的實現接口:

  public void handle() throws OAuthSystemException, ServletException, IOException {
        //驗證請求是否合法,主要是針對參數做基本的校驗,重定向鏈接,客戶端ID授權范圍等這些信息與注冊的是否相同。
        if (validateFailed()) {
            return;
        }
        //判斷用戶是否登錄過,根據session判斷。因此多個應用使用同一個授權服務的話,是可以直接跳過登錄步驟的也就實現了單點登錄的效果。如果沒有登錄的話,這一步的請求會被重定向至登錄頁面。(登錄也得隱藏域會帶上這些參數)
        if (goLogin()) {
            return;
        }
        //這個請求如果是從登錄頁面提交過來的,那么就提交用戶的登錄,這個框架中交給shiro去做登錄相關的操作。
        if (submitLogin()) {
            return;
        }
        // 本系統中把登錄和授權放在兩個步驟中完成,有點像新浪微博的方式,QQ是一步完成授權。用戶未授權則跳轉授權頁面
        if (goApproval()) {
            return;
        }
       //與登錄類似,也是提交用戶批準或拒絕了權限請求
        if (submitApproval()) {
            return;
        }
        //以上任意一步沒有通過都是授權失敗會進行相應處理,如果都通過了就發放Code碼。
        handleResponse();
    }

如果以上步驟都通過的話,認證服務器會轉向這個會調地址,帶上發放的Code碼,類似如下:

http://localhost:8000/login?code=bca654ab6133ab3cbc55bb751da93b1c&state=09876999

可以看到帶回了返回的參數,以及原樣返回的狀態碼。
應用服務器這時候拿到返回的code去換token,發起如下的一個請求:

localhost:8090/oauth/token?client_id=test&client_secret=test&grant_type=authorization_code&code=bca654ab6133ab3cbc55bb751da93b1c&redirect_uri=http://localhost:8000/login&scope=read%20write&state=09876999

與之前請求類似只是多了一個code字段,去驗證客戶端的合法性。


驗證服務器會在收到code以后去查找是否有支持這種code的處理器,如果有則發放token。

for (OAuthTokenHandler handler : handlers) {
            if (handler.support(tokenRequest)) {
                LOG.debug("Found '{}' handle OAuthTokenxRequest: {}", handler, tokenRequest);
                handler.handle(tokenRequest, response);
                return;
            }
        }

初始化支持的handler

private void initialHandlers() {
        handlers.add(new AuthorizationCodeTokenHandler());
        handlers.add(new PasswordTokenHandler());
        handlers.add(new RefreshTokenHandler());
        handlers.add(new ClientCredentialsTokenHandler());
    }

驗證通過后應用服務器會接受到包含token的一個json數據:

{
"access_token": "23e003b5e4b9b7eda228b845532d8336",
"refresh_token": "d6b49710f398c405a62f31a6676c5830",
"token_type": "Bearer",
"expires_in": 43199
}

這個token是有一定的有效期的,在服務端會緩存這個token以便下一次查詢,應用客戶端也應該保留這個token,訪問受限資源時候需要帶上這個token去驗證身份。
比如請求一個API如下:

curl -i -X GET \
   -H "Authorization:Bearer 33dbfc80f5659c6fdec73a044ff724c3" \
 'http://localhost:8090/api/test'

資源服務器上使用shiro做安全驗證,配置OAuth2對應的realms即可:

<property name="realms">
<list>
    <bean id="systemAuthorizingRealm" class="me.kbiao.example.modules.sys.security.SystemAuthorizingRealm"/>
    <bean id="oAuth2Realm" class="me.kbiao.example..modules.sys.security.OAuth2Realm"/>
</list>
</property>

在這個reamls中根據token去查到用戶信息,再去分發對應的資源。
自此便完成了整個oauth2的流程。
這個流程中認證服務系統需要配置三張數據表:

  • client_details表中存放注冊的客戶端數據。如回調地址,授權類型,是否信任,權限信息等
  • code中存放發放給客戶端應用的code,使用后失效,以保證安全性
  • access_token中存放用戶信息、客戶端和token的對應關系。

項目是基于Shiro+ALTU實現,參考方案mkk/oauth2-shiro - 碼云 - 開源中國 ,更詳細的內容,可以去讀讀Shengzhao Li開源的代碼

總結

本文簡單介紹了幾種統一認證的解決方案,然后詳細介紹了OAuth2的認證流程,并結合實例詳細介紹了CODE授權的流程。盡管OAuth2被廣泛用于多平臺登錄解決方案,我覺得在設置cookie、session共享之后也可以被應用于單點登錄的解決方案。
在使用Oauth2做前后端分離時遇到的兩個跨域問題的解決方案可以參考我的兩篇博客

點擊查看更多內容

本文原創發布于慕課網 ,轉載請注明出處,謝謝合作

39人點贊

若覺得本文不錯,就分享一下吧!

評論

相關文章推薦

正在加載中
意見反饋 幫助中心 APP下載
官方微信

舉報

0/150
提交
取消
lpl竞猜