Announcement Announcement Module
Collapse
No announcement yet.
OAuth 2 without Spring MVC? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • OAuth 2 without Spring MVC?

    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!

  • #2
    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.

    Comment


    • #3
      Originally posted by Dave Syer View Post
      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):

      Code:
      	
               <servlet>
      		<servlet-name>jersey</servlet-name>
      		<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</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.

      Comment


      • #4
        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).

        Comment


        • #5
          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.

          Comment


          • #6
            Originally posted by Dave Syer View Post
            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?

            Comment


            • #7
              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.

              Comment


              • #8
                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%2 Flocalhost%3A8080%2Ftonr2%2Ftraits%2F&response_typ e=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
                Originally posted by krishy View Post
                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?

                Comment


                • #9
                  Originally posted by harleen View Post
                  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%2 Flocalhost%3A8080%2Ftonr2%2Ftraits%2F&response_typ e=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 /*?

                  Comment


                  • #10
                    Getting contexed out...

                    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 [email protected]: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

                    Comment


                    • #11
                      Interestingly, if I add on a trailing '/':

                      Code:
                      [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:

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

                      Comment


                      • #12
                        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.
                        Last edited by Dave Syer; Jan 30th, 2012, 11:31 AM. Reason: spelling

                        Comment


                        • #13
                          Doh!

                          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.

                          Code:
                          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.client.ClientCredentialsTokenGranter.grant(ClientCredentialsTokenGranter.java:97)
                          	at org.springframework.security.oauth2.provider.CompositeTokenGranter.grant(CompositeTokenGranter.java:41)
                          	at org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.getAccessToken(TokenEndpoint.java: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

                          Comment

                          Working...
                          X