http://www.jroller.com/sjivan/entry/lingo_spring_remoting_passing_client

Lingo (Spring Remoting) : Passing client credentials to the server

Spring Remoting allows you to export a service interface which a remote client then then look up. Once the remote service interface is looked up, the client can work with this reference like its a local object reference. Well, kinda. There are certain limitations that are associated depending on the remoting transport protocol used.

The Spring distribution currently supports RMI, Spring's HTTP invoker, Hessian, Burlap and JAX-RPC. One of the biggest limitations in these transports is that none of them support passing a method argument by reference. This can be a show stopper for a several usecases such as callbacks. RMI as a transport protocol does support pass-by-reference however the Spring RMI Remoting Strategy does not support this yet. Regardless, RMI is a heavy weight protocol so I'm not a big fan of it.

This brings us to Lingo, an implementation of Spring Remoting over JMS which does support remote callbacks - the contract being that such arguments must either implement java.rmi.Remote or java.util.EventListener. Lingo also supports asynchronous one-way method invocations.

A common requirement in a client-server communication protocol is the propagation of the client's security context to the server during a remote method invocation. One could pass this information as an argument to every method call but that's a really really ugly way to go about it. The right way to do this would be to make such information available in as a ThreadLocal context variable on the server. Lingo does not support passing of client credentials to the server out of the box.

I examined the Lingo code found that adding support for this functionality was actually quite easy. I'll cover most of the details here. You can download the entire source for this as well.

Lingo uses Marshaller's on the client and server side which basically negotiates the underlying client-server JMS communication.

Lingo Marshaller's have a hook for adding and extracting custom JMS header messages. So I created a custom client Marshaller which adds the users Principal (user name) to the message header and a custom server Marshaller which reads this information and makes it available as a ThreadLocal context variable.

Here's the code for an Acegi based client marshaller.

public class AcegiClientMarshaller extends DefaultMarshaller {
protected void appendMessageHeaders(Message message, Requestor requestor,
LingoInvocation invocation) throws JMSException {
SecurityContext sc = SecurityContextHolder.getContext();
Authentication auth = sc.getAuthentication();
  //if you're using a custom acegi principal, update this code accordingly
String userName = null;
if (auth != null) {
Object principal = auth.getPrincipal();
if (principal instanceof net.sf.acegisecurity.UserDetails) {
userName = ((net.sf.acegisecurity.UserDetails) principal).getUsername();
} else if (principal instanceof String) {
userName = (String) principal;
} else {
throw new IllegalArgumentException("Invalid principal " + principal);
}
}  
//add user name info to the message header
message.setStringProperty(Constants.JMS_CLIENT_ID_PROPERTY, userName);
}
}

As you can see, I grab the principal (user name in our case) of the authenticated user and add it as a custom header property. (I have another simple implementation of a client Marshaller in the source distribution). Even if you're not using Acegi, you can basically follow a similar logic as above to add the client context info as a custom header message.

Now we need to create a server (un)Marshaller which extracts this information and puts it in a ThreadLocal context variable.

public class ServerMarshaller extends DefaultMarshaller {
 
private static final Log log = LogFactory.getLog(DefaultMarshaller.class);  
protected void handleInvocationHeaders(Message message) {
try {
String userName = message.getStringProperty(Constants.JMS_CLIENT_ID_PROPERTY);
ClientContextHolder.setContext(new ClientContextImpl(userName));
}
catch (JMSException e) {
log.error(e.getMessage());
throw new RuntimeException("An Unexpected error occured " + e.getMessage(), e);
}
}
}

Note that in this example we are just passing the user-name String as client context data, however you're not limited to using String's. You can use message.setObjectProperty(..) and message.getObjectProperty() and pass richer context information. The Object would need to be Serializable though.

Now the final step of hooking up the pieces in the client and server Spring ApplicationContext files.

The client configuration file looks like

<?xml version="1.0" encoding="UTF-8"?> 

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans> <!-- client side --> <bean id="clientOne" class="org.logicblaze.lingo.jms.JmsProxyFactoryBean"> <property name="serviceInterface" value="org.sanjiv.lingo.test.ExampleService"/> <property name="connectionFactory" ref="jmsFactory"/> <property name="destination" ref="exampleDestination"/> <property name="marshaller" ref="acegiClientMarshaller"/> </bean>
<bean id="acegiClientMarshaller" class="org.sanjiv.lingo.client.AcegiClientMarshaller"/>
<!-- JMS ConnectionFactory to use --> <bean id="jmsFactory" class="org.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="vm://localhost"/> </bean></beans>

