I too am making use of Jersey to create a Spring based SaaS app that serves only RESTful APIs. I was in the process of updating the various frameworks in use, and wanted to move from 1.0.0.M4 to 1.0.0.M5, but also to Spring 3.1.0.RELEASE since there are some new features (e.g. profiles) that I would like to work with.
Anyway, the security config is seriously different going from M4 to M5, and I ended up with the "not found" problem when trying to access .../appcontext/oauth/{token,authorize}. My application does not contain any user specific data, only tenant specific data. Tenant applications use their client credentials to obtain bearer tokens on behalf of user agents accessing their sites.
I'm trying to untangle what I need for working with M5 (and beyond?). My application is both the authorization server and the resource server. Looking at documentation on github, the M5 samples etc. and this thread, I managed to configure security in the context of the DispatcherServlet (the authorization server?), since it wants to load WEB-INF/servletname-servlet.xml. I had simply used ContextLoaderListener, now I have them both.
Sticking with the naming ("spring") in the examples for the time being, here is WEB-INF/spring-servlet.xml:
Code:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-1.0.xsd">
<!-- Note that ISEC is both an OAuth authorization server as well as the
resource server.
-->
<http auto-config="true" create-session="never" access-decision-manager-ref="accessDecisionManager">
<intercept-url pattern="/api/rest/v1/client/**" access="ROLE_CLIENT,SCOPE_READ" />
<intercept-url pattern="/api/rest/v1/partner/**" access="ROLE_PARTNER,SCOPE_WRITE" />
<intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<intercept-url pattern="/oauth/authorize" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<intercept-url pattern="/oauth/**" access="ROLE_CLIENT,ROLE_PARTNER" />
<custom-filter ref="resourceServerFilter" after="EXCEPTION_TRANSLATION_FILTER"/>
</http>
<oauth:resource-server id="resourceServerFilter" resource-id="isecApi" token-services-ref="tokenServices"/>
<oauth:authorization-server client-details-service-ref="clientDetailsService" token-services-ref="tokenServices">
<oauth:client-credentials />
</oauth:authorization-server>
<authentication-manager/>
<beans:bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.RandomValueTokenServices">
<beans:property name="tokenStore" ref="tokenStore"/>
<beans:property name="supportRefreshToken" value="false" />
</beans:bean>
<beans:bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased">
<beans:constructor-arg>
<beans:list>
<beans:bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter" />
<beans:bean class="org.springframework.security.access.vote.RoleVoter" />
<beans:bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
</beans:list>
</beans:constructor-arg>
</beans:bean>
</beans:beans>
The beans named tokenServices and clientDetailsService are defined in a different context file.
And web.xml:
Code:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:base-appContext.xml
classpath:cassandra-appContext.xml
classpath:search-appContext.xml
classpath:service-appContext.xml
classpath:resource-appContext.xml
classpath:integration-appContext.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>resourceServerFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>contextAttribute</param-name>
<param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.spring</param-value>
</init-param>
</filter>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>contextAttribute</param-name>
<param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.spring</param-value>
</init-param>
</filter>
<!-- Accept header filter. Enable API content type suffixes for Jersey -->
<filter>
<filter-name>acceptHeaderFilter</filter-name>
<filter-class>com.company.isec.util.AcceptHeaderServletFilter</filter-class>
<!-- Map suffixes to content types -->
<init-param>
<param-name>xml</param-name>
<param-value>application/xml</param-value>
</init-param>
<init-param>
<param-name>json</param-name>
<param-value>application/json</param-value>
</init-param>
</filter>
<!-- Jersey (JAX-RS) servlet for implementing RESTful APIs -->
<servlet>
<servlet-name>jerseySpringServlet</servlet-name>
<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
<param-value>com.sun.jersey.api.container.filter.GZIPContentEncodingFilter</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
<param-value>com.sun.jersey.api.container.filter.GZIPContentEncodingFilter</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
<param-value>com.sun.jersey.api.core.PackagesResourceConfig</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.company.isec.ws.resource,com.company.isec.ws.provider</param-value>
</init-param>
</servlet>
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<filter-mapping>
<filter-name>resourceServerFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>acceptHeaderFilter</filter-name>
<url-pattern>/api/rest/*</url-pattern>
</filter-mapping>
<servlet-mapping>
<servlet-name>jerseySpringServlet</servlet-name>
<url-pattern>/api/rest/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/notused</url-pattern>
</servlet-mapping>
</web-app>
In the log output I see:
Code:
...
2012-01-29 21:01:16 INFO annotation.DefaultAnnotationHandlerMapping - Mapped URL path [/oauth/token] onto handler 'tokenEndpoint'
2012-01-29 21:01:16 INFO annotation.DefaultAnnotationHandlerMapping - Mapped URL path [/oauth/token.*] onto handler 'tokenEndpoint'
2012-01-29 21:01:16 INFO annotation.DefaultAnnotationHandlerMapping - Mapped URL path [/oauth/token/] onto handler 'tokenEndpoint'
2012-01-29 21:01:16 INFO servlet.DispatcherServlet - FrameworkServlet 'spring': initialization completed in 376 ms
2012-01-29 21:01:16.284:INFO::Started SelectChannelConnector@0.0.0.0:9090
[INFO] Started Jetty Server
That's very encouraging. However, using an existing test command to acquire a bearer token results in:
Code:
[imac:~] jas% curl -w "\nhttp code: %{http_code}\n" -d "grant_type=client_credentials&client_id=myClientId&client_secret=myClientSecret" "http://localhost:9090/isec/oauth/token"
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
<title>Error 404 Not Found</title>
</head>
<body><h2>HTTP ERROR 404</h2>
<p>Problem accessing /isec/oauth/token. Reason:
<pre> Not Found</pre></p><hr /><i><small>Powered by Jetty://</small></i><br/>
<br/>
That results in a POST to the token endpoint, which all worked great in my prior incarnation.
Any ideas on why authorization server functionality is not quite right for me?
Thanks,
Jeff