您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“Tomcat容器的安全認證和鑒權(quán)講解”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“Tomcat容器的安全認證和鑒權(quán)講解”吧!
1. 授權(quán)
容器和 Web 應(yīng)用采用的是基于角色的權(quán)限訪問控制方式,其中容器需要實現(xiàn)認證和鑒權(quán)的功能,而 Web 應(yīng)用則要實現(xiàn)授權(quán)的功能。
在 Servlet 規(guī)范中描述了兩種授權(quán)方式:聲明式安全和編程式安全。聲明式安全就是在部署描述符中聲明角色、資源訪問權(quán)限和認證方式。以下代碼片段摘自 Tomcat 自帶的 Manager 應(yīng)用的 web.xml:
<security-constraint> <!-- 安全約束 --> <web-resource-collection> <!-- 限制訪問的資源集合 --> <web-resource-name>HTML Manager commands</web-resource-name> <url-pattern>/html/*</url-pattern> </web-resource-collection> <auth-constraint><!-- 授權(quán)可訪問此資源集合的角色 --> <role-name>manager-gui</role-name> </auth-constraint> </security-constraint> <login-config><!-- 配置驗證方法 --> <auth-method>BASIC</auth-method> <realm-name>Tomcat Manager Application</realm-name> </login-config> <security-role><!-- 定義一個安全角色 --> <description> The role that is required to access the HTML Manager pages </description> <role-name>manager-gui</role-name> </security-role>
這些安全相關(guān)的配置,都會在應(yīng)用部署時,初始化和設(shè)置到 StandardContext 對象中。更多詳細的內(nèi)容可查看規(guī)范對部署描述文件的解釋,接下來看 Tomcat 怎么設(shè)計和實現(xiàn)認證及鑒權(quán)。
2. 認證和鑒權(quán)的設(shè)計
Servlet 規(guī)范雖然描述了 Web 應(yīng)用聲明安全約束的機制,但沒有定義容器與關(guān)聯(lián)用戶和角色信息之間的接口。因此,Tomcat 定義了一個 Realm 接口,用于適配身份驗證的各種信息源。整體設(shè)計的類圖如下:
上圖中,包含了各個類的核心方法,關(guān)鍵類或接口的作用如下:
Realm - 譯為域,域有泛指某種范圍的意思,在這個范圍內(nèi)存儲著用戶名、密碼、角色和權(quán)限,并且提供身份和權(quán)限驗證的功能,典型的這個范圍可以是某個配置文件或數(shù)據(jù)庫
CombinedRealm - 內(nèi)部包含一個或多個 Realm,按配置順序執(zhí)行身份驗證,任一 Realm 驗證成功,則表示成功驗證
LockOutRealm - 提供用戶鎖定機制,防止在一定時間段有過多身份驗證失敗的嘗試
Authenticator - 不同身份驗證方法的接口,主要有 BASIC、DIGEST、FORM、SSL 這幾種標(biāo)準(zhǔn)實現(xiàn)
Principal - 對認證主體的抽象,它包含用戶身份和權(quán)限信息
SingleSignOn - 用于支持容器內(nèi)多應(yīng)用的單點登錄功能
2.1 初始化
Realm 是容器的一個可嵌套組件,可以嵌套在 Engine、Host 和 Context 中,并且子容器可以覆蓋父容器配置的 Realm。默認的 server.xml 在 Engine 中配置了一個 LockOutRealm 組合域,內(nèi)部包含一個 UserDatabaseRealm,它從配置的全局資源 conf/tomcat-users.xml 中提取用戶信息。
web.xml 中聲明的安全約束會初始化成對應(yīng)的 SecurityConstraint、SecurityCollection 和 LoginConfig 對象,并關(guān)聯(lián)到一個 StandardContext 對象。
在上圖可以看到,AuthenticatorBase 還實現(xiàn)了 Valve 接口,StandardContext 對象在配置的過程中,如果發(fā)現(xiàn)聲明了標(biāo)準(zhǔn)的驗證方法,那么就會把它加入到自己的 Pipeline 中。
3. 一次請求認證和鑒權(quán)過程
Context 在 Tomcat 內(nèi)部就代表著一個 Web 應(yīng)用,假設(shè)配置使用 BASIC 驗證方法,那么 Context 內(nèi)部的 Pipeline 就有 BasicAuthenticator 和 StandardContextValve 兩個閥門,當(dāng)請求進入 Context 管道時,就首先進行認證和鑒權(quán),方法調(diào)用如下:
整個過程的核心代碼就在 AuthenticatorBase 的 invoke 方法中:
public void invoke(Request request, Response response) throws IOException, ServletException { LoginConfig config = this.context.getLoginConfig(); // 0. Session 對象中是否緩存著一個已經(jīng)進行身份驗證的 Principal if (cache) { Principal principal = request.getUserPrincipal(); if (principal == null) { Session session = request.getSessionInternal(false); if (session != null) { principal = session.getPrincipal(); if (principal != null) { request.setAuthType(session.getAuthType()); request.setUserPrincipal(principal); } } } } // 對于基于表單登錄,可能位于安全域之外的特殊情況進行處理 String contextPath = this.context.getPath(); String requestURI = request.getDecodedRequestURI(); if (requestURI.startsWith(contextPath) && requestURI.endsWith(Constants.FORM_ACTION)) { return; } } // 獲取安全域?qū)ο?,默認配置是 LockOutRealm Realm realm = this.context.getRealm(); // 根據(jù)請求 URI 嘗試獲取配置的安全約束 SecurityConstraint [] constraints = realm.findSecurityConstraints(request, this.context); if ((constraints == null) /* && (!Constants.FORM_METHOD.equals(config.getAuthMethod())) */ ) { // 為 null 表示訪問的資源沒有安全約束,直接訪問下一個閥門 getNext().invoke(request, response); return; } // 確保受約束的資源不會被 Web 代理或瀏覽器緩存,因為緩存可能會造成安全漏洞 if (disableProxyCaching && !"POST".equalsIgnoreCase(request.getMethod())) { if (securePagesWithPragma) { response.setHeader("Pragma", "No-cache"); response.setHeader("Cache-Control", "no-cache"); } else { response.setHeader("Cache-Control", "private"); } response.setHeader("Expires", DATE_ONE); } int i; // 1. 檢查用戶數(shù)據(jù)的傳輸安全約束 if (!realm.hasUserDataPermission(request, response, constraints)) { // 驗證失敗 // Authenticator已經(jīng)設(shè)置了適當(dāng)?shù)腍TTP狀態(tài)代碼,因此我們不必做任何特殊的事情 return; } // 2. 檢查是否包含授權(quán)約束,也就是角色驗證 boolean authRequired = true; for(i=0; i < constraints.length && authRequired; i++) { if(!constraints[i].getAuthConstraint()) { authRequired = false; } else if(!constraints[i].getAllRoles()) { String [] roles = constraints[i].findAuthRoles(); if(roles == null || roles.length == 0) { authRequired = false; } } } // 3. 驗證用戶名和密碼 if(authRequired) { // authenticate 是一個抽象方法,由不同的驗證方法實現(xiàn) if (!authenticate(request, response, config)) { return; } } // 4. 驗證用戶是否包含授權(quán)的角色 if (!realm.hasResourcePermission(request, response,constraints,this.context)) { return; } // 5. 已滿足任何和所有指定的約束 getNext().invoke(request, response); }
另外,AuthenticatorBase 還有一個比較重要的 register() 方法,它會把認證后生成的 Principal 對象設(shè)置到當(dāng)前 Session 中,如果配置了SingleSignOn 單點登錄的閥門,同時把用戶身份、權(quán)限信息關(guān)聯(lián)到 SSO 中。
4. 單點登錄
Tomcat 支持通過一次驗證就能訪問部署在同一個虛擬主機上的所有 Web 應(yīng)用,可通過以下配置實現(xiàn):
<Host name="localhost" ...> ... <Valve className="org.apache.catalina.authenticator.SingleSignOn"/> ... </Host>
Tomcat 的單點登錄是利用 Cookie 實現(xiàn)的:
當(dāng)任一 Web 應(yīng)用身份驗證成功后,都會把用戶身份信息緩存到 SSO 中,并生成一個名為 JSESSIONIDSSO 的 Cookie
當(dāng)用戶再次訪問這個主機時,會通過 Cookie 拿出存儲的用戶 token,獲取用戶 Principal 并關(guān)聯(lián)到 Request 對象中
在單機環(huán)境下,沒有問題,在集群環(huán)境下,Tomcat 支持 Session 的復(fù)制,那單點登錄相關(guān)的信息也會同步復(fù)制嗎?后續(xù)會繼續(xù)分析 Tomcat 集群的原理和實現(xiàn)。
5. 小結(jié)
本文介紹的是 Tomcat 內(nèi)部實現(xiàn)的登錄認證和權(quán)限,而應(yīng)用程序通常都是通過 Filter 或者自定義的攔截器(如 Spring 的 Interceptor)實現(xiàn)登錄,或者使用第三方安全框架,比如 Shiro,但是原理都差不多。
到此,相信大家對“Tomcat容器的安全認證和鑒權(quán)講解”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。