繼續有關 JAAS 的文章第一部分。讓我們來弄清楚JAAS是否可以只使用註解,以及會遇到什麼問題。我們將在這一部分中了解哪些 Servlet API 工具可以讓我們讓程式碼更加通用。讓我們總結一下我們讀到的內容。
延續
在 JAAS 技術回顧的第一部分(請參閱「JAAS - 技術簡介(第 1 部分)」)中,我們研究了 JAAS 和 Servlet API 的主要用例。我們看到 Tomcat servlet 容器使用 JAAS 架構來管理 Web 應用程式的安全性。在了解了「auth-method」和「Security Realm」之後,Tomcat 容器本身為我們提供了身份驗證機制的必要實現,並為我們提供了 CallbackHandler,我們只需在登入模組中使用它。唯一需要記住的重要事情是瀏覽器保存透過 BASIC 驗證傳輸的登入名稱和密碼資料。因此,對於使用 Chrome 進行的每次新掃描,您可以按Ctrl+Shift+N開啟新視窗以在隱身模式下運作。註解
使用 XML 進行配置早已過時。因此,重要的是,從 Servlet API 3.0 版開始,我們有機會使用註解來設定 servlet 設置,而無需使用 web.xml 部署描述符檔案。讓我們看看如果使用註釋,安全管理將如何改變。是否可以使用註解來實現上述方法?Servlet API 規範及其「註釋和可插拔性」部分將再次幫助我們理解註釋。本節說明 servlet 聲明web.xml
可以用註解取代@WebServlet
。因此,我們的 servlet 將如下所示:
@WebServlet(name="app", urlPatterns = "/secret")
public class App extends HttpServlet {
接下來,我們來看「13.3 程式安全」章節。它說我們也可以透過註解聲明安全約束:
@WebServlet(name="app", urlPatterns = "/secret")
@ServletSecurity(httpMethodConstraints = {
@HttpMethodConstraint(value = "GET", rolesAllowed = "admin")
})
public class App extends HttpServlet {
現在web.xml
我們的只剩下一個街區了—— login-config
。問題是,碰巧沒有辦法輕鬆簡單地更換它。由於 Web 應用程式安全設定和 Web 伺服器安全設定之間的緊密聯繫,因此沒有簡單且通用的方法來執行此操作,即使是透過程式設計也是如此。這是使用 JAAS 和 Servlet API 進行身份驗證的問題之一。說到區塊login-config
,值得理解的是,它是身份驗證機制的聲明性描述,即 身份驗證機制。仍然沒有簡單的通用方法來取代它,因為... 處理web.xml
發生在 servlet 容器的深處。例如,在 Tomcat 中,您可以檢視來源ContextConfig.java。因此,即使對於 Tomcat servlet 容器,也有多種選擇,而且它們都是不同的。例如,如果我們使用嵌入式 Tomcat servlet 容器(即我們從程式碼啟動 Web 伺服器),那麼您可以在此處閱讀有關此類選項的資訊:「透過程式碼進行基本驗證的嵌入式 Tomcat 」。另外,提高Embedde Tomcat的一般範例可以在Heroku PaaS平台指南中看到:「使用嵌入式Tomcat建立Java Web應用程式」。如果Tomcat不是在Embedded模式下使用,那麼對於Tomcat你可以使用一種常用的方式-事件監聽器。在 Tomcat 中,這是「生命週期監聽器元件」。同時,重要的是要了解 servlet 容器(在我們的例子中是 Tomcat)可能有自己的類別載入器,並且不可能簡單地取得和使用您的類別。對於Tomcat你需要了解「Class Loader HOW-TO」。在另一個名為 Undertow 的 servlet 容器中,這可以使用「 Servlet Extensions 」來實作。正如您所看到的,有些提供了更靈活的機制,而有些則沒有。正如您所看到的,沒有單一的選擇。他們都很不同。是否有可能僅使用 Servlet API 和 JAAS 來做一些通用的事情?在網路上您可以找到使用Servlet Filter來執行無阻塞驗證的建議login-config
。最後讓我們考慮一下這個選項。這將使我們能夠重複 JAAS 的工作原理。
驗證過濾器
因此,我們的目標是完全刪除web.xml
該檔案。如果我們擺脫它,那麼不幸的是,我們將無法再使用安全約束,因為 它們的處理可以比應用 servlet 過濾器早得多。這是您為使用過濾器的「多功能性」而必須支付的費用。那些。我們必須刪除註釋@ServletSecurity
,並且我們之前在安全約束中描述的所有檢查都必須以程式設計方式執行。正如您所看到的,這個選項也為我們帶來了許多令人不快的限制。首先,讓我們@ServletSecurity
從資源中刪除註釋並login-config
從web.xml
. 現在,我們自己必須實施基本身份驗證。現在讓我們新增過濾器:
@WebFilter("/*")
public class JaasFilter implements javax.servlet.Filter {
到目前為止看起來很簡單。我們來寫一個初始化方法:
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String jaas_conf = filterConfig.getServletContext().getRealPath("/WEB-INF/jaas.config");
System.getProperties().setProperty("java.security.auth.login.config",jaas_conf);
}
正如您所看到的,我們現在被迫使用基本的 JAAS 工具來搜尋 jaas 設定檔。這有一個很大的缺點 - 如果有多個應用程序,那麼一個應用程式可能會破壞另一個應用程式的身份驗證。JAAS 文件中詳細描述了通常如何設定 Jaas 設定檔:「附錄 A:java.security 安全性屬性檔案中的 JAAS 設定」。接下來,我們將描述過濾方法本身。讓我們從接收 HTTP 請求開始:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
// Если в реквесте уже есть Principal - ничего не делаем
if (req.getUserPrincipal() != null ) {
chain.doFilter(request, response);
}
這裡一切都很簡單。如果主體可用,則身份驗證成功。接下來,您需要描述當使用者未通過身份驗證時過濾器的操作,即 他還沒有被認出。前面我們描述了基本身份驗證方法 BASIC。因為 由於我們現在自己編寫過濾器,因此我們必須弄清楚 BASIC 身份驗證的實際工作原理。您可以使用「MDN Web 文件:HTTP 授權」。還有“ HTTP 身份驗證如何運作? ” 從Basic身份驗證如何工作的描述中可以清楚地看出,如果伺服器想要執行BASIC身份驗證,並且用戶沒有提供數據,那麼伺服器會發送一個特殊的標頭「WWW-Authenticate」和錯誤代碼401。讓我們創建一個內部方法:
private void requestNewAuthInResponse(ServletResponse response) throws IOException {
HttpServletResponse resp = (HttpServletResponse) response;
String value = "Basic realm=\"JaasLogin\"";
resp.setHeader("WWW-Authenticate", value);
resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
現在讓我們使用這個方法並將doFilter
以下程式碼區塊新增到該方法中:
// Получаем security Header. А если его нет - запрашиваем
String secHeader = req.getHeader("authorization");
if (secHeader == null) {
requestNewAuthInResponse(response);
}
現在讓我們新增一個分支if
,它將在標頭已經傳輸時執行:
// Проверяем аутентификацию
else {
String authorization = secHeader.replace("Basic ", "");
Base64.Decoder decoder = java.util.Base64.getDecoder();
authorization = new String(decoder.decode(authorization));
String[] loginData = authorization.split(":");
try {
if (loginData.length == 2) {
req.login(loginData[0], loginData[1]);
chain.doFilter(request, response);
} else {
requestNewAuthInResponse(response);
}
} catch (ServletException e) {
requestNewAuthInResponse(response);
}
}
你可以為這段程式碼添加授權,例如:req.isUserInRole("admin")
這樣我們就用過濾器為你做了認證。一方面,有大量的程式碼和手動處理。另一方面,具有多功能性和伺服器獨立性。
GO TO FULL VERSION