PDA

View Full Version : OAuth 2 without Spring MVC?



krishy
Nov 10th, 2011, 05:16 PM
I have been using spring-security for our web application and would now like to use oauth 2 for our REST APIs. The web application does not use Spring MVC. spring-security-oauth2, however, seems to depend on Spring MVC which would mean it can be used only in web-apps that use Spring MVC.

Is my understanding correct?

Thanks!

Dave Syer
Nov 11th, 2011, 02:55 AM
Not precisely. The OAuth2 provider support is implemented using Spring MVC, but your app only needs to use Spring for that, not for anything else if you don't want to. Since you are already using Spring Security it doesn't seem like a big difference. Just keep the DispatcherServlet on it's own path.

krishy
Nov 11th, 2011, 10:09 AM
Just keep the DispatcherServlet on it's own path.

Thanks for the pointer.

How would I go about wiring the REST API's along with this. The REST APIs are built using Jersey.
Currently web.xml contains (among other things):



<servlet>
<servlet-name>jersey</servlet-name>
<servlet-class>com.sun.jersey.spi.spring.container.servlet.Spring Servlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>jersey</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>


What url-mapping would I use for the DispatcherServlet and how would that work with Jersey? I am sorry if the question is fairly basic. I am not familiar with Spring MVC.

Dave Syer
Nov 12th, 2011, 04:07 AM
Look at the web.xml for the OAuth2 samples (or any Spring MVC application). You need to add a DispatcherServlet for the OAuth endpoints and tell it where to get its application context from. The samples map DispatcherServlet to /, and that might work because it doesn't clash with your Jersey servlet. I would try that first and see if you can get it working before you go off piste with the OAuth endpoint URLs (they are set to /oauth/{token,authorize} by default, and to change them requires adding another filter in web.xml - something I haven't got round to documenting yet, but you will be a perfect guinea pig).

Dave Syer
Nov 12th, 2011, 04:12 AM
But, actually, now I come to think of it, you don't need the endpoints do you, if it's just a resource server? So you don't need a DispatcherServlet. You should be able to add the protected resource filter (via <oauth:resource-server/>) to your jersey endpoints via the regular Spring Security context wherever you put that.

krishy
Nov 29th, 2011, 01:34 PM
Look at the web.xml for the OAuth2 samples (or any Spring MVC application). You need to add a DispatcherServlet for the OAuth endpoints and tell it where to get its application context from. The samples map DispatcherServlet to /, and that might work because it doesn't clash with your Jersey servlet. I would try that first and see if you can get it working before you go off piste with the OAuth endpoint URLs (they are set to /oauth/{token,authorize} by default, and to change them requires adding another filter in web.xml - something I haven't got round to documenting yet, but you will be a perfect guinea pig).

Unfortunately / conflicts with the Struts 2 dispatcher. I guess you are referring to setting up the oauth2EndpointUrlFilter?



But, actually, now I come to think of it, you don't need the endpoints do you, if it's just a resource server? So you don't need a DispatcherServlet. You should be able to add the protected resource filter (via <oauth:resource-server/>) to your jersey endpoints via the regular Spring Security context wherever you put that.


If I am to provide the tokens don't I need the end points?

Dave Syer
Nov 30th, 2011, 02:09 AM
To your first point: I didn't say you had to use / for your DispatcherServlet, just that the samples are wired like that. Did you try it?

To your second point, yes you need the endpoints to provide tokens. I didn't get that requirement from your original post - you only mentioned REST APIs, which would be protected resources.

harleen
Jan 20th, 2012, 11:31 AM
Hi krishy,

