Java Config: Integrating JAX-RS Jersey with Spring Security


The Scenario
When we were developing the admin backend, we split it into 2 projects: backend application using jax-rs jersey, front end with angularjs - following single app design principle.

Later we decide to merge these 2 into one project, and use Spring security to protect front side(web url, page section etc) and jersey jax-rs.

We prefer Spring Java config over xml because it's more flexible, and XML configuration is kind of black box to developers,  using Java config, we can know more about the implementation and help us debug it later.

Update:
Later we upgrade to jersey 2:

@Priority(value = 1)
public class MyWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] {MyAppConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return null;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] {"/"};
    }

    @Override
    public void onStartup(final ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        // this is to fix issues in jersey-srping3
        // org.glassfish.jersey.server.spring.SpringWebApplicationInitializer
        servletContext.setInitParameter("contextConfigLocation", "");
        servletContext.addListener(RequestContextListener.class);
        MyUtil.addDefaultUncaughtExceptionHandler();
    }

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        final WebApplicationContext context = super.createRootApplicationContext();
        final ConfigurableEnvironment env = (ConfigurableEnvironment) context.getEnvironment();
        final String profile = (String) env.getSystemProperties().get("env");
        env.setActiveProfiles(profile);
        return context;
    }
}

public class MySecurityInitializer extends AbstractSecurityWebApplicationInitializer {}

@WebServlet(loadOnStartup = 1)
@ApplicationPath("/v1/*")
public class MyJerseyApplication extends ResourceConfig {
    public JerseyRestProvisionApplication() {
        packages("the_package_xx");
        property(ServerProperties.WADL_FEATURE_DISABLE, true);
        register(JacksonFeature.class);
        register(GZipEncoder.class);
        register(MultiPartFeature.class);

        register(new LoggingFilter(Logger.getLogger(MyJerseyApplication.class.getName()), true));
        register(RequestContextFilter.class);
        register(MyHttpHeaderFilter.class);
    }
}
How To
Basically we configure two servlets:

  • web.servlet.DispatcherServlet: map it to / (notice not /*). This handles Spring Security and Spring MVC.
  • Jersey SpringServlet: map to v1(all rest API starts with v1/).

Spring security is used to do authentication and authorization.

Jersery 1

public class AppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(final ServletContext servletContext) {
        final AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(AppConfig.class);

        final ConfigurableEnvironment env = rootContext.getEnvironment();

        final String profile = (String) env.getSystemProperties().get("env");
        env.setActiveProfiles(profile);

        servletContext.addListener(new ContextLoaderListener(rootContext));

        addSpringServlet(servletContext, profile);
        addSpringJersyServlet(servletContext, env);
    }

    protected void addSpringServlet(final ServletContext servletContext, final String profile) {

        final DispatcherServlet dispatcherServlet = new DispatcherServlet(new GenericWebApplicationContext());

        final Dynamic dispatcherDynamic = servletContext.addServlet("dispatcherServlet", dispatcherServlet);;
        dispatcherDynamic.setLoadOnStartup(1);

        dispatcherDynamic.addMapping("/");

        servletContext
                .addFilter(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME, DelegatingFilterProxy.class)
                .addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), false, "/*");
    }

    protected void addSpringJersyServlet(final ServletContext servletContext, final ConfigurableEnvironment env) {
        final ServletRegistration.Dynamic appServlet = servletContext.addServlet("jersey-servlet", new SpringServlet());

        appServlet.setInitParameter(JSONConfiguration.FEATURE_POJO_MAPPING, "true");
        appServlet.setInitParameter(ResourceConfig.FEATURE_TRACE, env.getProperty("jersey.enable.trace", "false"));
        appServlet.setInitParameter(ResourceConfig.FEATURE_DISABLE_WADL,
                env.getProperty("jersey.disable.wadl", "true"));

        appServlet.setInitParameter(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,..);
        appServlet.setInitParameter(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS, ..);

        appServlet.setLoadOnStartup(2);
        appServlet.addMapping("/v1/*");
    }
}
Resources
Difference between / and /* in servlet mapping url pattern
http://javapapers.com/servlet/what-is-servlet-mapping/

Labels

adsense (5) Algorithm (69) Algorithm Series (35) Android (7) ANT (6) bat (8) Big Data (7) Blogger (14) Bugs (6) Cache (5) Chrome (19) Code Example (29) Code Quality (7) Coding Skills (5) Database (7) Debug (16) Design (5) Dev Tips (63) Eclipse (32) Git (5) Google (33) Guava (7) How to (9) Http Client (8) IDE (7) Interview (88) J2EE (13) J2SE (49) Java (186) JavaScript (27) JSON (7) Learning code (9) Lesson Learned (6) Linux (26) Lucene-Solr (112) Mac (10) Maven (8) Network (9) Nutch2 (18) Performance (9) PowerShell (11) Problem Solving (11) Programmer Skills (6) regex (5) Scala (6) Security (9) Soft Skills (38) Spring (22) System Design (11) Testing (7) Text Mining (14) Tips (17) Tools (24) Troubleshooting (29) UIMA (9) Web Development (19) Windows (21) xml (5)