Instead of using the normal Acegi class for adding user's auth data to a remote request (AuthenticationSimpleHttpInvokerRequestExecutor), I created a new class based on it:
Code:
/**
* Adds BASIC authentication support to <code>SimpleHttpInvokerRequestExecutor</code>,
* but retrieves the current user's authentication data from Spring, not the
* static ContextHolder, thus making it suitable for multi-threaded apps.
* <P>
* Based on <code>AuthenticationSimpleHttpInvokerRequestExecutor</code> by Ben Alex.
*
* @author pattonm
* @version $Revision: 1.1 $
*/
public class AuthCrossThreadHttpInvokerRequestExecutor extends SimpleHttpInvokerRequestExecutor {
/////////////////////////////
// Normal member variables //
/////////////////////////////
protected CrossThreadContextHolder contextHolder = null;
/**
* Provided so subclasses can perform additional configuration if required
* (eg set additional request headers for non-security related information
* etc).
*
* @param con the HTTP connection to prepare
* @param contentLength the length of the content to send
*
* @throws IOException if thrown by HttpURLConnection methods
*/
protected void doPrepareConnection(HttpURLConnection con, int contentLength) throws IOException {
}
/**
* Called every time a HTTP invocation is made.
*
* <P>
* Simply allows the parent to setup the connection, and then adds an
* <code>Authorization</code> HTTP header property that will be used for
* BASIC authentication.
* </p>
*
* <P>
* The spring-managed <code>ContextHolder</code> is used to obtain the
* relevant principal and credentials.
* </p>
*
* @param con the HTTP connection to prepare
* @param contentLength the length of the content to send
*
* @throws IOException if thrown by HttpURLConnection methods
* @throws AuthenticationCredentialsNotFoundException if the
* <code>ContextHolder</code> does not contain a valid
* <code>Authentication</code> with both its
* <code>principal</code> and <code>credentials</code> not
* <code>null</code>
*/
protected void prepareConnection(HttpURLConnection con, int contentLength)
throws IOException, AuthenticationCredentialsNotFoundException {
super.prepareConnection(con, contentLength);
// Add the basic authorization headers
if (contextHolder == null) {
if (logger.isDebugEnabled()) {
logger.debug(
"Unable to set BASIC authentication header as contextHolder " +
"member variable is null.");
}
}
if ((contextHolder.getContext() != null)
&& (contextHolder.getContext() instanceof SecureContext)) {
Authentication auth = ((SecureContext) contextHolder.getContext())
.getAuthentication();
if ((auth != null) && (auth.getPrincipal() != null)
&& (auth.getCredentials() != null)) {
String base64 = auth.getPrincipal().toString() + ":"
+ auth.getCredentials().toString();
con.setRequestProperty("Authorization",
"Basic "
+ new String(Base64.encodeBase64(base64.getBytes())));
if (logger.isDebugEnabled()) {
logger.debug(
"HttpInvocation now presenting via BASIC authentication ContextHolder-derived: "
+ auth.toString());
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug(
"Unable to set BASIC authentication header as ContextHolder: "
+ contextHolder.getContext()
+ "; did not provide valid Authentication: " + auth);
}
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug(
"Unable to set BASIC authentication header as ContextHolder: "
+ contextHolder.getContext()
+ "; does not provide a SecureContext");
}
}
doPrepareConnection(con, contentLength);
}
public CrossThreadContextHolder getContextHolder() {
return contextHolder;
}
public void setContextHolder(CrossThreadContextHolder contextHolder) {
this.contextHolder = contextHolder;
}
}
It uses a CrossThreadContextHolder, which, instead of storing the data in a static member variable, uses a normal non-static member variable:
Code:
/**
* Associates a given Context with a member variable, for use across
* multiple execution threads.
*
* @author pattonm
* @version $Revision: 1.2 $
*/
public class CrossThreadContextHolder {
private Context context = null;
public CrossThreadContextHolder() {
}
public Context getContext() {
return context;
}
public void setContext(Context context) {
this.context = context;
}
}
Then, I just put both of them in Spring:
Code:
<!-- Automatically propagates ContextHolder-managed Authentication principal
and credentials to a HTTP invoker BASIC authentication header -->
<bean id="httpInvokerRequestExecutor" class="org.dm.daniel.grace.ui.util.AuthCrossThreadHttpInvokerRequestExecutor">
<property name="contextHolder"><ref bean="contextHolder"/></property>
</bean>
<!-- Bean for holding the secure context which holds the currently logged in
user's authentication principal and credentials -->
<bean id="contextHolder" class="org.dm.daniel.grace.ui.util.CrossThreadContextHolder"/>
And after I've authenticated I put the authentication class in the CrossThreadContextHolder:
Code:
CrossThreadContextHolder contextHolder = (CrossThreadContextHolder)appCtx.getBean("contextHolder");
SecureContext secureContext = new SecureContextImpl();
secureContext.setAuthentication(result);
contextHolder.setContext(secureContext);
I don't know Acegi too well so their might be problems to this admittedly naive approach, but it works fine for me since both threads can access spring, but both can't access the static data.