And the server configuration file looks like

<?xml version="1.0" encoding="UTF-8"?> 

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans> <!-- the server side --> <bean id="serverImpl" class="org.sanjiv.lingo.test.ExampleServiceImpl" singleton="true"/>
<bean id="server" class="org.logicblaze.lingo.jms.JmsServiceExporter"> <property name="service" ref="serverImpl"/> <property name="serviceInterface" value="org.sanjiv.lingo.test.ExampleService"/> <property name="connectionFactory" ref="jmsFactory"/> <property name="destination" ref="exampleDestination"/> <property name="marshaller" ref="serverMarshaller"/> </bean>
<bean id="serverMarshaller" class="org.sanjiv.lingo.server.ServerMarshaller"/>
<!-- JMS ConnectionFactory to use --> <bean id="jmsFactory" class="org.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="vm://localhost"/> </bean>
<bean id="exampleDestination" class="org.activemq.message.ActiveMQQueue"> <constructor-arg index="0" value="test.org.sanjiv.lingo.example"/> </bean></beans>

During execution of a remote method, you can extract the client context info by looking it up in the ThreadLocal context variable as illustrated by this example :

public class ExampleServiceImpl implements ExampleService {
 
public String whoAmI() {
String userName = ClientContextHolder.getUserName();
return userName != null ? userName : "annonymous";
}
}

The source for the ClientContextHolder class which manages the ThreadLocal variables is :

public class ClientContextHolder {
 
private static InheritableThreadLocal contextHolder = new InheritableThreadLocal();
 
/**
* Associates a new ClientContext with the current thread of execution.
*/ public static void setContext(ClientContext context) {
contextHolder.set(context);
}
 
/**
* Obtains the <code>ClientContext</code> associated with the current thread of execution.
*
* @return the current ClientContext
*/ public static ClientContext getContext() {
if (contextHolder.get() == null) {
contextHolder.set(new ClientContextImpl());
}
 
return (ClientContext) contextHolder.get();
}  
public static String getUserName() {
return ((ClientContext) contextHolder.get()).getUserName();
}
}

And there you have it. You can download the complete source for this here and check out the test case to get a better feel of how all the pieces hang together. JRoller doesn't allow uploading .zip files so I've uploaded the sample as a .jar file instead. The source distribution has a Maven 1.x project file. To build and run the tests, run "maven jar".

I like Lingo, it's simple and does the job well. I only wish there was a more active and responsive mailing list.

Feedback welcome.

Update 5/01/06 : I recently required to propagate the client's locale to the server as well and used a similar approach as above. Basically I created a composite ClientMarshaller and ServerMarshaller and added the Client and Server marshallers for propagating the principal (as shown above) and the client's locale declaratively in the Spring applicaiton context file. Here's the relevant code snippet : In LocaleClientMarshaller :

Locale locale = LocaleContextHolder.getLocale();
message.setStringProperty(OptimizerConstants.JMS_LOCALE_LANGUAGE_PROPERTY, locale.getLanguage());
message.setStringProperty(OptimizerConstants.JMS_LOCALE_COUNTRY_PROPERTY, locale.getCountry());

and in LocaleServerMarshaller

String language = message.getStringProperty(OptimizerConstants.JMS_LOCALE_LANGUAGE_PROPERTY);
String country = message.getStringProperty(OptimizerConstants.JMS_LOCALE_COUNTRY_PROPERTY);
if (language != null){
country = country == null ? "" : country;
Locale locale = new Locale(language, country);
LocaleContextHolder.setLocale(locale);
}

Note that I had previously mentioned that in addition to custom String headers, you could add any object that is serializable. Unfortunately this does not work.

org.activemq.message.ActiveMqMessage.setObjectMessage() only supports String and wrappers of primitive data type. And this behavior is specified in the JMS spec.

The setObjectProperty method accepts values of class Boolean, Byte, Short, Integer, Long, Float, Double and String. An attempt to use any other class must throw a JMSException. As a result I cannot pass the java.util.Locale object directly but instead pass the country and language as separate header properties.

Update 07/21/06 : I have commited this functionality into the Lingo codebase. This feature will be available in the Lingo 1.2 release which is due any day now.

