Tomcat 單點登錄配置及源碼分析

分類:技術 時間:2016-09-24

我們上網的時候,一定遇到過類似這樣的情況,例如使用網易郵箱時進行了登錄操作,之后再訪問網易的博客系統時,發現自動以之前的ID登錄了。這種實現在計算機中稱為 SSO (Single Sign On),即我們常說的 單點登錄 。這種在關聯網站間共享認證信息,避免需要在多個系統中重復輸入帳戶信息的行為,是SSO要解決的。

對于許多應用,可能會獨立部署等情況,所以常會采用cas的形式,來實現SSO。

我們今天要了解的,是作為在同一個Tomcat中部署的應用之間,如何實現SSO,避免重復登錄。

預備:

首先,有幾點預備知識需要先了解一下。

  1. 在Tomcat架構設計中,不同的Container中包含了Peipline。各個Pipeline中可以添加多種不同形式的Valve。例如我們之前提到的AccessLogValve

    Tomcat的AccessLogValve介紹

  2. Tomcat中session的實現,最常用的是Cookie Session, 通過將名為 JSESSIONID 的cookie寫回瀏覽器,實現session。我們在前面的文章里也描述過。 深入Tomcat源碼分析Session

  3. 關于認證的一些內容,可以參考介紹過的Basic認證。 你可能不了解的Basic認證

環境:

有了這些準備之后,我們開始進行環境的搭建和實驗。

以Tomcat自帶的幾個應用為例,我們啟動Tomcat后,訪問這兩個應用: docsexamples 我們看到,默認是不需要登錄的,都可以直接訪問。

此時,在docs應用的web.xml中增加如下配置:

lt;security-constraintgt;

ADVERTISEMENT

lt;display-namegt;SecurityConstraintlt;/display-namegt;

lt;web-resource-collectiongt;

lt;web-resource-namegt;ProtectedArealt;/web-resource-namegt;

