Basic authentication and HttpInvoker
I just threw together a quick and dirty pair of convenience classes for setting up an HttpInvokerProxyFactory that sends basic authentication with each request to the HTTP service. I'll share them in case others find them useful. I should note that this implementation could probably be spruced up a little. :-)
First, we need a BasicAuthenticationInvokerRequestExecutor. This uses common's HttpClient, so there will be that dependency in your code if you go this route:
Code:
public class BasicAuthenticationInvokerRequestExecutor extends CommonsHttpInvokerRequestExecutor
implements AuthorizationAware
{
private static final Log log = LogFactory.getLog(BasicAuthenticationInvokerRequestExecutor.class);
private String username;
private String password;
private boolean httpClientStateSet = false;
public HttpClient getHttpClient()
{
HttpClient ret = super.getHttpClient();
if(ret == null) {
setHttpClient((ret = new HttpClient()));
}
return ret;
}
public synchronized String getUsername()
{
return this.username;
}
public synchronized void setUsername(final String username)
{
this.username = username;
this.httpClientStateSet = false;
}
public synchronized String getPassword()
{
return this.password;
}
public synchronized void setPassword(final String password)
{
this.password = password;
this.httpClientStateSet = false;
}
protected RemoteInvocationResult doExecuteRequest(
final HttpInvokerClientConfiguration config, final ByteArrayOutputStream baos) throws IOException,
ClassNotFoundException
{
synchronized(this) {
if(!this.httpClientStateSet) {
final HttpClient client = getHttpClient();
final URI uri;
try {
uri = new URI(config.getServiceUrl());
} catch(URISyntaxException e) {
final IOException ioe = new IOException();
ioe.initCause(e);
throw ioe;
}
if(getUsername() != null) {
client.getState().setCredentials(null, uri.getHost(), new UsernamePasswordCredentials(getUsername(), getPassword()));
client.getState().setAuthenticationPreemptive(true);
} else {
client.getState().setCredentials(null, uri.getHost(), null);
client.getState().setAuthenticationPreemptive(false);
}
this.httpClientStateSet = true;
}
}
return super.doExecuteRequest(config, baos);
}
//
// METHODS FROM INTERFACE AuthorizationAware
//
public void setUserAttemptedAuthorization(final String principal,
final String credentials)
{
}
public synchronized void setUserAuthorization(final String principal, final String credentials)
{
if(log.isDebugEnabled()) log.debug(">>- setUserAuthorization invoked in BasicAuthenticationInvokerRequestExecutor for principal '" + principal + "'");
setUsername(principal);
setPassword(credentials);
}
}
Some notes on this class: the AuthorizationAware interface is my own interface. We have a bean in the app context that automatically injects authorization info into beans implementing this interface whenever a user logs in or out (this is in a rich client app). So, when the user logs in the username and password are automatically set on this RequestExecutor. In order to reuse this class for your own purposes, you will either need to create your own AuthorizationAware interface or remove the interface (and the two related methods) from this code and manually call setUsername/setPassword. Also note that this implemention creates a default HttpClient, but that you can set your own instance and override the default. You may also want to tweak the way this class sets up authorization on the HttpClient state.
You could just use this class directly with HttpInvokerProxyFactoryBean and be done with it... but we wanted to make our life just that much easier, so we created a convenience HttpInvokerProxyFactoryBean that default to using BasicAuthenticationInvokerRequestExecutor and also forwards AuthorizationAware injections to the internal request executor:
Code:
public class BasicAuthHttpInvokerProxyFactoryBean extends HttpInvokerProxyFactoryBean
implements AuthorizationAware
{
private static final Log log = LogFactory.getLog(BasicAuthHttpInvokerProxyFactoryBean.class);
private boolean doNotRemoteObjectMethods = true;
public BasicAuthHttpInvokerProxyFactoryBean()
{
setHttpInvokerRequestExecutor(new BasicAuthenticationInvokerRequestExecutor());
}
public void setUsername(final String username)
{
final HttpInvokerRequestExecutor hire = getHttpInvokerRequestExecutor();
if(hire instanceof BasicAuthenticationInvokerRequestExecutor) {
((BasicAuthenticationInvokerRequestExecutor)hire).setUsername(username);
}
}
public void setPassword(final String password)
{
final HttpInvokerRequestExecutor hire = getHttpInvokerRequestExecutor();
if(hire instanceof BasicAuthenticationInvokerRequestExecutor) {
((BasicAuthenticationInvokerRequestExecutor)hire).setPassword(password);
}
}
/**
* Determines if the proxy returned by this FactoryBean should remotely
* invoke methods inherited from java.lang.Object. If true, then the
* methods are invoked against the Proxy class itself, otherwise the
* methods will be invoked on the remote service. Default is true.
* @return
*/
public boolean getDoNotRemoteObjectMethods()
{
return this.doNotRemoteObjectMethods;
}
public void setDoNotRemoteObjectMethods(final boolean doNotRemoteObjectMethods)
{
this.doNotRemoteObjectMethods = doNotRemoteObjectMethods;
}
//
// METHODS FROM CLASS HttpInvokerProxyFactoryBean
//
public Object invoke(final MethodInvocation methodInvocation) throws Throwable
{
if(this.doNotRemoteObjectMethods &&
Object.class.equals(methodInvocation.getMethod().getDeclaringClass())) {
if(log.isDebugEnabled()) log.debug("==>> java.lang.Object method invoked on remote proxy '" + getServiceUrl() + "', calling on local object instead of remote: " + methodInvocation.getMethod());
return methodInvocation.getMethod().invoke(this, methodInvocation.getArguments());
}
return super.invoke(methodInvocation);
}
//
// METHODS FROM INTERFACE AuthorizationAware
//
public void setUserAttemptedAuthorization(final String principal,
final String credentials)
{
final HttpInvokerRequestExecutor hire = getHttpInvokerRequestExecutor();
if(hire instanceof AuthorizationAware) {
((AuthorizationAware)hire).setUserAttemptedAuthorization(principal, credentials);
}
}
public void setUserAuthorization(final String principal, final String credentials)
{
final HttpInvokerRequestExecutor hire = getHttpInvokerRequestExecutor();
if(hire instanceof AuthorizationAware) {
((AuthorizationAware)hire).setUserAuthorization(principal, credentials);
}
}
}
This class also provides setUsername/setPassword methods, and just forwards them to the internal request executor.
NOTE: one major difference between this class and HttpInvokerProxyFactoryBean is that it defaults to NOT remoting methods defined in java.lang.Object. In other words, if someone calls toString() (for example) on the remote proxy then the call will not be sent to the remote service but rather executed against the proxy factory bean instead. This can be changed by explicitly setting the doNotRemoteObjectMethods property to false.
- Andy
Re: Basic authentication and HttpInvoker
First of all, an interesting extension! I'll have a more detailed look at it later; we should probably add such support to the standard HTTP invoker.
Quote:
Originally Posted by adepue
NOTE: one major difference between this class and HttpInvokerProxyFactoryBean is that it defaults to NOT remoting methods defined in java.lang.Object. In other words, if someone calls toString() (for example) on the remote proxy then the call will not be sent to the remote service but rather executed against the proxy factory bean instead. This can be changed by explicitly setting the doNotRemoteObjectMethods property to false.
Note that I recently changed equals/hashCode/toString to be handled locally, both for HTTP invokers and RMI invokers, following a suggestion in another forum thread. So as of Spring 1.1.2, those methods will always be handled locally; Hessian and Burlap behave the same, BTW. I consider it a bug that HTTP/RMI invoker haven't behaved that way before.
Juergen