Lingo (Spring Remoting) : Passing client credentials to the server的更多相关文章

  1. Asynchronous calls and remote callbacks using Lingo Spring Remoting

    http://www.jroller.com/sjivan/entry/asynchronous_calls_and_callbacks_using Asynchronous calls and re ...

  2. 【转载】一起来学Spring Cloud | Eureka Client注册到Eureka Server的秘密

    LZ看到这篇文章感觉写得比较详细,理解以后,便转载到自己博客中,留作以后回顾学习用,喝水不忘挖井人,内容来自于李刚的博客:http://www.spring4all.com/article/180 一 ...

  3. Spring Remoting: Remote Method Invocation (RMI)--转

    原文地址:http://www.studytrails.com/frameworks/spring/spring-remoting-rmi.jsp Concept Overview Spring pr ...

  4. Spring Remoting: HTTP Invoker--转

    原文地址:http://www.studytrails.com/frameworks/spring/spring-remoting-http-invoker.jsp Concept Overview ...

  5. Spring Remoting: Burlap--转

    原文地址:http://www.studytrails.com/frameworks/spring/spring-remoting-burlap.jsp Concept Overview In the ...

  6. Spring Remoting: Hessian--转

    原文地址:http://www.studytrails.com/frameworks/spring/spring-remoting-hessian.jsp Concept Overview The p ...

  7. 在ASP.NET中基于Owin OAuth使用Client Credentials Grant授权发放Token

    OAuth真是一个复杂的东东,即使你把OAuth规范倒背如流,在具体实现时也会无从下手.因此,Microsoft.Owin.Security.OAuth应运而生(它的实现代码在Katana项目中),帮 ...

  8. [OAuth]基于DotNetOpenAuth实现Client Credentials Grant

    Client Credentials Grant是指直接由Client向Authorization Server请求access token,无需用户(Resource Owner)的授权.比如我们提 ...

  9. (转)基于OWIN WebAPI 使用OAuth授权服务【客户端模式(Client Credentials Grant)】

    适应范围 采用Client Credentials方式,即应用公钥.密钥方式获取Access Token,适用于任何类型应用,但通过它所获取的Access Token只能用于访问与用户无关的Open ...

随机推荐

  1. 南阳OJ 背包问题

     /*背包问题 时间限制:3000 ms  |  内存限制:65535 KB 难度:3 描写叙述 如今有非常多物品(它们是能够切割的),我们知道它们每一个物品的单位重量的价值v和 重量w(1< ...

  2. Unity 逻辑层通知UI 通过接口消除他们通信的switch判断

    需求如何:  用户名字长度为2-12之间,  如果错误在界面上弹出一个消息框 写烂一点 public void PostRename(string name) { if (string.IsNullO ...

  3. 织梦dedecms修改include和plus重命名提高安全性防漏洞注入挂马

    织梦dedecms是新手站长使用得比较多的一个建站开源程序,正因如此,也是被被入侵挂马比较多的程序.下面就来跟大家说一下怎么重新命名dedecms的include文件夹以及plus文件夹来提高网站的安 ...

  4. IIS7如何部署asp.net网站 (asp.net mvc 5 , asp.net 4.5 , asp.net 4.0 都可以 )

      第一步:发布网站 右键asp.net web项目,选择发布, 然后新建配置文件名称并选择 "文件系统" 发布方法. 目标位置选择本地新建的文件夹如: IISWebSite 第二 ...

  5. python selenium ---键盘事件

    转自:http://www.cnblogs.com/fnng/p/3258946.html 本节重点: l 键盘按键用法 l 键盘组合键用法 l send_keys() 输入中文运行报错问题 键盘按键 ...

  6. Tcp/Ip--正常情况下的三次握手,四次挥手

    三次握手 四次挥手

  7. 点滴积累【JS】---JS小功能(offsetLeft实现图片滚动效果)

    效果: 代码: <head runat="server"> <title></title> <style type="text/ ...

  8. Effective JavaScript Item 37 认识this的隐式指向

    本系列作为Effective JavaScript的读书笔记. CSV数据通常都会被某种分隔符进行分隔.所以在实现CSV Reader时,须要支持不同的分隔符.那么,非常自然的一种实现就是将分隔符作为 ...

  9. NEFU 117-素数个数的位数(素数定理)

    题目地址:NEFU 117 题意:给你一个整数N(N<1e9).假设小于10^N的整数中素数的个数为π(N).求π(N)的位数是多少. 思路:题目的数据量非常大,直接求肯定TLE,所以考虑素数定 ...

  10. thread_线程间协作:wait、notify、notifyAll和Condition

    经典模式:生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权.因为生产者如果不释放对临界资源的占用权,那么消费 ...