HttpInvoker session 解决

How to Integrate Browser Authentication with a Java Web Start Application

This article describes a simple technique for integrating browser based
authentication with a Java Web Start application that uses Acegi Security
System for Spring and the Spring Framework's HttpInvoker to communicate to
the server.

I used this approach for dynamic websites where a secured section of the
site is used for administration of data. Rather than fighting HTML and
attempting to provide a slick administration interface using AJAX, I
decided there was no good reason not to use a Java Web Start application and
benefit from a Swing rich client. As some site administration was
already performed using web pages, a way of integrating the authentication of
web page based administration and rich client based administration was required
to prevent the need for multiple logins.
This article describes the solution used.

Approach Summary

The approach is based on passing the servlet session id into the Java
Web Start application via a dynamicaly generated JNLP file. The Web Start
application then appends the session id to HTTP requests to remote services
that are protected by Acegi Security.

The scenario is as follows:

  • The user authenticates themselves through the browser.
  • The user requests the Java Web Start application from within the
    authenticated session.
  • The server dynamically generates the Web Start JNLP file for the
    client application providing the id of the session to the application
    as a system property.
  • For each security protected service that the client application
    calls over HTTP, the client appends the servlet session id to the URL
    of the request.

Dynamic Generation of JNLP File

The following code snippet shows a sample JSP file used to generate
a JNLP file that can supply properties to a Web Start application
for accessing secured remote services..

<%@ include file="/WEB-INF/jsp/includes.jsp" %>
<% response.setContentType("application/x-java-jnlp-file"); %>
<?xml version="1.0" encoding="utf-8"?>
<!-- JNLP File for Administration Tool -->
<jnlp
  spec="1.5+"
  codebase="http://<%= request.getServerName()%>/app"
  href="admin/adminApp.jnlp">
  <information>
    <title>Administration Tool</title>
    .........
  </information>
  <security>
      <all-permissions/>
  </security>
  <resources>
    <j2se version="1.5+" java-vm-args="-esa -Xnoclassgc "/>
    <property
      name="sid"
      value="$\{cookie['JSESSIONID'].value\}"/>
    <property
      name="serviceHost"
      value="<%=request.getServerName()%>"/>
    <jar href="myApp.jar"/>
  </resources>
  <application-desc
    main-class="com.mycompany.admin.AdminTool"/>
</jnlp>

The key part of the JNLP file are the properties passed into the JVM at the end of the example. The 'sid' property is the value of the JSESSIONID cookie for the current authenticated session and the 'serviceHost' property is the name of the server that hosts the JNLP file.

Supplying these two properties is enough to allow the client to make use of the authenticated browser session to call server-side services using HttpInvoker.

Remote Services

Using HttpInvoker it is very simple to expose services to remote clients. The example below shows two services that are to be used by the example client application.

<!--
  - Application context for remote services layer.
  -->
<beans>

  <bean
      name="/DirectoryService"
      class="org.springframework.remoting.httpinvoker \
               .HttpInvokerServiceExporter">
    <property
        name="service"
        ref="directoryService" />
    <property
        name="serviceInterface"
        value="com.mycompany.domain.logic.DirectoryService"/>
  </bean>

  <bean name="/IndexingService"
        class="org.springframework.remoting.httpinvoker \
               .HttpInvokerServiceExporter">
    <property
        name="service"
        ref="indexingService" />
    <property
        name="serviceInterface"
        value="com.mycompany.domain.logic.IndexingService"/>
  </bean>

</beans>

In this example, the beans are defined in a separate DispatcherServlet with its own context and a URL pattern of /remote*.

Securing Services

The remote services offered by the server-side application are secured using Acegi Security. The sample bean definition below shows how two URL paths are configured to accessable to users with the role, ROLE_ADMIN. In this example the URL paths in the application context starting with /admin (any web based admin screens are under this path) and /remote (any remote services are under this path).

