簡體   English   中英

如何安全地處理Java Servlet過濾器中的密碼?

[英]How do I securely handle passwords in a Java Servlet Filter?

我有一個過濾器,用於處理基於HTTPS的BASIC身份驗證。 這意味着有一個名為“ Authorization”的標頭,其值類似於“ Basic aGVsbG86c3RhY2tvdmVyZmxvdw ==“。

我不關心如何處理身份驗證,401加上WWW-Authenticate響應標頭,JDBC查找或類似的東西。 我的過濾器效果很好。

我擔心的是,我們永遠不要將用戶密碼存儲在java.lang.String中,因為它們是不可變的。 完成身份驗證后,我無法將String歸零。 該對象將一直位於內存中,直到垃圾回收器運行為止。 這就為壞人打開了一個更大的窗口,讓他可以獲取核心轉儲或以其他方式觀察堆。

問題是,我看到的讀取Authorization標頭的唯一方法是通過javax.servlet.http.HttpServletRequest.getHeader(String)方法,但是它返回一個String。 我需要一個getHeader方法,該方法返回一個字節或字符數組。 理想情況下,在任何時候,從Socket到HttpServletRequest以及介於兩者之間的任何地方,請求都不應為String。

如果我改用某種形式的基於表單的安全性,那么問題仍然存在。 javax.servlet.ServletRequest.getParameter(String)返回一個String。

這僅僅是Java EE的限制嗎?

實際上,只有字符串文字保留在Permgen的“字符串池”區域中。 創建的字符串是一次性的。

所以...內存轉儲可能是基本身份驗證的次要問題之一。 其他是:

  • 密碼以明文形式通過網絡發送。
  • 對於每個請求,密碼都會重復發送。 (較大的攻擊窗口)
  • 密碼由網絡瀏覽器緩存,最小長度為窗口/進程的長度。 (可以由對服務器的任何其他請求以靜默方式重用,例如CSRF)。
  • 如果用戶要求,密碼可以永久存儲在瀏覽器中。 (與上一點相同,此外,其他用戶可能會在共享計算機上被盜)。
  • 即使使用SSL,內部服務器(位於SSL協議之后)也可以訪問純文本可緩存密碼。

同時,Java容器已經解析了HTTP請求並填充了對象。 因此,這就是為什么您從請求標頭中獲取String的原因。 您可能應該重寫Web容器以解析安全HTTP請求。

更新資料

我錯了。 至少對於Apache Tomcat。

http://alvinalexander.com/java/jwarehouse/apache-tomcat-6.0.16/java/org/apache/catalina/authenticator/BasicAuthenticator.java.shtml

可以看到,來自Tomcat項目的BasicAuthenticator使用MessageBytes(即避免String)執行身份驗證。

/**
 * Authenticate the user making this request, based on the specified
 * login configuration.  Return <code>true if any specified
 * constraint has been satisfied, or <code>false if we have
 * created a response challenge already.
 *
 * @param request Request we are processing
 * @param response Response we are creating
 * @param config    Login configuration describing how authentication
 *              should be performed
 *
 * @exception IOException if an input/output error occurs
 */
public boolean authenticate(Request request,
                            Response response,
                            LoginConfig config)
    throws IOException {

    // Have we already authenticated someone?
    Principal principal = request.getUserPrincipal();
    String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
    if (principal != null) {
        if (log.isDebugEnabled())
            log.debug("Already authenticated '" + principal.getName() + "'");
        // Associate the session with any existing SSO session
        if (ssoId != null)
            associate(ssoId, request.getSessionInternal(true));
        return (true);
    }

    // Is there an SSO session against which we can try to reauthenticate?
    if (ssoId != null) {
        if (log.isDebugEnabled())
            log.debug("SSO Id " + ssoId + " set; attempting " +
                      "reauthentication");
        /* Try to reauthenticate using data cached by SSO.  If this fails,
           either the original SSO logon was of DIGEST or SSL (which
           we can't reauthenticate ourselves because there is no
           cached username and password), or the realm denied
           the user's reauthentication for some reason.
           In either case we have to prompt the user for a logon */
        if (reauthenticateFromSSO(ssoId, request))
            return true;
    }

    // Validate any credentials already included with this request
    String username = null;
    String password = null;

    MessageBytes authorization = 
        request.getCoyoteRequest().getMimeHeaders()
        .getValue("authorization");

    if (authorization != null) {
        authorization.toBytes();
        ByteChunk authorizationBC = authorization.getByteChunk();
        if (authorizationBC.startsWithIgnoreCase("basic ", 0)) {
            authorizationBC.setOffset(authorizationBC.getOffset() + 6);
            // FIXME: Add trimming
            // authorizationBC.trim();

            CharChunk authorizationCC = authorization.getCharChunk();
            Base64.decode(authorizationBC, authorizationCC);

            // Get username and password
            int colon = authorizationCC.indexOf(':');
            if (colon < 0) {
                username = authorizationCC.toString();
            } else {
                char[] buf = authorizationCC.getBuffer();
                username = new String(buf, 0, colon);
                password = new String(buf, colon + 1, 
                        authorizationCC.getEnd() - colon - 1);
            }

            authorizationBC.setOffset(authorizationBC.getOffset() - 6);
        }

        principal = context.getRealm().authenticate(username, password);
        if (principal != null) {
            register(request, response, principal, Constants.BASIC_METHOD,
                     username, password);
            return (true);
        }
    }


    // Send an "unauthorized" response and an appropriate challenge
    MessageBytes authenticate = 
        response.getCoyoteResponse().getMimeHeaders()
        .addValue(AUTHENTICATE_BYTES, 0, AUTHENTICATE_BYTES.length);
    CharChunk authenticateCC = authenticate.getCharChunk();
    authenticateCC.append("Basic realm=\"");
    if (config.getRealmName() == null) {
        authenticateCC.append(request.getServerName());
        authenticateCC.append(':');
        authenticateCC.append(Integer.toString(request.getServerPort()));
    } else {
        authenticateCC.append(config.getRealmName());
    }
    authenticateCC.append('\"');        
    authenticate.toChars();
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
    //response.flushBuffer();
    return (false);

}

只要您可以訪問org.apache.catalina.connector.Request,就不用擔心。

因此,如何避免解析HTTP請求

在stackoverflow詳細信息中有一個了不起的答案

使用Servlet過濾器從發布的數據中刪除表單參數

還有一個重要的解釋:

方法

該代碼遵循正確的方法:

在wrapRequest()中,它實例化HttpServletRequestWrapper並覆蓋觸發請求解析的4種方法:

public String getParameter(String name)public Map getParameterMap()public Enumeration getParameterNames()public String [] getParameterValues(String name)doFilter()方法使用包裝的請求調用過濾器鏈,這意味着后續的過濾器以及目標servlet(URL) -mapped)。

沒錯,但是永遠不要在數據庫中存儲要檢查的實際密碼,而是對密碼本身進行哈希運算,然后運行哈希運算來確定兩個哈希值是否相同,即從未使用過但由原始用戶使用的密碼。

如果您擔心的話,請在Filter使用ServletRequest.getInputStream()而不是HttpServletRequest.getHeader(String) 您應該可以將HTTP請求作為流獲取,跳過直到獲得Authorization標頭並在char []獲取密碼。

但是,所有這些工作可能都是徒勞的,因為基礎對象仍然是HTTPServletRequest並且可能在映射中包含所有標頭作為鍵val對,詳細信息取決於servlet的實現方式。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM