We already have REST web services written in Java on Spring MVC and I have been trying to protect them.
The OAuth server is implemented in another website which handles the logging in and creation of the Access Token. I need therefore to verify that the access token is correct before giving the users access to the webservices.
However the documentation for Spring Security with OAuth seems to be really poor, and the example code doesn't actually explain what it is doing! I am not even sure if I should be implementing it for this, as it should be such a simple check.
What is the best way to go about protecting these web services? And what is t开发者_C百科he best way to get started?
Thanks for you help.
Important
[Edit 12/27/2012: The tutorial I reference below is now throwing a 404. There is a slightly updated version of this tutorial on github. I've struck through the links which appear to be bad. For now I'm leaving this as-is for posterity since the now-missing tutorial is the one referenced by the asker. To the best of my knowledge, the information contained herein is still useful, so maybe someday when I have time I'll rewrite it against the new tutorial.]
This answer assumes that by "The OAuth server is implemented in another website which handles the logging in and creation of the Access Token." You mean that you are consuming a service on a separate site that is not your own.
Background
I can certainly relate to your documentation woes. Spring Security has arguably the steepest learning curve of any Spring project, and OAuth support is fairly new and maintained separately from Spring Security. The Spring Security OAuth docs are sparse.
If you don't have a good feel for OAuth, go get one! You are asking your users to trust the security of your site's implementation of this standard. As such, you cannot afford any ambiguity in your understanding of the subject! The obvious place to start is OAuth.net and the OAuth Beginner's Guide at huniverse.
If/Once you have a good feel for how OAuth works, I'd highly recommend reading through the Spring Security "Getting Started" and "Articles and Tutorials" documentation lists to get a good feel for how Spring Security is implemented in general.
Once you have a decent knowledge of Spring Security and a decent knowledge of OAuth, the official Spring Security OAuth user guide will start to make sense. You'll want to pay attention particularly the Consumer/Client sections for the version of OAuth you're working with (1.0 or 2.0).
That same site also has a decent tutorial for both OAuth 1.0 and OAuth 2.0 which is based on the second section of the services OAuth Beginner's Guide mentioned above.
Accessing Protected Restful Resources
For your problem we're going to focus on the implementation of the Tonr photo printing service from the tutorial mentioned above. This service prints photos which are OAuth protected resources hosted by external sites. Tonr defers to these sites for access control of these resources. This will include redirecting the user for user authentication and authentication confirmation if necessary.
Spring-MVC REST services/controllers which are themselves consumers of external OAuth protected resources implement this "deferred authorization" (my term) behavior through the use of request filters. Per the 1.0 user guide:
There are two request filters that are applicable to the OAuth consumer logic. The first filter, OAuthConsumerContextFilter, is responsible for establishing an OAuth-specific security context, very similar to Spring Security's SecurityContext. The security context simply contains a set of access tokens that have been obtained for the current user. This security context is leveraged when making requests for protected resources.
There is another request filter, OAuthConsumerProcessingFilter, that can be applied to specific URLs or URL patterns that require access to a remote protected resource. Putting this filter in Spring Security's filter chain will ensure that any access tokens needed for the specified URL patters will be obtained before allowing access to the resources.
So as you can see, for OAuth 1.0, filtering requests with a valid OAuthConsumerProcessingFilter
will handle everything surrounding acquiring valid Access Tokens, as well as notifying the user when access is denied. Likewise there is are corresponding OAuth2ClientContextFilter
and OAuth2ClientProcessingFilter
classes.
Finally, once this is all set up you can access OAuth protected resources in your controllers with OAuthRestTemplate
or OAuth2RestTemplate
just like you would access unprotected resources with the normal RestTemplate
(info here). However they must be injected into your service or controller with an instance of ProtectedResourceDetails or OAuth2ProtectedResourceDetails.
I have good news if this sounds complex. All of this nonsense is typically abstracted away and handled for you by the OAuth and OAuth2 XML Namespaces
The oauth namespace is demonstrated in the Tonr tutorials' XML config files located in their respective src/webapp/WEB-INF directories. The examples below are abbreviated directly from there.
If you'd like to see how the provider side works without using OAuth namespaces, I'd suggest you check this SpringSource forum post, and follow the issue SECOAUTH-53 for updates.
OAuth 1.0 Example
Tonr is consuming OAuth protected services from both Sparklr and Google here, so it sets up a ProtectedResourceDetailsService
called resourceDetails
using the oauth:resource-details-service
tag. It then sets up the OAuthConsumerContextFilter
and OAuthConsumerProcessingFilter
with a reference to resourceDetails
by using the oauth:consumer
tag. These filters are created with instances of ProtectedResourceDetails
for each of the protected resource providers by using the oauth:resource
tag.
From tonr's applicationContext.xml:
<oauth:consumer resource-details-service-ref="resourceDetails" oauth-failure-page="/oauth_error.jsp">
<oauth:url pattern="/sparklr/**" resources="sparklrPhotos"/>
<oauth:url pattern="/google/**" resources="google"/>
</oauth:consumer>
<oauth:resource-details-service id="resourceDetails">
<oauth:resource id="sparklrPhotos"
key="tonr-consumer-key"
secret="SHHHHH!!!!!!!!!!"
request-token-url="http://localhost:8080/sparklr/oauth/request_token"
user-authorization-url="http://localhost:8080/sparklr/oauth/confirm_access"
access-token-url="http://localhost:8080/sparklr/oauth/access_token"/>
<!--see http://code.google.com/apis/accounts/docs/OAuth_ref.html-->
<oauth:resource id="google" key="anonymous" secret="anonymous"
request-token-url="https://www.google.com/accounts/OAuthGetRequestToken"
user-authorization-url="https://www.google.com/accounts/OAuthAuthorizeToken"
access-token-url="https://www.google.com/accounts/OAuthGetAccessToken"
request-token-method="GET"
access-token-method="GET">
<oauth:addtionalParameter name="scope" value="https://picasaweb.google.com/data/"/>
<oauth:addtionalParameter name="xoauth_displayname" value="Tonr Example Application"/>
</oauth:resource>
</oauth:resource-details-service>
Next the sparklrService
and googleService
beans are created, each with their own internal OAuthRestTemplate
bean, each which is provided with a reference via constructor-arg
to the respective ProtectedResourceDetails
which were created previously and injected into the ProtectedResourceDetailsService
bean.
From tonr's spring-servlet.xml:
<bean id="sparklrService" class="org.springframework.security.oauth.examples.tonr.impl.SparklrServiceImpl">
<property name="sparklrPhotoListURL" value="${sparklrPhotoListURL}"/>
<property name="sparklrPhotoURLPattern" value="${sparklrPhotoURLPattern}"/>
<property name="sparklrRestTemplate">
<bean class="org.springframework.security.oauth.consumer.OAuthRestTemplate">
<constructor-arg ref="sparklrPhotos"/>
</bean>
</property>
</bean>
<bean id="googleService" class="org.springframework.security.oauth.examples.tonr.impl.GoogleServiceImpl">
<property name="googleRestTemplate">
<bean class="org.springframework.security.oauth.consumer.OAuthRestTemplate">
<constructor-arg ref="google"/>
</bean>
</property>
</bean>
OAuth 2.0 Example
My understanding is a little bit weaker here. Part of the reason for this is that the OAuth2 namespace appears to abstract away a lot more. Also, it looks like the Tonr 2 example hasn't been fleshed out as well as the original Tonr example. I'll do my best and edit if necessary.
First an oauth:client
tag is created and given a reference to an InMemoryOAuth2ClientTokenServices
bean. It appears that this sets up the appropriate filters. Then OAuth2ProtectedResourceDetails
beans are created for both sparklr and Facebook with the oauth:resource
.
From tonr 2's applicationContext.xml:
<!--apply the oauth client context-->
<oauth:client token-services-ref="oauth2TokenServices"/>
<beans:bean id="oauth2TokenServices" class="org.springframework.security.oauth2.consumer.token.InMemoryOAuth2ClientTokenServices"/>
<!--define an oauth 2 resource for sparklr-->
<oauth:resource id="sparklr" type="authorization_code" clientId="tonr"
accessTokenUri="http://localhost:8080/sparklr/oauth/authorize"
userAuthorizationUri="http://localhost:8080/sparklr/oauth/user/authorize"/>
<!--define an oauth 2 resource for facebook. according to the facebook docs, the 'clientId' is the App ID, and the 'clientSecret' is the App Secret -->
<oauth:resource id="facebook" type="authorization_code" clientId="162646850439461" clientSecret="560ad91d992d60298ae6c7f717c8fc93"
bearerTokenMethod="query" accessTokenUri="https://graph.facebook.com/oauth/access_token"
userAuthorizationUri="https://www.facebook.com/dialog/oauth"/>
Next, just like in the previous example each controller or service bean which needs access to a protected resource is created with an internal OAuth2RestTemplate
bean. This internal bean is given a reference to the correct OAuth2ProtectedResourceDetails
bean via constructor-arg
.
From tonr 2's spring-servlet.xml:
<bean id="facebookController" class="org.springframework.security.oauth.examples.tonr.mvc.FacebookController">
<!-- snipped irrelevant properties -->
<property name="facebookRestTemplate">
<bean class="org.springframework.security.oauth2.consumer.OAuth2RestTemplate">
<constructor-arg ref="facebook"/>
</bean>
</property>
<property name="tokenServices" ref="oauth2TokenServices"/>
</bean>
<bean id="sparklrService" class="org.springframework.security.oauth.examples.tonr.impl.SparklrServiceImpl">
<!-- snipped irrelevant properties -->
<property name="sparklrRestTemplate">
<bean class="org.springframework.security.oauth2.consumer.OAuth2RestTemplate">
<constructor-arg ref="sparklr"/>
</bean>
</property>
<property name="tokenServices" ref="oauth2TokenServices"/>
</bean>
精彩评论