简体   繁体   中英

How to create request scoped web service port?

I have Spring MVC based web application that calls web service. Web service requires http based authentication for every call.I hold web Service Proxy configuration in applicationContext.xml:

<beans...

<bean id="paymentWebService"
      class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
    <property name="customProperties">
        <ref local="jaxwsCustomProperties"/>
    </property>
    <property name="serviceInterface" value="com.azercell.paymentgateway.client.PaymentGatewayWebService"/>
    <property name="wsdlDocumentUrl"
              value="#{config.wsdlDocumentUrl}"/>
    <property name="namespaceUri" value="http://webservice.paymentgateway.azercell.com/"/>
    <property name="serviceName" value="PaymentGatewayWebServiceImplService"/>
    <property name="portName" value="PaymentGatewayWebServiceImplPort"/>
</bean>

I have instance field for web service port in my controller:

@Controller
public class PaymentController {

@Resource(name = "paymentWebService")
private PaymentGatewayWebService paymentPort;

@ModelAttribute
public void ajaxAttribute(WebRequest request, Model model) {
    UtilMethods.authenticationWebServicePort(paymentPort);
    ...
}
...

@RequestMapping(value = "/massPayment", method = RequestMethod.POST)
public String massPayment(@RequestParam String amount, @RequestParam MultipartFile file, Model model, Locale locale) {
    ...

    WebServiceResponse response = paymentPort.massPayment(UtilMethods.getNewRequestId()
             , fileUploader, UtilMethods.getAmountInCoins(amountBigDecimal), null);

    ...      

    return SpringView.MASS_PAYMENT.toString(ajaxRequest);
   }
}

Code of UtilMethods.authenticationWebServicePort:

public static void authenticationWebServicePort(PaymentGatewayWebService paymentPort) {
    String username = (String) RequestContextHolder.currentRequestAttributes().getAttribute(USERNAME_SESSION_VARIABLE_NAME, RequestAttributes.SCOPE_SESSION);
    String password = (String) RequestContextHolder.currentRequestAttributes().getAttribute(PASSWORD_SESSION_VARIABLE_NAME, RequestAttributes.SCOPE_SESSION);
    BindingProvider prov = (BindingProvider) paymentPort;
    prov.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, username);
    prov.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, password);
}

As controllers are singleton objects there occurring problems every time when requests overlap, if to be precisely one user by mistake can use web methods with username and password that belong to another user.

To prevent this I tried to make web service port request scoped in configuration as below:

<bean id="paymentWebService"
  class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean" scope="request">

in this case I got error:

    SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.filterChains': Cannot resolve reference to bean 'org.springframework.security.web.DefaultSecurityFilterChain#0' while setting bean property 'sourceList' with key [0]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.web.DefaultSecurityFilterChain#0': Cannot resolve reference to bean 'org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#0' while setting constructor argument with key [2]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#0': Cannot resolve reference to bean 'org.springframework.security.authentication.ProviderManager#0' while setting bean property 'authenticationManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.authentication.ProviderManager#0': Cannot resolve reference to bean 'org.springframework.security.config.authentication.AuthenticationManagerFactoryBean#0' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.config.authentication.AuthenticationManagerFactoryBean#0': FactoryBean threw exception on object creation; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.authenticationManager': Cannot resolve reference to bean 'myAuthenticationProvider' while setting constructor argument with key [0]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myAuthenticationProvider': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'paymentWebService': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

How to create request scoped web service port?

Try to expose current http request to spring. Just add RequestContextListener in your web.xml :

<web-app>
...
<listener>
  <listener-class>
      org.springframework.web.context.request.RequestContextListener
  </listener-class>
</listener>
...
</web-app>

See this entry from official documentation for more details / options.

EDIT. Think about the difference in lifecycle between controller and paymentPort dependency. Controller is always the same, but paymentPort must be refreshed for each new request. So you cann't continue inject it directly. You need to get fresh instance for each request. You can do it using javax.inject.Provider interface.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM