I have a filter that handles BASIC authentication over HTTPS. That means there's a header coming in named "Authorization" and the value is something like "Basic aGVsbG86c3RhY2tvdmVyZmxvdw==".
I'm not concerned with how to handle the authentication, the 401 plus WWW-Authenticate response header, JDBC lookup or anything like that. My filter works beautifully.
My concern is that we should never store a user password in a java.lang.String because they're immutable. I can't zero out that String as soon as I'm done authenticating. That object will sit in memory until the garbage collector runs. That leaves open a much wider window for a bad guy to get a core dump or somehow otherwise observe the heap.
The problem is that the only way I see to read that Authorization header is via the javax.servlet.http.HttpServletRequest.getHeader(String)
method, but it returns a String. I need a getHeader method that returns an array of bytes or chars. Ideally, the request should never be a String at any point in time, from Socket to HttpServletRequest and everywhere in between.
If I switched to some flavor of form-based security, the problem still exists. javax.servlet.ServletRequest.getParameter(String)
returns a String too.
Is this simply a limitation of Java EE?
Actually only string literals are keeped in String Pool area of Permgen. Created Strings are disposables.
So... Probably memory dump is one of minor problems with Basic Authentication. Others are:
At the same time, Java container has already parsed HTTP request and populate object. So, that's why you get String from request header. You probably should rewrite the Web Container to parse safety HTTP request.
I was wrong. At least to Apache Tomcat.
http://alvinalexander.com/java/jwarehouse/apache-tomcat-6.0.16/java/org/apache/catalina/authenticator/BasicAuthenticator.java.shtml
How you can see, BasicAuthenticator from Tomcat project use MessageBytes (i.e. avoiding String) to perform the authentication.
/**
* 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);
}
As long you have access to org.apache.catalina.connector.Request, no worries.
There's an amazing answer here in stackoverflow detailing
Use servlet filter to remove a form parameter from posted data
and an important explanation:
Approach
The code follows the correct approach:
in wrapRequest(), it instantiates HttpServletRequestWrapper and overrides the 4 methods that trigger request parsing:
public String getParameter(String name) public Map getParameterMap() public Enumeration getParameterNames() public String[] getParameterValues(String name) the doFilter() method invokes the filter chain using the wrapped request, meaning subsequent filters, plus the target servlet (URL-mapped) will be supplied the wrapped request.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With