实际上,只有字符串字面量被保留在Permgen的字符串池区域中。创建的字符串是可丢弃的。
所以... 基本认证可能存在的问题不止内存转储,还包括:
- 密码明文传输。
- 为每个请求重复发送密码。(攻击窗口更大)
- Web浏览器会缓存密码,至少在窗口/进程的长度内。(可以被任何其他请求静默地重新使用,例如CSRF)。
- 如果用户请求,密码可能会永久存储在浏览器中。(与前一个点相同,此外可能会被共享计算机上的另一个用户窃取)。
- 即使使用SSL,内部服务器(在SSL协议后面)也将访问明文可缓存的密码。
同时,Java容器已经解析了HTTP请求并填充了对象。因此,您从请求头中获取字符串。您可能应该重写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)来执行身份验证。
public boolean authenticate(Request request,
Response response,
LoginConfig config)
throws IOException {
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() + "'");
if (ssoId != null)
associate(ssoId, request.getSessionInternal(true));
return (true);
}
if (ssoId != null) {
if (log.isDebugEnabled())
log.debug("SSO Id " + ssoId + " set; attempting " +
"reauthentication");
if (reauthenticateFromSSO(ssoId, request))
return true;
}
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);
CharChunk authorizationCC = authorization.getCharChunk();
Base64.decode(authorizationBC, authorizationCC);
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);
}
}
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);
return (false);
}
只要您可以访问org.apache.catalina.connector.Request,就没有问题。
那么,如何避免解析HTTP请求呢?
在stackoverflow上有一个很棒的答案详细介绍了{{link1:使用servlet过滤器从发布的数据中删除表单参数}}。
还有一个重要的解释:
方法
代码遵循正确的方法:
在wrapRequest()中,它实例化HttpServletRequestWrapper并覆盖触发请求解析的4个方法:
public String getParameter(String name)
public Map getParameterMap()
public Enumeration getParameterNames()
public String[] getParameterValues(String name)
doFilter()方法使用包装的请求调用过滤器链,这意味着后续的过滤器以及目标servlet(URL映射)将使用包装的请求。
request.getCoyoteRequest.getMimeHeaders().getValue(String)
方法获取密码的char[],然后在进行身份验证后甚至可以将其清零。我不太喜欢将其与Tomcat绑定,因为我们有一些客户运行不同的Web容器。如果不是Tomcat,我可以使用默认的getHeader(String)
方法,而在Tomcat中则使用这种疯狂的方式。哇。 - bmauter