如何在Javaservlet过滤器中安全地处理密码?

如何在Javaservlet过滤器中安全地处理密码?,java,security,servlets,filter,passwords,Java,Security,Servlets,Filter,Passwords,我有一个过滤器,可以处理HTTPS上的基本身份验证。这意味着将有一个名为“Authorization”的头,其值类似于“Basic aGVsbG86c3RhY2tvdmVyZmxvdw==” 我不关心如何处理身份验证、401pluswowauthenticate响应头、JDBC查找或诸如此类的内容。我的过滤器工作得很好 我担心的是,我们永远不应该在java.lang.String中存储用户密码,因为它们是不可变的。我无法在完成身份验证后立即将该字符串归零。在垃圾收集器运行之前,该对象将一直位于内

我有一个过滤器,可以处理HTTPS上的基本身份验证。这意味着将有一个名为“Authorization”的头,其值类似于“Basic aGVsbG86c3RhY2tvdmVyZmxvdw==”

我不关心如何处理身份验证、401pluswowauthenticate响应头、JDBC查找或诸如此类的内容。我的过滤器工作得很好

我担心的是,我们永远不应该在java.lang.String中存储用户密码,因为它们是不可变的。我无法在完成身份验证后立即将该字符串归零。在垃圾收集器运行之前,该对象将一直位于内存中。这为坏人获得内核转储或以其他方式观察堆留下了更大的窗口

问题是,我看到读取授权头的唯一方法是通过
javax.servlet.http.HttpServletRequest.getHeader(String)
方法,但它返回一个字符串。我需要一个返回字节或字符数组的getHeader方法。理想情况下,在任何时间点,从套接字到HttpServletRequest以及两者之间的任何地方,请求都不应该是字符串

如果我切换到某种形式的基于表单的安全性,问题仍然存在
javax.servlet.ServletRequest.getParameter(字符串)
也返回一个字符串


这仅仅是Java EE的一个限制吗?

这是正确的,但它不应该在db中存储要检查的实际密码,而是在密码本身上存储一个哈希,然后运行一个哈希,以确定这两个哈希是否相同,而密码是由原始用户使用的。

实际上,字符串中只保留字符串文字Permgen的游泳池面积。创建的字符串是一次性的

所以。。。内存转储可能是基本身份验证的一个小问题。其他包括:

  • 密码以明文形式通过电线发送
  • 每次请求都会重复发送密码。(更大的攻击窗口)
  • 密码由webbrowser缓存,至少为窗口/进程的长度。(可通过向服务器发出的任何其他请求(如CSRF)进行静默重用)
  • 如果用户要求,密码可以永久存储在浏览器中。(与前一点相同,此外,可能会被共享计算机上的其他用户窃取)
  • 即使使用SSL,内部服务器(SSL协议背后)也可以访问纯文本可缓存密码
同时,Java容器已经解析了HTTP请求并填充对象。所以,这就是为什么从请求头获取字符串。您可能应该重写Web容器来解析安全HTTP请求

更新 我错了。至少对ApacheTomcat是这样

您可以看到,Tomcat项目的BasicAuthenticator使用MessageBytes(即避免字符串)来执行身份验证

/**
 * 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);

}
/**
*根据指定的身份验证发出此请求的用户
*登录配置。如果指定了任何值,则返回
true
*约束已满足,如果满足,则
false
*已创建响应挑战。
*
*@param请求我们正在处理
*@param响应我们正在创建
*@param config登录配置描述如何进行身份验证
*应该执行
*
*@exception IOException如果发生输入/输出错误
*/
公共布尔认证(请求,
回应,,
登录(配置)
抛出IOException{
//我们已经确认有人了吗?
Principal=request.getUserPrincipal();
String ssoId=(String)request.getNote(Constants.REQ\u ssoId\u NOTE);
if(主体!=null){
if(log.isDebugEnabled())
log.debug(“已通过身份验证””+principal.getName()+“”);
//将会话与任何现有SSO会话关联
如果(ssoId!=null)
关联(ssoId,request.getSessionInternal(true));
返回(真);
}
//是否存在可以尝试重新验证的SSO会话?
如果(ssoId!=null){
if(log.isDebugEnabled())
log.debug(“ssoId”+ssoId+”集;正在尝试”+
“重新认证”);
/*尝试使用SSO缓存的数据重新验证。如果失败,
原始SSO登录是摘要登录或SSL登录(其中
我们无法重新验证自己,因为没有
缓存的用户名和密码),或域被拒绝
由于某种原因,用户的重新身份验证。
无论哪种情况,我们都必须提示用户登录*/
if(从SSO重新验证(ssoId,请求))
返回true;
}
//验证此请求中已包含的任何凭据
字符串username=null;
字符串密码=null;
MessageBytes授权=
request.getCoyoteRequest().getMimeHeaders()
.getValue(“授权”);
if(授权!=null){
toBytes();
ByteChunk authorizationBC=authorization.getByteChunk();
if(授权BC.启动WithIgnoreCase(“基本”,0)){
authorizationBC.setOffset(authorizationBC.getOffset()+6);
//修正:添加修剪
//授权bc.trim();
CharChunk authorizationCC=authorization.getCharChunk();
Base64.解码(authorizationBC、authorizationCC);
//获取用户名和密码
int colon=authorizationCC.indexOf(“:”);
if(冒号<0){
username=authorizationCC.toString();
}否则{
char[]buf=authorizationCC.getBuffer();
用户名=新字符串(buf,0,冒号);
密码=新字符串(buf,冒号+1,
authorizationCC.getEnd()-colon-1);
}
authorizationBC.setOffset(authorizationBC.g