Spring Remoting and Security
Spring Remoting and Security
The RCP client uses Spring HTTP Remoting to call services remotely on the server. Two issues to resolve for remote calls are: Authentication and Security. Since HTTP Remoting works by specifying a URL on the server for each remote service, we can use Spring HTTP Remoting on the server to intercept all calls to service URLs and check that the requesting user has the appropriate credentials (i.e. Roles) to call the service. To ensure the user's credentials are not intercepted during the call, you should use secure the transport layer using Enabling security on the server.
Remoting and Authentication
Spring remoting consists of several parts. Client-side proxy bean configuration for remoting, and additional configuration for authentication.
If we did not need to authenticate every remote service call, basic remoting would be sufficient.
The client's Spring Application Context needs to have each Service bean defined in a Spring configuration file, which we do in serviceCMClient.xml. Each service bean is actually defined as a proxy that will transparently call the service on the server. In order to avoid having to specify the fully-qualified URL of the remote service for each and every bean, the default Spring HttpInvokerProxyFactoryBean was extended to create the EpHttpInvokerProxyFactoryBean, which grabs the server's URL at client login time. This way the Spring bean configuration only has to specify the name of the service, and not the full URL. A sample bean configuration would look like this:
<bean id="productService" class="com.elasticpath.cmclient.core.EpHttpInvokerProxyFactoryBean"> <property name="serviceUrl"> <value>productService.remote</value> </property> <property name="serviceInterface"> <value>com.elasticpath.service.catalog.ProductService</value> </property> </bean>
The Spring Security HttpInvokerProxyFactoryBean uses an implementation of Spring's HttpInvokerClientInterceptor to make remote calls. By default, the bean uses the implementation provided by SimpleHttpInvokerRequestExecutor, but you can set the interceptor to something different. The Spring framework also provides another implementation: CommonsHttpInvokerRequestExecutor, which can be used for more advanced needs (e.g. connection pooling, authentication, etc).
One of Spring's ClientInterceptor implementations would probably be sufficient for many cases. However, since we know that we're using Spring Security, we can use the handy implementation provided by Spring Security instead: AuthenticationSimpleHttpInvokerRequestExecutor, which simply extends Spring's SimpleHttpInvokerRequestExecutor to add BASIC auth support to it by overriding its prepareConnection() method to add an Authorization HTTP header. This class grabs the relevant principal and credentials automatically from an Spring Security SecurityContextHolder, which associates a given SecurityContext with the current execution thread. So in summary, the execution thread that is going to call your remote service still has to create an Acegi SecurityContext and hand it to the SecurityContextHolder, from which the remoting code will obtain the credentials. The SecurityContextHolder can be global to the JVM if configured to work that way, which we do.
In the RCP Commerce Manager, the rich client queries the user for their login information using a login dialog. The login procedure uses an Acegi RemoteAuthenticationManager and RemoteAuthenticationProvider to verify the username and password. The RemoteAuthenticationManager is NOT protected by BASIC or any other HTTP-level authentication, because it is designed to provide authentication itself.
If credentials are valid, the server returns an authentication token containing the user's roles and other security information, from which the client creates a client-side SecurityContext object and gives it to the SecurityContextHolder. This will allow the client to submit the login credentials with every request if the service proxy is using Acegi's AuthenticationSimpleHttpInvokerRequestExecutor. To avoid having to inject the RequestExecutor via the Spring configuration file into every remote service call, it is injected programatically in the EpHttpInvokerProxyFactoryBean.
Security can be implemented as transport-layer security, using SSL for communication between client and server. If you choose to use SSL, be aware that additional configuration will be required and you may experience a performance hit.
Enabling security on the server
SSL can be enabled within the Spring Security configuration file on the server, ensuring that all the services require a secure transport layer. Please see the deployment guide for instructions on enabling SSL. Make sure that you specify the hostname in generating the certificate (e.g. $JAVA_HOME/bin/keytool -genkey -keyalg "RSA" -dname "cn=myhostname" -alias tomcat)
Handling security on the client
Note that the SSL certificate used on the server must be trusted by the client. This usually means that the certificate must be signed by a well-known certificate-issuing authority, and that the root certification authority must be present in the trusted certificates of the java platform on the client, normally stored in the $JAVA_HOME/jre/lib/security/cacerts file. By default, the major commercial certificate-issuing authorities are trusted; however, if you wish to use a self-signed certificate then you can manually import the certificate into the client's cacerts file.
Importing a self-signed certificate into the client's JRE
If your certificate is self-signed then you must manually import the certificate into your client's JRE security keystore so that the client trusts it as if it were a certificate issued from a recognized certificate-issuing authority.
- Export your self-signed certificate on the server (e.g. keytool -export -alias mycertificate -file mycertificate.cer)
- Import the self-signed certificate on the client (e.g. keytool -import -alias mycertificate -file mycertificate.cer). This will import the certificate on the client into the default keystore name .keystore which lives in the user's home directory.
- Copy the .keystore file on the client into the jre/lib/security directory on the client and rename the file to jssecacerts. Make sure that it's the same JRE that your RCP client is using.