lt;url-patterngt;/*lt;/url-patterngt;

lt;/web-resource-collectiongt;

lt;auth-constraintgt;

lt;role-namegt;tomcatlt;/role-namegt;

lt;/auth-constraintgt;

lt;/security-constraintgt;

lt;login-configgt;

ADVERTISEMENT

lt;auth-methodgt;BASIClt;/auth-methodgt;

lt;realm-namegt;SSOTestlt;/realm-namegt;

lt;/login-configgt;

lt;security-rolegt;

lt;role-namegt;tomcatlt;/role-namegt;

lt;/security-rolegt;

此時重啟Tomcat,再次請求docs應用,發現需要驗證了。

同樣,再修改examples應用的web.xml,限制對于其直接訪問,在文件中增加如下內容: lt;url-patterngt;/*lt;/url-patterngt; 。只需要增加這個就可以了,下面是修改內容對應的位置參考。

lt;web-resource-collectiongt;

ADVERTISEMENT

lt;web-resource-namegt;ProtectedArea-Allowmethodslt;/web-resource-namegt;

lt;url-patterngt;/jsp/security/protected/*lt;/url-patterngt;

lt;url-patterngt; /* lt;/url-patterngt;

lt;http-methodgt;DELETElt;/http-methodgt;

lt;http-methodgt;GETlt;/http-methodgt;

lt;http-methodgt;POSTlt;/http-methodgt;

lt;http-methodgt;PUTlt;/http-methodgt;

lt;/web-resource-collectiongt;

修改之后,examples也需要登錄才能訪問了。由于同樣的認證,我們對兩個應用的訪問需要重復輸入用戶名、密碼進行認證,此時,SSO的配置就顯出了必要性了。

在Tomcat的server.xml中,默認的Host,localhost中,增加以下Valve:

lt;ValveclassName=quot;org.apache.catalina.authenticator.SingleSignOnquot;/gt;

再次重啟Tomcat,這個時候SSO已經生效了,你再重新訪問上面兩個應用時,只需要對其中一個進行認證即可,是不是很容易?

原理:

在前面分析請求流程的幾篇文章中,我們介紹過從CoyoteAdapter進行service處理,再到達各個Pipeline、Valve。(Facade模式與請求處理)

而這些Valve中,對于SSO的Valve SingleSignOn 是在認證的Valve AuthenticatorBase 之前執行。

在SingleSignOn中,會先進行userPrincipal的判斷,不為空就會直接向后執行,為空時,判斷請求中是否包含SSO Cookie。

if(request.getUserPrincipal()!=null){

getNext().invoke(request,response);

return;

}

//Checkforthesinglesignoncookie

Cookiecookie=null;

Cookiecookies[]=request.getCookies();

if(cookies!=null){

for(inti=0;ilt;cookies.length;i ){

if(Constants.SINGLE_SIGN_ON_COOKIE.equals(cookies[i].getName())){

cookie=cookies[i];

break;

}

}

}

if(cookie==null){

getNext().invoke(request,response);

return;

}

對于第一個就進認證的應用,走的流程基本和配置之前一樣,區別就在于SSO配置后,會把認證的信息,添加到Cookie中。并將其存儲并和一個ssoId進行關聯。

BasicAuthenticator:

對于docs應用,使用的是Basic認證方式

principal=context.getRealm().authenticate(username,password);

if(principal!=null){

register(request,response,principal,

HttpServletRequest.BASIC_AUTH,username,password);

return(true);

}

}

FormAuthenticator

對于examples應用,使用的是Form的認證方式,如果是Form認證的應用不是第一個請求,則在請求到達時,已經進行過認證,后面的請求會直接獲取session并關聯到ssoId上。 如果是初次請求即訪問Form認證的應用,SsoId還沒值,流程基本和Basic一樣,不同的是從表單中提取用戶名和密碼信息,再進行 register

Principalprincipal=request.getUserPrincipal();

StringssoId=(String)request.getNote(Constants.REQ_SSOID_NOTE);

if(principal!=null){

//AssociatethesessionwithanyexistingSSOsession

if(ssoId!=null){

associate(ssoId,request.getSessionInternal(true));//注意這里,把新獲取到的sessionId關聯到ssoId中

}

returntrue;

}

這里register會把認證的信息添加,在ssoId為空時,進行Cookie的創建,

StringssoId=(String)request.getNote(Constants.REQ_SSOID_NOTE);

if(ssoId==null){

ssoId=sessionIdGenerator.generateSessionId();

Cookiecookie=newCookie(Constants.SINGLE_SIGN_ON_COOKIE,ssoId);

cookie.setMaxAge(-1);

cookie.setPath(quot;/quot;);

//Bugzilla41217

cookie.setSecure(request.isSecure());

//Bugzilla34724

StringssoDomain=sso.getCookieDomain();

if(ssoDomain!=null){

cookie.setDomain(ssoDomain);

}

//ConfigurehttpOnlyonSSOcookieusingsamerulesassessioncookies

if(request.getServletContext().getSessionCookieConfig().isHttpOnly()||

request.getContext().getUseHttpOnly()){

cookie.setHttpOnly(true);

}

response.addCookie(cookie);

//RegisterthisprincipalwithourSSOvalve

sso.register(ssoId,principal,authType,username,password);

request.setNote(Constants.REQ_SSOID_NOTE,ssoId);

Cookie不為空時,進行ssoId和session的關聯

protectedbooleanassociate(StringssoId,Sessionsession){

SingleSignOnEntrysso=cache.get(ssoId);

if(sso==null){

if(containerLog.isDebugEnabled()){

containerLog.debug(sm.getString(quot;singleSignOn.debug.associateFailquot;,

ssoId,session));

}

returnfalse;

}else{

}

sso.addSession(this,ssoId,session);

returntrue;

}

}

我們注意到這行代碼 sso.addSession(this, ssoId, session) 這里會給session添加一個listener,這個listener會在session過期銷毀時,把sso的session也移除掉

應用的SSO

在Pipeline中從SingleSignOn這個Valve開始,一直調用到AuthenticatorBase,再到達其實現類. SingleSignOn這個Valve處理請求時,判斷entry是否為空,此時由于前面的應用已經存儲過該信息,所以這里不為空,就會據此設置request中的authType和principal

SingleSignOnEntryentry=cache.get(cookie.getValue());

if(entry!=null){

request.setNote(Constants.REQ_SSOID_NOTE,cookie.getValue());

//Onlysetsecurityelementsifreauthenticationisnotrequired

if(!getRequireReauthentication()){

request.setAuthType(entry.getAuthType());

request.setUserPrincipal(entry.getPrincipal());

}

而后面的Valve中,認證時首先會判斷principal是否為空。由于前置的sso已經把這些信息填充過了,所以這里就會走這樣的邏輯:

publicvoidinvoke(Requestrequest,Responseresponse)

throwsIOException,ServletException{

//HavewegotacachedauthenticatedPrincipaltorecord?

if(cache){

Principalprincipal=request.getUserPrincipal();//這里不為空

if(principal==null){

Sessionsession=request.getSessionInternal(false);

if(session!=null){

principal=session.getPrincipal();

if(principal!=null){

request.setAuthType(session.getAuthType());

request.setUserPrincipal(principal);

}

}

}

}

總結一下:

單點登錄的實現,是在第一次進行認證的時候,將認證信息進行存儲。后續相同域的請求到達時,會先判斷是否存儲了單點登錄的認證信息,如果已經存儲過,就將其添加到新到達的request中,以此進行后續的認證,從而實現SSO.

PS. 微信公眾號里,代碼的羅列真心不好弄,通過其他的Markdown編輯器預覽效果很不錯的,粘過來就變了形了。各位如果有好的工具或辦法,歡迎留言或私信,謝謝。

    相關閱讀

    1. 深入Tomcat源碼分析Session到底是個啥!

    2. 對于過期的session,Tomcat做了什么?

    3. 禁用Cookie后,Session怎么樣使用?

    4. Tomcat的AccessLogValve介紹

    5. 詳解集群內Session高可用的實現原理

    6. 快看Apache那個二道販子

    猜你喜歡

    1. 深度揭秘亂碼問題背后的原因及解決方式

    2. WEB應用是怎么被部署的?

    3. 怎樣調試Tomcat源碼

    4. IDE里的Tomcat是這樣工作的!

    5. 重定向與轉發的本質區別

    6. 怎樣閱讀源代碼

掃描或長按下方二維碼,即可關注!


Tags: Tomcat

文章來源:http://mp.weixin.qq.com/s?__biz=MzI3MTEwODc5Ng==



相關文章
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • Oracle11g的安裝 2016-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01