3

Implementing an oauth2 system, I am having some problems with the following code:

import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpointHandlerMapping;
import org.springframework.web.servlet.HandlerMapping;

...
HandlerMapping.class.isAssignableFrom(FrameworkEndpointHandlerMapping.class);

Indeed, as the class FrameworkEndpointHandlerMapping is implementing the interface HandlerMapping, this function should always return true. It is the case when I run a unit test on this function. However, during the startup of the server, this function returns false (checked using the debugger). This is a huge problem because when the DispatcherServlet is instantiated, it searches for the class implementing HandlerMapping and my FrameworkEndpointHandlerMapping is discarded leading to bugs in the application.

I have searched for other questions like this one but most of them answer that the isAssignableFrom method is not used correctly (mix of the class and the parameter of the function). This is not the case here since the JUnit is working.

Suspecting the imported jar files to be the problem, I have limited them to the following ones:

  • spring-beans-4.1.4.RELEASE.jar
  • spring-security-oauth2-2.0.6.RELEASE.jar
  • spring-webmvc-4.1.4.RELEASE.jar (also tried with the 4.0.9)
  • spring-core-4.1.4.RELEASE.jar
  • ...

Do you have any idea of a solution to explore please?

For more information, here are the sources:

My own DispatcherServlet (the constructor with the String as a parameter is called by the JUnit test and the default constructor is called during the startup of the system)

import org.junit.Assert;
import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpointHandlerMapping;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

public class MobileDispatcherServlet extends DispatcherServlet
{

    private static final long serialVersionUID = 1L;

    public MobileDispatcherServlet()
    {
        super();
        HandlerMapping.class.isAssignableFrom(FrameworkEndpointHandlerMapping.class);
    }

    public MobileDispatcherServlet(final WebApplicationContext webApplicationContext)
    {
        super(webApplicationContext);
        final FrameworkEndpointHandlerMapping handlerMapping = webApplicationContext.getBean(FrameworkEndpointHandlerMapping.class);

        HandlerMapping.class.isAssignableFrom(handlerMapping.getClass());
    }

    public MobileDispatcherServlet(final String test)
    {
        Assert.assertTrue(HandlerMapping.class.isAssignableFrom(FrameworkEndpointHandlerMapping.class));
    }
}

The oauth configuration file:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:security="http://www.springframework.org/schema/security"
    xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
                        http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2.xsd">

    <!-- Root of the configuration -->
    <!-- The FrameworkEndpointHandlerMapping is created here -->
    <oauth:authorization-server client-details-service-ref="clientDetails"
                                token-services-ref="tokenServices"
                                user-approval-handler-ref="userApprovalHandler"
                                authorization-endpoint-url="/oauth/authorize.action"
                                token-endpoint-url="/oauth/token.action" >
        <oauth:authorization-code authorization-code-services-ref="authorizationCodeServices" disabled="false" />
        <oauth:refresh-token />
    </oauth:authorization-server>

    ...

</beans>

The web.xml:

<web-app id="sitestorefront" version="3.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app.xsd"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app.xsd" 
         metadata-complete="true">

...

    <context-param>
        <description>
            The 'contextConfigLocation' param specifies where your configuration files are located.
            The 'WEB-INF/config/web-application-config.xml' file includes several other XML config
            files to build up the configuration for the application.
        </description>
        <param-name>contextConfigLocation</param-name>
        <param-value>WEB-INF/config/web-application-config.xml</param-value>
    </context-param>
    <context-param>
        <param-name>tagpoolMaxSize</param-name>
        <param-value>50</param-value>
    </context-param>

<servlet>
        <description>
            DispatcherServlet
        </description>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>be.sbh.site.storefront.oauth2.MobileDispatcherServlet</servlet-class>
        <init-param>
            <description>
                Specifies the location for Spring MVC to load an additional XML configuration file.
                Because hybris is already configured with the XML spring configuration files to load
                we must set this param value to EMPTY in order to prevent loading of the default
                /WEB-INF/applicationContext.xml file.
            </description>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <!-- Map all requests to the DispatcherServlet -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