<beans>
  .....
  <bean id="filterSecurityInterceptor"
        class="org.acegisecurity.intercept.web. \
               FilterSecurityInterceptor">
    <property name="authenticationManager">
      <ref bean="authenticationManager"/>
    </property>
    <property name="accessDecisionManager">
      <ref bean="accessDecisionManager"/>
    </property>
    <property name="objectDefinitionSource">
      <value>
        CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
        \A/admin.*\Z=ROLE_ADMIN
        \A/remote.*\Z=ROLE_ADMIN
      </value>
    </property>
  </bean>
  .....
</beans>

With this example definition, both web based administration screens and remote services used by a rich client are secured and accessible only by users with the role, ROLE_ADMIN.

Accessing Remote services

In the Spring context of the client application, beans are defined to allow access to the remote services. In the example below, the bean definition required to access the remote indexing service is shown.

<beans>
  .....
  <!-- Remote indexing service -->
  <bean
    id="httpInvokerISProxy"
    class="com.mycompany.admin \
           .SesssionHttpInvokerProxyFactoryBean">
    <property
      name="serviceUrl"
      value=
        "http://${serviceHost}/app/remote/IndexingService"/>
    <property name="httpInvokerRequestExecutor"
      ref="commonsHttpInvokerRequestExecutor"/>
    <property
      name="serviceInterface"
      value="com.mycompany.domain.logic.IndexingService"/>
  </bean>

  <bean
        id="indexingServiceClient"
        class="com.mycompany.admin.IndexingServiceClient"
        lazy-init="false">
    <property
      name="indexingService"
      ref="httpInvokerISProxy"/>
  </bean>

  <bean
    id="commonsHttpInvokerRequestExecutor"
    class="org.springframework.remoting.httpinvoker. \
           CommonsHttpInvokerRequestExecutor"/>
  .....
</beans>

The important points to note in the above definitions are:

  • The Spring supplied HttpInvokerProxyFactoryBean class has been extended to allow the session id to be appended to every service request (see details below).
  • The serviceUrl passed to SesssionHttpInvokerProxyFactoryBean embeds the system property 'serviceHost' (i.e. the property passed to the client in the dynamically generated JNLP file)
  • Rather than using the default HttpInvokerRequestExecutor, the CommonsHttpInvokerRequestExecutor is used. This needs to be used because the default SimpleHttpInvokerRequestExecutor attempts to establish a new session each time a service is called rather than using the existing session identified by the session id appended to the service URL.

The code below for SesssionHttpInvokerProxyFactoryBean overrides HttpInvokerProxyFactoryBean setServiceUrl method to allow the jsessionid of the users current session to be appended to the service url. This allows service urls protected by Acegi Security to be accessed by the HttpInvoker client.

    ......

    public class SesssionHttpInvokerProxyFactoryBean
        extends HttpInvokerProxyFactoryBean {

      private String sessionIdPostfix;

      public SesssionHttpInvokerProxyFactoryBean() {
        super();
        String jsessionid = System.getProperty("sid");
        if (jsessionid != null)
          sessionIdPostfix = ";jsessionid=" + jsessionid;
        else
          sessionIdPostfix = "";
      }

      public void setServiceUrl(String serviceUrl) {
        super.setServiceUrl(serviceUrl + sessionIdPostfix);
      }
    }

Conclusions

The approach is simple to implement and deploy, and has worked successfully in practice.

Potential issues

Session timeout

If neither the Web Start client application or the browser based application are used within the timeout period of the session, the session expires and an error will occur the next time the application makes a request to a secured service. To re-establish a new session, the client application must be closed and restarted in a new session after re-authenticating in the browser.

Options to consider to reduce or resolve this problem include: (i) extending the length of the session timeout on the server; (ii) adding a scheduled 'keep-alive' request from the client to the server; (iii) adding logic to the client to enable it to establish a new session by re-requesting the users credentials.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值