I was wondering if you were ever able to get the authorization server working while in a webapp that has struts2 handling the /* context.

I have tried specifying the custom oauth2 endpoints via the authorization server configuration and specifying the necessary filter before spring security in the web.xml with no luck. Whenever I hit the authorization URL /<appName>/api-access/oauth/authorize?client_id=tonr&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Ftonr2 %2Ftraits%2F&response_type=code&scope=read, I get a 404.

Also, when I come from the client (in this case, a modified tonr2 sample app), the request that comes into tomcat is /<appName>/api-access/oauth/authorize, but when I do the app login, I get sent to /<appName>/oauth/authorize. This also results in a 404.

Is there something I'm missing?

Thanks,
Harleen

Unfortunately / conflicts with the Struts 2 dispatcher. I guess you are referring to setting up the oauth2EndpointUrlFilter?



If I am to provide the tokens don't I need the end points?

krishy
Jan 20th, 2012, 02:09 PM
I have tried specifying the custom oauth2 endpoints via the authorization server configuration and specifying the necessary filter before spring security in the web.xml with no luck. Whenever I hit the authorization URL /<appName>/api-access/oauth/authorize?client_id=tonr&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Ftonr2 %2Ftraits%2F&response_type=code&scope=read, I get a 404.

Also, when I come from the client (in this case, a modified tonr2 sample app), the request that comes into tomcat is /<appName>/api-access/oauth/authorize, but when I do the app login, I get sent to /<appName>/oauth/authorize. This also results in a 404.


No, I did not follow this through. I ended up forking the project and adding some rudimentary Jersey support for the RO authorization type (since the web app I am working is built with Struts 2 and Jersey).

To answer your question: could you please paste your web.xml configuration? More specifically the mappings for /*?

jas
Jan 29th, 2012, 11:09 PM
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:



<?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.Unanimous Based">
<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.Authentic atedVoter" />
</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:



<?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.ContextLoaderListe ner</listener-class>
</listener>

<filter>
<filter-name>resourceServerFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterPro xy</filter-class>
<init-param>
<param-name>contextAttribute</param-name>
<param-value>org.springframework.web.servlet.FrameworkServlet.C ONTEXT.spring</param-value>
</init-param>
</filter>

<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterPro xy</filter-class>
<init-param>
<param-name>contextAttribute</param-name>
<param-value>org.springframework.web.servlet.FrameworkServlet.C ONTEXT.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.Spring Servlet</servlet-class>
<load-on-startup>1</load-on-startup>
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerRequestFilte rs</param-name>
<param-value>com.sun.jersey.api.container.filter.GZIPContentEnc odingFilter</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerResponseFilt ers</param-name>
<param-value>com.sun.jersey.api.container.filter.GZIPContentEnc odingFilter</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.p rovider</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:



...
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:



[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

jas
Jan 29th, 2012, 11:11 PM
Interestingly, if I add on a trailing '/':



[imac:~] jas% curl -w "\nhttp code: %{http_code}\n" --include -d "grant_type=client_credentials&client_id=myClientId&client_secret=myClientSecret" "http://localhost:9090/isec/oauth/token/"
HTTP/1.1 302 Found
WWW-Authenticate: Bearer
Location: http://localhost:9090/isec/spring_security_login
Content-Length: 0
Server: Jetty(7.2.2.v20101205)

http code: 302
[imac:~]


So, I'm being challenged to provide a bearer token. But, that's what I'm trying to acquire using my client credentials. I'm guessing this is due to:



<intercept-url pattern="/oauth/**" access="ROLE_CLIENT,ROLE_PARTNER" />


Roles where specified in the example config, and setting that to IS_AUTHENTICATED_ANONYMOUSLY gets me back to "not found". Makes sense I guess.

Cheers,

Jeff

Dave Syer
Jan 30th, 2012, 10:31 AM
As far as I can tell your web.xml doesn't map any valid URLs onto the "spring" servlet. It will only serve up tokens for you if it is allowed to handle /oauth/token (by default) - and I think that's probably relative to the servlet path as well, so maybe if you map /oauth/** to the spring servlet you might make some progress.

The WWW-Authenticate header is clearly wrong (please raise a JIRA if you have a chance), but that's not the main problem here.

jas
Jan 30th, 2012, 11:30 AM
Thanks Dave!

For whatever reason I figured the DispatcherServlet's context def was what was important and not the servlet itself. So, I had it mapped to /notused. :) Changing it to / (per the examples), I got something more to my liking.

Looks like I need to go over my changes made to my services when moving from M4 to M5.



2012-01-30 10:09:37 DEBUG cassandra.CassandraClientDetailsService - loadClientByClientId - loading details for clientId: myClientId
2012-01-30 10:09:37 DEBUG cassandra.CassandraClientDetailsService - loadClientByClientId - for clientId: myClientId, returning details: IsecClientDetails [ clientId: myClientId, tenantId: myTentantId, resourceIds: null, grantTypes: [client_credentials], scope: null, authorities: [ROLE_CLIENT], solrSearchHandler: partner-tmo, solrProductCore: partner-tmo]
2012-01-30 10:09:37.963:WARN::/isec/oauth/token
java.lang.NullPointerException
at java.util.HashSet.<init>(HashSet.java:99)
at org.springframework.security.oauth2.provider.clien t.ClientCredentialsTokenGranter.grant(ClientCreden tialsTokenGranter.java:97)
at org.springframework.security.oauth2.provider.Compo siteTokenGranter.grant(CompositeTokenGranter.java: 41)
at org.springframework.security.oauth2.provider.endpo int.TokenEndpoint.getAccessToken(TokenEndpoint.jav a:60)


At least I can look at my own code for now.

Once I get this thing running again, I'll look into submitting the JIRA you suggested.

Cheers,

Jeff