...

</web-app>

The web-application-config.xml imports all the configuration files (among them, the oauth configuration file). One of them has a <mvc:annotation-driven /> to enable the mvc annotations.

I tried to limit my sources to the ones that seems relevant to me. If you need more, do not hesitate to ask me.

Thank you for reading this question,

Laurent

Martin Serrano
  • 3,727
  • 1
  • 35
  • 48
Laurent
  • 123
  • 1
  • 7
  • 1
    I suggest you look carefully at the classloader for each of those class literals - and explore (in the debugger) the `FrameworkEndpointHandlerMapping.class` in terms of superclasses etc. – Jon Skeet May 12 '15 at 15:23
  • Hmm, I once suspected something as weird, because a breakpoint was not hit in the debugger ... until I realized that breakpoints were not active during spring initialization ! Don't rely on the debugger for those early stages, but on logging (slf4j, commons logging, JUL, log4j ...) – Serge Ballesta May 12 '15 at 15:29
  • You wrote *when the DispatcherServlet is instantiated, it searches for the class implementing HandlerMapping and my FrameworkEndpointHandlerMapping is discarded*. Could you show where and how if you custom DispatcherServlet does that, and what configuration is involved if standard DispatcherServlet is supposed to do it. AFAIK it is not a standard action. – Serge Ballesta May 12 '15 at 15:47
  • 1
    @Serge: thank you for your comments. I have restarted my server to log the value of the `isAssignableFrom` but it is still false (so without the debugger). My custom `DispatcherServlet` extends the one from Spring so it has the same behaviour except for the one I added in the constructor. If you look at the source of the `DispatcherServlet`, in the method `initHandlerMappings` (line 545), it searches for all the beans that are of type `HandlerMapping`. During this search, the method `isAssignableFrom` is used and returns false which discards my `FrameworkEndpointHandlerMapping`. – Laurent May 13 '15 at 07:30
  • 1
    @Jon: Thank you for your comment. I am not sure to understand what you suggested me to do. To perform a test, I debugged the method `isAssignable` of the class `ClassUtils` where the method `isAssignableFrom` is called. There, in the values of `lhsType` and `rhsType`, I can see the complete class name and it is exactly the one needed (I mean, the "package + class name" are exactly the one I expect). However, the method `isAssignableFrom` still returns false. – Laurent May 13 '15 at 07:39
  • 1
    There's more to class identity than the class name though - there's the class *loader*. Look at the loaders for each class, and if they're different, that may well be the problem. Look at the superclass of `FrameworkEndpointHandlerMapping` - I suspect that's not the same as the `HandlerMapping` class you're looking at, probably because they're loaded by different class loaders. – Jon Skeet May 13 '15 at 08:20

1 Answers1

0

This is really weird. Facing such a problem, I would first suspect that the bean is not found, or is hidden by a bean of same name in a child application context. But as you say you managed to see that ClassUtils.isAssignableFrom was called and returned False, I admit the correct bean was found and tested.

The last problem I can imagine would be multiple instances of HandlerMapping in classpath. As suggested by JonSkeet's comment, if you have multiple spring-webmvc.jar in classpath, the classloader for FrameworkEndpointHandlerMapping could have pick one, and the classloader for your custom DispatcherServlet could have pick another one.

If you are using maven, do control the dependency graph. And anyway, do control the list of jars in lib folder of you app and of the servlet container, and the ones publically accessible through a global CLASSPATH environment variable

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • This solved my problem :-) I am trying to create the oauth context in a different project than the one used for the website (so the `web.xml` is in a different project than the `oauth-ds-context.xml`). I had added a `spring-webmvc.jar` file in both projects (same version of the file). By removing the jar file in the 'website' project, it is now working. Thank you for the help Serge and Jon! – Laurent May 13 '15 at 09:18