成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Tomcat安全域?qū)崿F(xiàn)細(xì)節(jié)分析

開發(fā) 開發(fā)工具
本文重點(diǎn)介紹了Tomcat安全域部分的實(shí)現(xiàn),結(jié)合部署描述符web.xml中的配置,講解了Tomcat安全域?qū)φJ(rèn)證、授權(quán)工作的流程處理。

一、簡介

為了實(shí)現(xiàn) Servlet 規(guī)范中規(guī)定的對于特定資源的保護(hù),Tomcat 提供了安全域的功能實(shí)現(xiàn)。如果應(yīng)用使用了安全域保護(hù)系統(tǒng)資源,安全域就需要對每一次的訪問負(fù)責(zé),結(jié)合 Tomcat的訪問流程,可以想到安全域的認(rèn)證器是作為一個閥門(Valve)來實(shí)現(xiàn)的。

Tomcat實(shí)現(xiàn)了多種多樣的安全域滿足不同的用戶需求:

  • 配置快捷、利用數(shù)據(jù)源進(jìn)行認(rèn)證的DataSourceRealm,
  • 更為簡易的JDBCRealm,
  • 通過第三方Ldap服務(wù)器認(rèn)證的JNDIRealm,
  • 限制失敗次數(shù)防止暴力破解的LockOutRealm,
  • 通過文本文件配置用戶信息、一般用于開發(fā)、測試的MemoryRealm,

Tomcat安全域的默認(rèn)實(shí)現(xiàn)UserDatabaseRealm、靈活的用戶自定義的JaasRealm,他們都實(shí)現(xiàn)了Realm接口,并擁有共同的父類RealmBase。

二、Realm接口

Realm接口是安全域模塊的核心接口,其提供了幾個重要的方法:authenticate()方法以及多個重載用于提供用戶名、密碼等方式的認(rèn)證功能;hasResourcePermission()方法用于認(rèn)證器(Authenticator)判斷當(dāng)前角色是否有權(quán)限訪問資源,該方法通過調(diào)用hasRole()等方法進(jìn)行判斷;而hasUserDataPermission()方法則對數(shù)據(jù)傳輸層的傳輸要求進(jìn)行判斷。

Realm接口

通過上述幾個方法,就能夠大致勾勒出一次通過安全域的請求訪問的流程:用戶請求資源,經(jīng)過各層閥門后走到安全域(認(rèn)證器),安全域首先要判斷對目標(biāo)資源的請求是否符合數(shù)據(jù)傳輸層的要求;然后通過authenticate()方法判斷當(dāng)前用戶是否經(jīng)過認(rèn)證,如果沒有認(rèn)證則向用戶請求認(rèn)證信息;通過認(rèn)證后,則進(jìn)行角色和權(quán)限的判斷;最后,根據(jù)認(rèn)證結(jié)果繼續(xù)請求流程或者直接返回請求拒絕信息。

Realm接口使用了Principal、SecurityConstraint、X509Certificate等接口或類,Principal是jdk api定義的表示主體的抽象概念,X509Certificate是jdk api定義的X.509 證書的抽象類,該類提供了一種訪問 X.509 證書所有屬性的標(biāo)準(zhǔn)方式,SecurityConstraint則是tomcat定義的對web.xml中相應(yīng)元素的抽象實(shí)現(xiàn)。

三、RealmBase抽象類

RealmBase類對Realm接口中的大部分方法進(jìn)行了實(shí)現(xiàn),前面說到安全域的authenticate()認(rèn)證方法提供了多種重載,其目的就是為了適用各種環(huán)境下的認(rèn)證方式,畢竟并不是所有的認(rèn)證信息都可以用用戶名和密碼的方式傳遞的。

例如,冗長的認(rèn)證方法authenticate(String username, String clientDigest,String nonce, String nc, String cnonce,String qop, String realm,String md5a2)是為了Digest認(rèn)證而設(shè)計的,而簡約的authenticate(X509Certificate certs[])方法則對應(yīng)https協(xié)議下的證書認(rèn)證。由于特殊的需求,RealmBase的部分子類仍會重寫authenticate()方法。相關(guān)的認(rèn)證方法及實(shí)現(xiàn),在后續(xù)介紹HTTP認(rèn)證方式時再詳細(xì)介紹。

在進(jìn)行真正的認(rèn)證工作前,有一步非常重要的校驗(yàn)工作,即當(dāng)前請求是否滿足定義的支持的連接類型,該處的邏輯處理由hasUserDataPermission()方法完成。

如果在web.xml的user-data-constraint節(jié)點(diǎn)定義了連接類型,而且連接類型不為NONE的話,Tomcat則會認(rèn)為該請求需要建立在安全的連接之上,按照servlet規(guī)范定義,通過查找當(dāng)前請求連接器的HTTPS重定向端口,該請求通過response.sendRedirect()的方式跳轉(zhuǎn)到https請求。如果當(dāng)前的請求連接器并沒有配置有效的HTTPS重定向端口,則返回403 (SC_FORBIDDEN)狀態(tài)碼。

如果通過了前面提到的安全域認(rèn)證,這說明了用戶提供的用戶名、密碼等憑證是有效的,但這還不能夠說明當(dāng)前用戶對目標(biāo)資源具有訪問的權(quán)限,所以要經(jīng)過

hasResourcePermission()方法,進(jìn)行用戶“授權(quán)”的工作。前面提到,類SecurityConstraint是對web.xml中相應(yīng)元素的抽象實(shí)現(xiàn),元素定義了全部的安全限制條件:受保護(hù)的資源,受保護(hù)的HTTP方法、可訪問受保護(hù)資源的用戶角色以及對數(shù)據(jù)傳輸?shù)囊蟆R粋€典型的元素如下所示:

  1. <security-constraint> 
  2. <web-resource-collection> 
  3. <web-resource-name>Protected Area</web-resource-name> 
  4. <url-pattern>/adminInfo/*</url-pattern> 
  5. <http-method>GET</http-method> 
  6. <http-method>POST</http-method> 
  7. </web-resource-collection> 
  8. <auth-constraint> 
  9. <role-name>admin</role-name> 
  10. </auth-constraint> 
  11. <user-data-constraint> 
  12. <transport-guarantee>CONFIDENTIAL</transport-guarantee> 
  13. </user-data-constraint> 
  14. </security-constraint> 

為了進(jìn)行最后的授權(quán)工作,需要將用戶的當(dāng)前角色與web.xml定義的角色進(jìn)行比對。不要小看短短的幾行配置文件,Servlet規(guī)范進(jìn)行了詳盡的描述,Tomcat也遵循規(guī)范進(jìn)行了細(xì)致的實(shí)現(xiàn)。例如:role-name的配置,通常來說,會按照業(yè)務(wù)需求將其配置為具有相應(yīng)權(quán)限的用戶名稱,但對于通配符“*”賦予了特殊的含義。

“*”表示web.xml中定義的所有用戶,“**”則表示所有通過了認(rèn)證的用戶。同時對于role-name為空的情況,則任何用戶都不能訪問相應(yīng)的資源。

在認(rèn)證階段,如果用戶沒有通過認(rèn)證,或者是第一次訪問,則會拒絕該請求并返回401(SC_UNAUTHORIZED)狀態(tài)碼(FORM類型的認(rèn)證除外,因?yàn)橐D(zhuǎn)至登錄頁面);如果用戶角色沒有滿足預(yù)先定義的權(quán)限,則會拒絕該請求并返回403 (SC_FORBIDDEN)狀態(tài)碼。

四、HTTP認(rèn)證方法與實(shí)現(xiàn)

下面簡單介紹JavaEE平臺支持的四種認(rèn)證機(jī)制:

  • Basic authentication
  • Form-based authentication
  • Digest authentication
  • Client authentication

Tomcat為實(shí)現(xiàn)上述認(rèn)證機(jī)制,提供了多種認(rèn)證器,如下圖所示。認(rèn)證器位處于安全域前端,對于不同類型的HTTP認(rèn)證方式,先由各認(rèn)證器根據(jù)相應(yīng)的規(guī)范對客戶端發(fā)送至服務(wù)端的信息進(jìn)行解析,然后再交由安全域進(jìn)行處理,例如前面提到的對當(dāng)前請求是否滿足定義的支持連接類型的判斷就是由認(rèn)證器發(fā)起的。各認(rèn)證器都繼承了AuthenticatorBase抽象類,其中重要的authenticate()方法由各實(shí)現(xiàn)類進(jìn)行具體的實(shí)現(xiàn)。

HTTP認(rèn)證方法與實(shí)現(xiàn)

BASIC認(rèn)證

BASIC基本認(rèn)證是HTTP1.0標(biāo)準(zhǔn)提出的認(rèn)證方式,規(guī)范中即提出BASIC認(rèn)證是不安全的用戶認(rèn)證方案,并支持在目前日益嚴(yán)重的網(wǎng)絡(luò)安全問題面前采用更加復(fù)雜的其他認(rèn)證方式及加密機(jī)制。因此,對于非SSL層請求的認(rèn)證,不建議使用BASIC認(rèn)證;但如果請求是在安全的傳輸層上,傳輸層提供了安全保障,即使是簡單加密的BASIC認(rèn)證也可以認(rèn)為是安全的。BASIC認(rèn)證的規(guī)則如下:

  1. 客戶端訪問受保護(hù)的資源。
  2. 服務(wù)器返回401 Unauthorized狀態(tài),響應(yīng)頭信息如下圖所示,其中WWW-Authenticate:Basic realm="MyRealm"表示該資源的受保護(hù)信息。
  3. 瀏覽器根據(jù)響應(yīng)彈出窗口,提示用戶輸入用戶名和密碼。
  4. 瀏覽器將客戶端將輸入的用戶名、密碼用Base64算法進(jìn)行加密后發(fā)送給服務(wù)器。例如,使用用戶名、密碼都是“java”進(jìn)行登錄,瀏覽器則發(fā)送的請求頭中包含“Authorization: Basic amF2YTpqYXZh”,其中“amF2YTpqYXZh”是用戶名、密碼組成的字符串“java:java”進(jìn)行Base64加密得到的結(jié)果。
  5. 如果認(rèn)證成功,則返回相應(yīng)的受保護(hù)資源。如果認(rèn)證失敗,則仍返回401 Unauthorized狀態(tài),要求重新進(jìn)行認(rèn)證。

BASIC認(rèn)證

可以簡單了解一下Tomcat的BASIC認(rèn)證器類BasicAuthenticator的關(guān)鍵代碼:

  1. public boolean authenticate(Request request, HttpServletResponse response) 
  2. throws IOException { 
  3.  
  4. if (checkForCachedAuthentication(request, response, true)) { 
  5. return true; 
  6.     } 
  7.  
  8. // Validate any credentials already included with this request 
  9. MessageBytes authorization = 
  10.         request.getCoyoteRequest().getMimeHeaders() 
  11.         .getValue("authorization");//獲取authorization請求頭 
  12.  
  13. if (authorization != null) { 
  14.         authorization.toBytes(); 
  15.         ByteChunk authorizationauthorizationBC = authorization.getByteChunk(); 
  16.         BasicCredentials credentials = null
  17. try { 
  18.             credentials = new BasicCredentials(authorizationBC);//Base64解密用戶名密碼 
  19.             String username = credentials.getUsername(); 
  20.             String password = credentials.getPassword(); 
  21.  
  22.             Principal principal = context.getRealm().authenticate(username, password);//安全域認(rèn)證 
  23. if (principal != null) {//認(rèn)證成功 
  24.                 register(request, response, principal, 
  25.                     HttpServletRequest.BASIC_AUTH, username, password); 
  26. return (true); 
  27.             } 
  28.         } 
  29. catch (IllegalArgumentException iae) { 
  30. if (log.isDebugEnabled()) { 
  31. log.debug("Invalid Authorization" + iae.getMessage()); 
  32.             } 
  33.         } 
  34.     } 
  35.  
  36. // the request could not be authenticated, so reissue the challenge 
  37. StringBuilder value = new StringBuilder(16);//認(rèn)證失敗返回重新認(rèn)證 
  38.     value.append("Basic realm=\""); 
  39.     value.append(getRealmName(context)); 
  40.     value.append('\"'); 
  41.     response.setHeader(AUTH_HEADER_NAME, value.toString()); 
  42.     response.sendError(HttpServletResponse.SC_UNAUTHORIZED); 
  43. return (false); 
  44.  

該認(rèn)證方法的基本邏輯還是比較清晰的:

  1. 首先判斷是否已經(jīng)進(jìn)行了認(rèn)證,如果已經(jīng)認(rèn)證則沒有必要重復(fù)認(rèn)證,返回即可。
  2. 嘗試取出“authorization”請求頭,如果沒有該請求頭,則返回401Unauthorized狀態(tài)碼以及受保護(hù)資源的安全域信息。
  3. 對“authorization”請求頭進(jìn)行BASIC64解密,然后使用“:”切分為用戶名和密碼。
  4. 使用安全域?qū)τ脩裘⒚艽a進(jìn)行真正的認(rèn)證工作,如果認(rèn)證成功,將當(dāng)前用戶信息進(jìn)行緩存。

Form認(rèn)證

Basic認(rèn)證和后面介紹的Digest認(rèn)證都是rfc2616中明確定義的認(rèn)證方式,拋開安全性,兩者在實(shí)際使用中均有一個嚴(yán)重的缺點(diǎn),即用戶UI幾乎無設(shè)計的問題。在用戶體驗(yàn)至高無上的互聯(lián)網(wǎng)時代,UI界面占據(jù)著很大的比重,而Basic和Digest認(rèn)證由于其自身的設(shè)計,各瀏覽器的實(shí)現(xiàn)都是彈出一個無所謂美觀的對話框,對用戶體驗(yàn)有很大的影響。Form認(rèn)證中定義了采集用戶信息的登錄頁面、登錄失敗頁面,通過用戶自定義實(shí)現(xiàn)這兩個頁面,能夠完成美觀的登錄操作。在web.xml中配置Form認(rèn)證方式及登錄頁面示例如下:

  1. <login-config> 
  2.     <auth-method>FORM</auth-method> 
  3.     <realm-name>file</realm-name> 
  4.     <form-login-config> 
  5.         <form-login-page>/login.xhtml</form-login-page> 
  6.         <form-error-page>/error.xhtml</form-error-page> 
  7.     </form-login-config> 
  8. </login-config> 

在Servlet規(guī)范中規(guī)定,使用Form認(rèn)證時,表單提交的action必須為j_security_check,而獲取登錄信息的字段必須為j_username和j_password,這樣的約定省去了相關(guān)字段的配置工作。From認(rèn)證的邏輯也很清晰,下面抽取tomcat的關(guān)鍵代碼進(jìn)行解釋:

  1. 查看是否已經(jīng)對當(dāng)前用戶進(jìn)行了認(rèn)證,避免重復(fù)認(rèn)證造成資源浪費(fèi):checkForCachedAuthentication(request, response, true)。
  2. 如果沒有認(rèn)證,則需要保存當(dāng)前用戶需要保存的頁面:saveRequest(request, session),然后跳轉(zhuǎn)到登錄頁面:forwardToLoginPage(request, response, config)。
  3. 用戶提交了用戶名和密碼,進(jìn)行認(rèn)證工作:principal = realm.authenticate(username, password);如果認(rèn)證失敗,則跳轉(zhuǎn)至失敗頁面:forwardToErrorPage(request, response, config);如果認(rèn)證成功,則跳轉(zhuǎn)至第二步保存的頁面:response.sendRedirect(response.encodeRedirectURL(uri))。
  4. 瀏覽器接收到302重定向狀態(tài)碼后,將頁面跳轉(zhuǎn)至最初訪問的頁面。
  5. 再次走進(jìn)Form認(rèn)證器的認(rèn)證流程,通過判斷條件matchRequest(request)將認(rèn)證主體(Principal)保存在Request和Session中,判斷條件為:已經(jīng)通過了認(rèn)證;存在一個已保存的頁面且與當(dāng)前請求頁面路徑相同。然后將本次請求的所有信息都重置為最初的請求信息:restoreRequest(request, session)。此后的訪問在第一步即直接返回了。

有興趣的讀者可以深入的了解一下上述的關(guān)鍵代碼的實(shí)現(xiàn)。

Digest認(rèn)證

Digest摘要認(rèn)證是在HTTP1.1中提出的替代Basic認(rèn)證的方法。由于Basic認(rèn)證使用的的Base64加密幾乎等于明文傳輸,安全性低,Digest認(rèn)證提供了一種不使用明文發(fā)送用戶名密碼的方式。當(dāng)然,HTTP1.1標(biāo)準(zhǔn)也提出,摘要訪問認(rèn)證語法("Digest Access Authentication scheme")并非要提供一個網(wǎng)絡(luò)安全的完美解決方案,其目的僅僅是為了避免深受詬病的Basic認(rèn)證的諸多缺點(diǎn)。因此,不管怎樣,相比較Basic認(rèn)證,Digest認(rèn)證的安全性還是有所提高的。Digest認(rèn)證的規(guī)則如下:

1.客戶端訪問受保護(hù)的資源。

2.服務(wù)器返回401 Unauthorized狀態(tài),響應(yīng)頭信息如下圖所示,其中

  1. WWW-Authenticate:Digest realm="MyRealm"qop="auth"nonce="1454307975468:a0aefce3e84d69723e6f04fda5674ad0"opaque="23BB4CB60BFE2CD08B490A16B86C9661" 

表示相關(guān)的安全域信息、隨機(jī)數(shù)信息(nonce)等。

3.瀏覽器根據(jù)響應(yīng)彈出窗口,提示用戶輸入用戶名和密碼。

4.瀏覽器將客戶端將輸入的用戶名以明文的方式、密碼等其他信息以摘要的方式返回給服務(wù)端。

5.服務(wù)端將用戶名、正確的密碼等信息按規(guī)則進(jìn)行摘要加密,與客戶端提供的信息進(jìn)行比對。如果認(rèn)證成功,則返回相應(yīng)的受保護(hù)資源。如果認(rèn)證失敗,則仍返回401 Unauthorized狀態(tài),要求重新進(jìn)行認(rèn)證。

其中的隨機(jī)數(shù)nonce的值應(yīng)當(dāng)是永不重復(fù)的數(shù)值,下面看一下tomcat是怎樣簡單的實(shí)現(xiàn)并保證唯一性的:

  1. protected String generateNonce(Request request) { 
  2.  
  3. long currentTime = System.currentTimeMillis(); 
  4.  
  5. synchronized (lastTimestampLock) {//加鎖,并發(fā)下也不會取到相同的時間 
  6. if (currentTime > lastTimestamp) { 
  7. lastTimestamp = currentTime
  8.         } else { 
  9.             currentTime = ++lastTimestamp; 
  10.         } 
  11.     } 
  12.  
  13.     String ipTimeKey = 
  14.         request.getRemoteAddr() + ":" + currentTime + ":" + getKey(); 
  15.  
  16. byte[] buffer = ConcurrentMessageDigest.digestMD5( 
  17.             ipTimeKey.getBytes(StandardCharsets.ISO_8859_1)); 
  18.     String nonce = currentTime + ":" + MD5Encoder.encode(buffer); 
  19.  
  20.     NonceInfo info = new NonceInfo(currentTime, getNonceCountWindowSize()); 
  21. synchronized (nonces) { 
  22. nonces.put(nonce, info); 
  23.     } 
  24.  
  25. return nonce; 

Tomcat使用了客戶端IP地址、當(dāng)前時間和Digest認(rèn)證器的一個固定的key進(jìn)行拼接然后進(jìn)行MD5加密等最終生成nonce的。其中的固定值key是tomcat在初始化該Digest認(rèn)證器時,使用的是與session id相同的方法生成,其中具體使用了JDK提供的

java.security.SecureRandom隨機(jī)數(shù)等,與UUID的生成方式相似,感興趣的讀者可以分析一下JDK中UUID生成唯一值的算法。可以看到,tomcat在生成nonce隨機(jī)數(shù)時考慮了三方面的可能性,以保證隨機(jī)數(shù)nonce的唯一性:

  1. 生產(chǎn)環(huán)境中IP地址的唯一性;
  2. 進(jìn)行http訪問時當(dāng)前時間可能存在由于并發(fā)導(dǎo)致的不唯一性,此時會在同步塊中進(jìn)行對比以確保唯一。
  3. 同一IP地址下不同的tomcat或者不同應(yīng)用的電子標(biāo)簽key的唯一性。

上面三個唯一性結(jié)合進(jìn)行MD5加密,保證了任何可能環(huán)境下的唯一性。

用戶提交用戶名、密碼信息后,Digest認(rèn)證器將獲取的相關(guān)Authorization請求頭信息,正如authenticate(String username, String clientDigest,String nonce, String nc, String cnonce,String qop, String realm,String md5a2)方法中的各項(xiàng)參數(shù),交由相應(yīng)的安全域進(jìn)行處理。其主要思想就是將用戶提供的根據(jù)規(guī)則進(jìn)行摘要加密生成的字符串,與服務(wù)端使用正確的密碼、相同規(guī)則生成的字符串進(jìn)行比對。如果用戶名、密碼正確,客戶端提供的字符串自然與服務(wù)端生成的字符串相同,則認(rèn)證通過。在此不在進(jìn)行進(jìn)一步闡述。

Client認(rèn)證

前面提到不管是明文傳輸?shù)腂asic認(rèn)證和Form認(rèn)證,還是經(jīng)過摘要加密的Digest認(rèn)證,都不能很好的解決網(wǎng)絡(luò)安全問題。Client認(rèn)證依賴于HTTPS,因此是Java EE安全規(guī)范中安全性最高的一種認(rèn)證方式。使用Client認(rèn)證需要在web.xml中配置如下:

  1. <login-config> 
  2.     <auth-method>CLIENT-CERT</auth-method> 
  3. </login-config> 

HTTPS通道相關(guān)知識以及如何在tomcat配置HTTPS通道證書以及信任證書讀者可自行Google,下面重點(diǎn)分析tomcat證書與安全域的關(guān)系。

由于Client認(rèn)證依賴于HTTPS,如果對相應(yīng)資源的請求不在HTTPS通道上,tomcat就無法獲取到客戶端證書,也就無法通過證書進(jìn)一步對用戶身份進(jìn)行認(rèn)證。此時,瀏覽器獲得的響應(yīng)如下圖所示:

瀏覽器獲得的響應(yīng)

Tomcat在請求流程處理中已經(jīng)將證書解析并保存在了Request對象的

"javax.servlet.request.X509Certificate"屬性中,證書類使用了JDK提供的抽象類

java.security.cert.X509Certificate,并使用X509Certificate.getSubjectDN().getName()方法作為安全域的默認(rèn)登錄用戶名。登錄用戶名是通過

org.apache.catalina.realm.X509UsernameRetriever接口的實(shí)現(xiàn)類

org.apache.catalina.realm.X509SubjectDnRetriever獲取的,因此如果不想要在安全域的用戶名列表里添加過于復(fù)雜的形如“CN=localhost, OU=apache, O=apache, L=beijing, ST=bj, C=cn”的用戶名,可以定制自己的X509UsernameRetriever實(shí)現(xiàn)類。

因?yàn)橛脩糇C書不會帶有密碼信息,而證書本身就已經(jīng)能夠表示用戶身份,所以在接下來的認(rèn)證中,只需要判斷當(dāng)前通過證書獲取的用戶名是否在安全域名單中就可以了。如果安全域名單中存在該證書用戶,則可以認(rèn)為認(rèn)證通過,可以繼續(xù)進(jìn)行下面的授權(quán)工作。

五、授權(quán)

認(rèn)證工作完成的是證明發(fā)起當(dāng)前請求的用戶是其所聲稱的用戶,簡單的可以解釋為只要提供了正確的憑證(用戶名、密碼或證書),則認(rèn)為是該用戶在請求資源。而接下來的授權(quán)工作則需要判斷該用戶是否有權(quán)限訪問該資源。通過在web.xml中配置如下參數(shù),決定哪些用戶可以訪問相關(guān)資源:

  1. <auth-constraint> 
  2. <role-name>admin</role-name> 
  3. <role-name>test</role-name> 
  4. </auth-constraint> 

前面說了,Servlet規(guī)范除了規(guī)定了role-name匹配外,也對通配符“*”做了定義:“*”表示web.xml中定義的所有用戶,“**”則表示所有通過了認(rèn)證的用戶。同時對于role-name為空的情況,則任何用戶都不能訪問相應(yīng)的資源。針對上述幾種特殊的情況,tomcat在授權(quán)時按續(xù)進(jìn)行了處理:

  1. 判斷“constraint.getAuthenticatedUsers() && principal != null”,如果配置了“**”,且通過了認(rèn)證,設(shè)標(biāo)志位為true;否則進(jìn)行下一步。
  2. 判斷“roles.length == 0 && !constraint.getAllRoles() &&!constraint.getAuthenticatedUsers()“,如果沒有配置role-name,且沒有配置“*”和“**”,設(shè)標(biāo)志位為false;否則進(jìn)行下一步。
  3. 判斷“principal == null”,如果沒有通過授權(quán),設(shè)標(biāo)志位為false,否則進(jìn)行下一步。
  4. 比對當(dāng)前用戶角色與配置文件中的角色,如果存在匹配角色,設(shè)標(biāo)志位為true,進(jìn)行下一步。沒有通過上述授權(quán)?沒關(guān)系,還有通配符“*”沒有充分派上用場。針對“*”通配符,Tomcat做出了比servlet規(guī)范更加貼合實(shí)際應(yīng)用場景的擴(kuò)展,分為三種情形:一,嚴(yán)格按照規(guī)范使用,“*”只表示web-app/security-role/role-name節(jié)點(diǎn)下的所有用戶;二,“*”表示任何通過了認(rèn)證的用戶,該用法在實(shí)際應(yīng)用中使用的可能更多一些,面對用戶量大且復(fù)雜的應(yīng)用場景,將所有用戶角色添加到web.xml中缺乏可行性和易維護(hù)性,此處實(shí)現(xiàn)與規(guī)范定義的“**”功能相同,筆者認(rèn)為其現(xiàn)實(shí)意義就是使通配符“*”的含義更加符合開發(fā)人員的使用習(xí)慣;三,上述兩種方法的折中,如果配置了web-app/security-roles下的角色,則按第一種方法使用,否則按照第二種方法使用。因此,授權(quán)流程繼續(xù):
  5. 判斷“allRolesMode == AllRolesMode.AUTH_ONLY_MODE”,只需認(rèn)證即可,設(shè)標(biāo)志位為“true”,否則進(jìn)行下一步。
  6. 判斷“roles.length == 0 && allRolesMode == AllRolesMode.STRICT_AUTH_ONLY_MODE”,沒有配置web-app/security-roles節(jié)點(diǎn)下的角色,只需認(rèn)證即可,設(shè)標(biāo)志位為true。
  7. 根據(jù)標(biāo)志位返回403 Forbidden 響應(yīng)或者返回用戶請求資源。

六、安全域?qū)崿F(xiàn)

前面簡單介紹了安全域的相關(guān)接口和抽象類,在具體的安全域?qū)崿F(xiàn)時只需根據(jù)相應(yīng)的邏輯獲取或者比對認(rèn)證信息,讀者對此應(yīng)該有了大致的了解。例如,JDBC安全域在進(jìn)行認(rèn)證時,通過JDBC連接查詢用戶名對應(yīng)的密碼,然后與客戶端密碼進(jìn)行比對,返回認(rèn)證結(jié)果。下面介紹一下Tomcat中很有實(shí)用價值的用于防止暴力破解用戶信息的

org.apache.catalina.realm.LockOutRealm以及跟另一規(guī)范相關(guān)的

org.apache.catalina.realm.JAASRealm。

LockOutRealm的主要工作是對先于認(rèn)證工作對用戶名進(jìn)行校驗(yàn),而真正的認(rèn)證工作還依賴于其他的安全域?qū)崿F(xiàn),所以LockOutRealm繼承了父類

org.apache.catalina.realm.CombinedRealm,LockOutRealm后續(xù)的認(rèn)證工作就交由CombinedRealm中的其他安全域進(jìn)行了。

在了解LockOutRealm的實(shí)現(xiàn)之前,可以構(gòu)思一下要實(shí)現(xiàn)防止暴力破解需要哪些功能:

  1. 首先需要一個List或者M(jìn)ap,用于存儲登錄失敗的用戶名稱和相關(guān)信息,而這個List或者M(jìn)ap又不能無限大,必須是有界的,否則會導(dǎo)致嚴(yán)重的內(nèi)存泄露,當(dāng)然如果不受在tomcat的jvm實(shí)現(xiàn)的限制話,生產(chǎn)條件下我們可能會使用Redis。
  2. 需要定義用戶鎖定時的登錄失敗次數(shù)。
  3. 需要定義用戶解鎖時長。
  4. 存儲登錄失敗用戶的List或者M(jìn)ap由于有界,就有可能存在撐滿的情況,需定義此時的操作規(guī)則。

完成上述幾個功能點(diǎn),一個比較完善的防暴力破解安全域就形成了。

下面重點(diǎn)看一下tomcat存儲失敗用戶的實(shí)現(xiàn):

  1. new LinkedHashMap<String, LockRecord>(cacheSize, 0.75f, 
  2. true) { 
  3. private static final long serialVersionUID = 1L
  4. @Override 
  5. protected boolean removeEldestEntry(//重寫方法,防止內(nèi)存溢出 
  6.             Map.Entry<String, LockRecord> eldest) { 
  7. if (size() > cacheSize) { 
  8. // Check to see if this element has been removed too quickly 
  9. long timeInCache = (System.currentTimeMillis() - 
  10.                     eldest.getValue().getLastFailureTime())/1000; 
  11.  
  12. if (timeInCache < cacheRemovalWarningTime) {//沒到時間就被移出黑名單了,要打個日志 
  13. log.warn(sm.getString("lockOutRealm.removeWarning", 
  14.                         eldest.getKey(), Long.valueOf(timeInCache))); 
  15.             } 
  16. return true; 
  17.         } 
  18. return false; 
  19.     } 
  20. }; 

Tomcat使用了常用的LinkedHashMap存儲登錄失敗的用戶,并且重寫了removeEldestEntry方法,在JDK的實(shí)現(xiàn)中改方法是始終返回false的:

  1. protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { 
  2.     return false; 

而removeEldestEntry方法在每一次調(diào)用put或者putAll方法向Map中添加entry的時候都會被調(diào)用,通過該方法的返回值,判斷是否需要將最“老”的一個entry刪除。可見JDK為LinkedHashMap提供了一種靈活的控制Map大小的方法,而tomcat則利用了LinkedHashMap的這一特性。而后,將登陸失敗的用戶存儲在Map中,并通過記錄當(dāng)前時間,在以后的登陸中判斷是否對當(dāng)前用戶放行就很好實(shí)現(xiàn)了:

  1. private void registerAuthFailure(String username) { 
  2.     LockRecord lockRecord = null
  3. synchronized (this) { 
  4. if (!failedUsers.containsKey(username)) { 
  5.             lockRecord = new LockRecord(); 
  6. failedUsers.put(username, lockRecord);    //第一次登錄失敗,加入黑名單 
  7.         } else { 
  8.             lockRecord = failedUsers.get(username); 
  9. if (lockRecord.getFailures() >= failureCount && 
  10.                     ((System.currentTimeMillis() - 
  11.                             lockRecord.getLastFailureTime())/1000) 
  12.                             > lockOutTime) { 
  13. // User was previously locked out but lockout has now 
  14.                 // expired so reset failure count 
  15. lockRecord.setFailures(0);    //距離上次失敗時間久遠(yuǎn),重置失敗次數(shù) 
  16.             } 
  17.         } 
  18.     } 
  19.     lockRecord.registerFailure();    //失敗次數(shù)自增,失敗時間更新,用于下次判斷 

JAASRealm是Tomcat提供的最為開放的安全域,采用了JAAS規(guī)范相關(guān)的類和接口,因?yàn)镴AAS安全域中進(jìn)行實(shí)際認(rèn)證的類需要用戶按照使用場景進(jìn)行實(shí)現(xiàn),因此JAAS安全域也被稱為自定義安全域。

JAAS規(guī)范全稱為Java Authentication and Authorization Service,是一套可插拔的認(rèn)證授權(quán)機(jī)制,Tomcat實(shí)現(xiàn)的現(xiàn)有安全域都可以通過JAAS安全域進(jìn)行實(shí)現(xiàn)。JAAS安全域的認(rèn)證流程如下:

  1. 使用當(dāng)前配置創(chuàng)建一個LoginContext的實(shí)例,配置包括LoginModule的名稱,用于傳遞認(rèn)證信息的JAASCallbackHandler實(shí)例,configFile配置。
  2. 通過LoginContext.login()方法進(jìn)行驗(yàn)證。
  3. 如果沒有異常且認(rèn)證信息不為空,則認(rèn)證成功;否則捕獲異常,認(rèn)證失敗。關(guān)鍵代碼如下:
    1. protected Principal authenticate(String username, 
    2. CallbackHandler callbackHandler) { 
    3. …… 
    4. try { 
    5. Configuration config = getConfig(); 
    6. loginContext = new LoginContext(//構(gòu)造LoginContext 
    7. appName, null, callbackHandler, config); 
    8. …… 
    9. try { 
    10. loginContext.login();//調(diào)用login方法進(jìn)行認(rèn)證 
    11. subject = loginContext.getSubject();//獲取認(rèn)證信息,為空則認(rèn)證失敗 
    12. if (subject == null) { 
    13. if( log.isDebugEnabled()) 
    14. log.debug(sm.getString("jaasRealm.failedLogin", username)); 
    15. return (null); 
    16. …… 

這個流程是不是看起來超簡單?只需按自己的需求實(shí)現(xiàn)LoginModule,JAAS安全域的認(rèn)證工作便如行云流水般了。LoginModule的實(shí)現(xiàn)可參照相關(guān)文檔或JDK中的源碼,默認(rèn)JDK已經(jīng)提供了6種實(shí)現(xiàn)哦。

七、總結(jié)一下

本文重點(diǎn)介紹了Tomcat安全域部分的實(shí)現(xiàn),結(jié)合部署描述符web.xml中的配置,講解了Tomcat安全域?qū)φJ(rèn)證、授權(quán)工作的流程處理。文章中對HTTP的四種認(rèn)證方式進(jìn)行了較大篇幅的講解,在授權(quán)部分也詳細(xì)講解了Tomcat的處理流程。最后在安全域?qū)崿F(xiàn)部分,重點(diǎn)介紹了兩種特殊的安全域,并簡單分析了JAAS規(guī)范的相關(guān)內(nèi)容,就是這樣啦。

【本文為51CTO專欄作者“侯樹成”的原創(chuàng)稿件,轉(zhuǎn)載請通過作者微信公眾號『Tomcat那些事兒』獲取授權(quán)】

戳這里,看該作者更多好文

責(zé)任編輯:趙寧寧 來源: 51CTO專欄
相關(guān)推薦

2015-03-02 14:00:54

2013-01-08 16:42:32

Flash Playe安全域Security Do

2012-11-19 14:54:45

2020-05-08 07:41:46

網(wǎng)絡(luò)安全域隔離網(wǎng)絡(luò)安全域

2011-03-16 10:19:54

2024-01-29 15:49:49

2014-09-26 15:01:01

2013-08-02 15:27:41

2024-09-09 09:29:05

2011-06-27 09:19:33

2010-12-28 10:44:00

2017-08-25 12:53:21

2009-09-17 09:16:25

WebForm頁面內(nèi)容ASP.NET MVC

2011-04-20 15:54:38

2017-03-29 15:12:43

2022-04-12 08:30:45

TomcatWeb 應(yīng)用Servlet

2009-07-07 09:24:24

2010-02-03 16:04:34

C++標(biāo)準(zhǔn)類庫

2016-04-21 10:54:15

友盟+UBDC全域大數(shù)據(jù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 国产激情在线观看视频 | 天堂综合网久久 | 国产精品精品 | 国产福利二区 | 亚洲在线一区 | 成人国产精品久久久 | 国产伦一区二区三区 | 国产视频第一页 | 四虎影院欧美 | 亚洲日本欧美 | 久久精品一区二区 | 精品不卡| 91成人免费电影 | 七七婷婷婷婷精品国产 | 国产精品视频一区二区三 | 91久久久www播放日本观看 | 久久精品伊人 | 久久久91精品国产一区二区三区 | 一级做a爰片性色毛片16美国 | 亚洲品质自拍视频网站 | 亚洲第一在线 | 日本欧美国产在线 | 一级特黄网站 | 最新中文字幕在线 | 久久精品 | 欧美在线精品一区 | 久久91av| 古装人性做爰av网站 | 欧产日产国产精品99 | 欧美成人精品在线 | 国产免费一区二区三区 | 精品国产乱码久久久久久图片 | 三a毛片| 99久久99久久精品国产片果冰 | 亚洲欧洲日韩精品 中文字幕 | 精品久久久久一区二区国产 | 国产一区欧美 | pacopacomama在线 | 国产高清精品在线 | 久久久人成影片免费观看 | 欧美日韩国产精品激情在线播放 |