Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.


...

Span
style
font-size:2em;font-weight:bold

...

JAX-RS:

...

OAuth

...



Table of Contents

Warning

OAuth 1.0 support in CXF was dropped from the 3.5.0 release onwards

Introduction

CXF 2.5.0 implements OAuth 1.0. Please also see the JAX-RS OAuth2 about the OAuth2 support in CXF.

...

7. After getting an access token token, the service finally proceeds with accessing the current user's resources and completes the user's request.

...

Maven dependencies

Code Block
xml
xml

<dependency>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-rt-rs-security-oauth</artifactId>
  <version>2.5.0</version>
</dependency>

...

CXF offers 3 JAX-RS service implementations that can be used to create OAuth 1.0 servers fast: RequestTokenService, AuthorizationRequestService and AccessTokenService.

All of these 3 services rely on the custom OAuthDataProvider which manages request and access tokens. Writing your own OAuthDataProvider implementations is what is needed to get the OAuth server up and running.

RequestTokenService

The main responsibility of of RequestTokenService is to create a temporarily request token and return it back to the consumer. It supports POST and GET requests and returns a form payload containing the new request token and its secret.

Here is an example request log:

Code Block
xml
xml

Address: http://localhost:8080/services/oauth/initiate
Encoding: ISO-8859-1
Http-Method: POST
Content-Type: */*
Headers: {
Accept=[application/x-www-form-urlencoded], 

Content-Length=[0],

Authorization=[OAuth oauth_callback="http%3A%2F%2Flocalhost%3A8080%2Fservices%2Freservations%2Freserve%2Fcomplete", 
                     oauth_nonce="e365fa02-772e-4e33-900d-00a766ccadf8", 
                     oauth_consumer_key="123456789", 
                     oauth_signature_method="HMAC-SHA1", 
                     oauth_timestamp="1320748683", 
                     oauth_version="1.0", 
                     oauth_signature="ztTQuqaJS7L6dNQwn%2Fqi1MdaqQQ%3D"] 
}

...

Before asking OAuthDataProvider to generate a request token, it attempts to validate a callback URI against a Client's application URI.

Finally it delegates to OAuthDataProvider to create a request token, passing to it a populated RequestTokenRegistration bean.

This bean references a Client instance, callback URI and a state. State is something that a consumer may also include during the request token request using a "state" parameter and will be returned back to the consumer alongside the verifier after the request token has been authorized. For example, it may represent a key that a consumer will use to retrieve the state of the request that it was processing when requesting a token. For OAuth 1.0
consumers, the request token itself may represent a good enough key for such purposes, but "state" may need to be used too and will become more useful for OAuth 2.0.

...

Finally, one more property that may be set on this bean instance: list of scopes. List of scopes represents optional permissions that the consumer may need to access the resources. These can be provided by an "x_oauth_scope" ("scope" in OAuth 2.0) request parameter, for example,

Code Block
xml
xml

Authorization=[OAuth ..., 
                     x_oauth_scope="readCalendar updateCalendar"]

It's expected that each of the x_oauth_scope values such as "readCalendar" and "updateCalendar" are translated into OAuthPermissions during the creation of a new request token. If no x_oauth_scope parameter is provided then the OAuth data provider will likely assign a default OAuthPermission instance to the new token.

After a new request token has been created by OAuthDataProvider, RequestTokenService returns the token key and secret pair to the consumer:

Code Block
xml
xml

Response-Code: 200
Content-Type: application/x-www-form-urlencoded
Headers: {Date=[Tue, 08 Nov 2011 10:38:03 GMT]}
Payload: 
oauth_callback_confirmed=true&oauth_token=6dfd5e52-236c-4939-8df8-a53212f7d2a2&oauth_token_secret=ca8273df-b9b0-43f9-9875-cfbb54ced550

The consumer is now ready to redirect the current end user to AuthorizationRequestService.

AuthorizationRequestService

...

Remember that a third-party consumer redirects the current user to AuthorizationRequestService, for example, here is how a redirection may happen:

Code Block
xml
xml

Response-Code: 303
Headers: {Location=[http://localhost:8080/services/social/authorize?oauth_token=f4415e16-56ea-465f-9df1-8bd769253a7d]}

The consumer application asks the current user (the browser) to go to a new address provided by the Location header and the follow-up request to AuthorizationRequestService will look like this:

Code Block
xml
xml

Address: http://localhost:8080/services/social/authorize?oauth_token=6dfd5e52-236c-4939-8df8-a53212f7d2a2
Http-Method: GET
Content-Type: 
Headers: {
Accept=[text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8],   
Referer=[http://localhost:8080/services/forms/reservation.jsp], 
...
}

First, AuthorizationRequestService will retrieve RequestToken (which extends the base Token class) from OAuthDataProvider using the value provided by the "oauth_token" query parameter.

Next it uses this token (which also links to Client) to populate an instance of OAuthAuthorizationData bean and returns it. OAuthAuthorizationData contains application name and URI properties, optional list of Permissions and URIs.

Two other important OAuthAuthorizationData properties are "oauthToken" and "authenticityToken", both are important for processing the decision request coming from the authorization form. The former is a request token key which will be used by AuthorizationRequestService to retrieve the RequestToken again and the latter for validating that the current session has not been hijacked - AuthorizationRequestService generates a random key, stores it in a Servlet HTTPSession instance and expects the returned authenticityToken value to match it - this is a recommended approach and it also implies that the authenticityToken value is hidden from a user, for example, it's kept in a 'hidden' form field.

...

Assuming RequestDispatcherProvider is used, the following example log shows the initial response from AuthorizationRequestService:

Code Block
xml
xml

08-Nov-2011 13:32:40 org.apache.cxf.jaxrs.provider.RequestDispatcherProvider logRedirection
INFO: Setting an instance of "org.apache.cxf.rs.security.oauth.data.OAuthAuthorizationData" 
as HttpServletRequest attribute "data" and redirecting the response to "/forms/oauthAuthorize.jsp".

08-Nov-2011 13:32:40 org.apache.cxf.interceptor.LoggingOutInterceptor
---------------------------
Response-Code: 200
Content-Type: text/html

Note that a "/forms/oauthAuthorize.jsp" view handler will create an HTML view - this is a custom JSP handler and whatever HTML view is required can be created there, using the OAuthAuthorizationData bean for building the view. Most likely you will want to present a form asking the user to allow or deny the consumer accessing some of this user's resources. If OAuthAuthorizationData has a list of Permissions set then addig adding the information about the permissions is needed, same for a list of URIs.

Next the user makes a decision and selects a button allowing or denying the consumer accessing the resources. AuthorizationRequestService does not need to know how a user has been asked to make the decision, but it expects to receive a form-based submission containing the following 3 parameters, named "session_authenticity_token" and "oauth_token" with values matching those of OAuthAuthorizationData's "authenticityToken" and "oauthToken" properties, and "oAuthDecision" with either "allow" or "deny" values:

Code Block
xml
xml

Address: http://localhost:8080/services/social/authorize/decision
Http-Method: POST
Content-Type: application/x-www-form-urlencoded
Headers: {
Authorization=[Basic YmFycnlAc29jaWFsLmNvbToxMjM0],
Cookie=[JSESSIONID=eovucah9rwqp], 
Referer=[http://localhost:8080/services/social/authorize?oauth_token=6dfd5e52-236c-4939-8df8-a53212f7d2a2], 
User-Agent=[Mozilla/5.0 (X11; Linux x86_64; rv:2.0) Gecko/20100101 Firefox/4.0]}
--------------------------------------
09-Nov-2011 16:41:58 org.apache.cxf.jaxrs.utils.FormUtils logRequestParametersIfNeeded
INFO: session_authenticity_token=e52b5033-9bf5-4b34-9d3a-39a7d5b7e686&oauthDecision=allow
&oauth_token=6dfd5e52-236c-4939-8df8-a53212f7d2a2

AuthorizationRequestService will use a session_authenticity_token to validate that the session is valid and will process the user decision next.
If it is set to "allow" then it will ask OAuthDataProvider to generate an authorization key (verifier) and return this verifier alongside with the request token key and the state if any by redirecting the current user back to the callback URI provided during the request token request:

Code Block
xml
xml

Response-Code: 303
Headers: {
Location=[http://localhost:8080/services/reservations/reserve/complete?
oauth_token=6dfd5e52-236c-4939-8df8-a53212f7d2a2&oauth_verifier=00bd8fa7-4233-42a2-8957-0a0a22c684ba]
}

which leads to a browser redirecting the user:

Code Block
java
java

Address: http://localhost:8080/services/reservations/reserve/complete?
oauth_token=6dfd5e52-236c-4939-8df8-a53212f7d2a2&oauth_verifier=00bd8fa7-4233-42a2-8957-0a0a22c684ba
Http-Method: GET
Content-Type: 
Headers: {
Authorization=[Basic YmFycnlAc29jaWFsLmNvbToxMjM0], 
Cookie=[JSESSIONID=eovucah9rwqp],
Referer=[http://localhost:8080/services/social/authorize?oauth_token=6dfd5e52-236c-4939-8df8-a53212f7d2a2], 
User-Agent=[Mozilla/5.0 (X11; Linux x86_64; rv:2.0) Gecko/20100101 Firefox/4.0]}

...

The OAuth 1.0 mentions so called "oob" (out-of-band) callbacks. If the third-party client is not running as a web application or if it is known it can not receive the redirect response from AuthorizationRequestService for whatever reasons, then a callback URI can be set to "oob", when a request token is
requested:

Code Block
xml
xml

Address: http://localhost:8080/services/oauth/initiate
Encoding: ISO-8859-1
Http-Method: POST
Content-Type: */*
Headers: {
Accept=[application/x-www-form-urlencoded], 

Content-Length=[0],

Authorization=[OAuth oauth_callback="oob", 
                     oauth_nonce="e365fa02-772e-4e33-900d-00a766ccadf8", 
                     oauth_consumer_key="123456789", 
                     oauth_signature_method="HMAC-SHA1", 
                     oauth_timestamp="1320748683", 
                     oauth_version="1.0", 
                     oauth_signature="ztTQuqaJS7L6dNQwn%2Fqi1MdaqQQ%3D"] 
}

RequestTokenService will only accept the "oob" value if a client callbackURI property has been set to "oob" during the client application registration process. Specifically, RequestTokenService will expect that a Client bean will have its callbackURI property being set to "oob".

When a callback URI is set to "oob", it means that a user decision response needs to be presented directly to the current user - which will then make the request token and verifier info somehow available to the client application. In case of "oob", AuthorizationRequestService, instead of redirecting the user back to the callback URI as shown earlier on, will simply return an instance of OOBAuthorizationResponse. RequestDispatcherProvider will need to be used for redirecting this data to the view handler exactly how it is done when a user is asked to authorize the client application, with the view handler formatting the data and actually returning it to the user

...

The role of AccessTokenService is to exchange an authorized request token for a new access token which will be used by the consumer to access the end user's resources.
Here is an example request log:

Code Block
xml
xml

Address: http://localhost:8080/services/oauth/token
Http-Method: POST
Headers: {
Accept=[application/x-www-form-urlencoded], 
Authorization=[OAuth oauth_signature_method="HMAC-SHA1", 
                     oauth_consumer_key="123456789", 
                     oauth_token="6dfd5e52-236c-4939-8df8-a53212f7d2a2", 
                     oauth_verifier="00bd8fa7-4233-42a2-8957-0a0a22c684ba", 
                     oauth_timestamp="1320760259", 
                     oauth_nonce="16237669362301", 
                     oauth_version="1.0", 
                     oauth_signature="dU%2BhXPNFfFpX2sC74IOxzTjdVrY%3D"]
}

...

Next it asks the data provider to create a new AccessToken based on this RequestToken. The resulting access token key and secret pair is returned back to a consumer:

Code Block
xml
xml

Response-Code: 200
Content-Type: application/x-www-form-urlencoded
Headers: {Date=[Tue, 08 Nov 2011 13:50:59 GMT]}
Payload: oauth_token=abc15aca-2073-4bde-b1be-1a02dc7ccafe&oauth_token_secret=859dfe9e-ca4c-4b36-9e60-044434ab636c

The consumer will use this access token to access the current user's resources in order to complete the original user's request, for example, the request to access a user's calendar may look like this:

Code Block
xml
xml

Address: http://localhost:8080/services/user/calendar
Http-Method: GET
Headers: {
Accept=[application/XML], 
Authorization=[OAuth oauth_signature_method="HMAC-SHA1", 
                     oauth_consumer_key="123456789", 
                     oauth_token="abc15aca-2073-4bde-b1be-1a02dc7ccafe", 
                     oauth_version="1.0", 
                     oauth_signature="dU%2BhXPNFfFpX2sC74IOxzTjdVrY%3D"]
}

...

Using CXF OAuth service implementations will help a lot with setting up an OAuth server. As you can see from the above sections, these services rely on a custom OAuthDataProvider implementation.

The main task of OAuthDataProvider is to persist request and access tokens and generate authorization/verifier keys. The way it's done is really application-specific. Consider starting with a basic memory based implementation and then move on to keeping the data in some DB.

Note that OAuthDataProvider supports retrieving Client instances but it has no methods for creating or removing Clients. The reason for it is that the process of registering third-party consumers is very specific to a particular OAuth application, so CXF does not offer a registration support service and hence OAuthDataProvider has no Client create/update methods. You will likely need to do something like this:

Code Block
java
java

public class CustomOAuthProvider implements OAuthDataProvider {
   public Client registerClient(String applicationName, String applicationURI, ...) {}
   public void removeClient(String cliendId) {}
   // etc
   // OAuthDataProvider methods
}

...

When creating RequestToken or AccessToken tokens as well as authorization keys, OAuthDataProvider will need to create unique identifiers.
The way it's done is application specific and custom implementations may also use a utility MD5SequenceGenerator shipped with CXF, for example:

Code Block
java
java

public String setRequestTokenVerifier(RequestToken requestToken) throws OAuthServiceException {
    requestToken.setVerifier(generateSequence());
    return requestToken.getVerifier();
}

private String generateSequence() throws OAuthServiceException {
    try {
       return tokenGenerator.generate(UUID.randomUUID().toString().getBytes("UTF-8"));
    } catch (Exception e) {
       throw new OAuthServiceException("Unable to generate the key", e.getCause());
    }
}

...

Finally OAuthDataProvider may need to convert opaque scope values such as "readCalendar" into a list of OAuthPermissions. AuthorizationRequestService and OAuth security filters will depend on it (assuming scopes are used in the first place). In the former case AuthorizationRequestService will use this list to populate OAuthAuthorizationData - the reason this bean only sees Permissions is that the properties OAuthPermission keeps are of no interest to OAuthAuthorizationData handlers.

...

With CXF offering OAuth service implementations and a custom OAuthAuthorizationData provider in place, it is time to deploy the OAuth server.
Most likely, you'd want to deploy RequestTokenService and AccessTokenService as two root resources inside a single JAX-RS endpoint (or have one RequestTokenService and one AccessTokenService endpoint), for example:

Code Block
xml
xml

<!-- implements OAuthDataProvider -->
<bean id="oauthProvider" class="oauth.manager.OAuthManager"/>

<bean id="requestTokenService" class="org.apache.cxf.rs.security.oauth.services.RequestTokenService">
   <property name="dataProvider" ref="oauthProvider"/>
</bean>
     
<bean id="accessTokenService" class="org.apache.cxf.rs.security.oauth.services.AccessTokenService">
  <property name="dataProvider" ref="oauthProvider"/>
</bean>

<jaxrs:server id="oauthServer" address="/oauth">
   <jaxrs:serviceBeans>
      <ref bean="requestTokenService"/>
      <ref bean="accessTokenService"/>
  </jaxrs:serviceBeans>
</jaxrs:server>

...

AuthorizationRequestService is better to put where the main application endpoint is. It can be put alongside RequestTokenService and AccessTokenService - but the problem is that the end user is expected to authenticate itself with the resource server after it has been redirected by a third-party consumer to AuthorizationRequestService. That would make it more complex for the OAuth server endpoint to manage both OAuth (third-party consumer) and the regular user authentication - that can be done, see more on it below in the Design considerations section, but the simpler option is to simply get AuthorizationRequestService under the control of the security filter enforcing the end user authentication:

Code Block
java
java

<bean id="authorizationService" class="org.apache.cxf.rs.security.oauth.services.AuthorizationRequestService">
  <property name="dataProvider" ref="oauthProvider"/>
</bean>

<bean id="myApp" class="org.myapp.MyApp">
  <property name="dataProvider" ref="oauthProvider"/>
</bean>

<jaxrs:server id="oauthServer" address="/myapp">
   <jaxrs:serviceBeans>
      <ref bean="myApp"/>
      <ref bean="authorizationService"/>
  </jaxrs:serviceBeans>
</jaxrs:server>

...

Protecting resources with OAuth filters

OAuthRequestFilter request handler can be used to protect the resource server when processing the requests from the third-party consumers. Add it as a jaxrs:provider to the endpoint which deals with the consumers requesting the resources.

When checking a request like this:

Code Block
xml
xml

Address: http://localhost:8080/services/user/calendar
Http-Method: GET
Headers: {
Accept=[application/XML], 
Authorization=[OAuth oauth_signature_method="HMAC-SHA1", 
                     oauth_consumer_key="123456789", 
                     oauth_token="abc15aca-2073-4bde-b1be-1a02dc7ccafe", 
                     oauth_version="1.0", 
                     oauth_signature="dU%2BhXPNFfFpX2sC74IOxzTjdVrY%3D"]
}

...

1. It will validate the signature and will get Client and AccessToken from OAuthDataProvider.

2. It will check if AccessToken have a "uris" property set and if yes then it will validate the current request URI against it.

3. If AccessToken has a list of OAuthPermissions. For every permission it will:

...

4. Finally, it will create a CXF SecurityContext using this list of OAuthPermissions and the Client loginName property.

This loginName property is something that can be optionally associated with the new Client during the registration - if it is not set then the filter will use a Client "applicationName" property instead. The application code checking the user Principal will see the chosen value. Additionally every OAuthPermission may have a list of application-specific roles such as "consumer", etc, which will be added to SecurityContext and will be checked during SecurityContext.isUserInRole(roleName) calls.

...

This SecurityContext will not necessarily be important for some of OAuth applications. Most of the security checks will be done by OAuth filters and security filters protecting the main application path the end users themselves use. Only if you would like to share the same JAX-RS resource code and access URIs between end users and consumers then it can become handy. More on it below.

Note that OAuthServletFilter can be deployed instead. It will need the OAuthDataProvider full class name referenced as an "oauth.data.provider-class" servlet context parameter.

...

The injected MessageContext provides an access to OAuthContext which has been set by OAuth filters described in the previous section. OAuthContext will act as a container of the information which can be useful to the custom application code which do not need to deal with the OAuth internals which will likely change between OAuth 1.0 and OAuth 2.0. At the moment OAuthContext provides an access to UserSubject which is created by CXF AuthorizationService at the moment of the end user authorizing the third-party client and captures the end user's login name (and roles which will be available if CXF JAASLoginInterceptor is used to authenticate end users) and associates it with the current RequestToken. It will be a responsibility of custom OAuthDataProviders to make sure this UserSubject bean is copied across to a corresponding AccessToken. OAuthContext also references the list of the permissions which have been validated againt the current client request.

Additionally you may get OAuth filters to set up a SecurityContext which will use the information available in UserSubject, in other words, get the 3rd-party client impersonating the end user (which authorized this client in the first place) for the duration of the current request. Set a jaxrs contextual "org.apache.cxf.rs.security.oauth.use_user_subject" property to 'true'
for this to happen.

Client-side support

When developing a third party application which needs to participate in OAuth flows one has to write the code that will redirect users to OAuth AuthorizationRequestService, interact with RequestTokenService and AccessTokenService in order to get request and access tokens as well as correctly build Authorization OAuth headers when accessing the end users' resources. JAX-RS makes it straightforward to support the redirection, while OAuthClientUtils class makes it possible to encapsulate most of the complexity away from the client application code.

...

For example, the following custom code can be used by the third-party application:

Code Block
java
java

public class OAuthClientManager {
	
	private WebClient accessTokenService;
        private WebClient requestTokenService;
        private String authorizationServiceURI;
    
        // inject properties...
	
	public URI getAuthorizationServiceURI(String token) {
	    return OAuthClientUtils.getAuthorizationURI(authorizationServiceURI, token);
	}
	
	public Token getRequestToken(URI callback) {
	    try {
	        return OAuthClientUtils.getRequestToken(requestTokenService, consumer, callback, null);
	    } catch (OAuthServiceException ex) {
               return null;
            }    
	}
	
	public Token getAccessToken(Token requestToken, String verifier) {
	    try {
	        return OAuthClientUtils.getAccessToken(accessTokenService, consumer, requestToken, verifier);
	    } catch (OAuthServiceException ex) {
	        return null;
	    }
	}
	
	public String createAuthorizationHeader(Token token, String method, String requestURI) {
            return OAuthClientUtils.createAuthorizationHeader(consumer, token, method, requestURI);
	}
}

The reason such a simple wrapper can be introduced is to minimize the exposure to OAuth of the main application code to the bare minimum, this is why
in this example OAuthServiceExceptions are caught, presumably logged and null values are returned which will indicate to the main code that the request failed. Obviously, OAuthClientUtils can be used directly as well.

...

Also note that AuthorizationRequestService can return XML or JSON OAuthAuthorizationData representations. That makes it easy for a client code to get OAuthAuthorizationData and offer a pop-up window or get the input from the command-line. Authorizing the third-party application might even be automated in this case - which can lead to a complete 3-leg OAuth flow implemented without a human user being involved.

...

CXF OAuth 1.0 services will report only HTTP status code in case of various OAuth-related errors to minimize the information about the actual cause of the failure and will log the details locally. If providing the extra error information can help with debugging 3rd-party applications or if such application can indeed recover from the failures based on such details, then setting a contextual "report.failure.details" property to "true" will get the error messages available in the response body. Some OAuth1.0 implementers have chosen to return a custom "oauth_problem" HTTP header instead - this option can be supported by additionally setting a contextual "report.failure.details.as.header" property to "true", for example:

Code Block
xml
xml

<jaxrs:server id="oauthServer" address="/initiate">
        <jaxrs:serviceBeans>
            <bean class="org.apache.cxf.rs.security.oauth.services.RequestTokenService"/>
        </jaxrs:serviceBeans>
        <jaxrs:properties>
           <entry key="report.failure.details" value="true"/>
           <entry key="report.failure.details.as.header" value="true"/>
        </jaxrs:properties>
</jaxrs:server>

...

The first problem which needs to be addressed is how to distinguish end users from third-party consumers and get both parties authenticated as required.
Perhaps the simplest option is to extend a CXF OAuth filter (JAX-RS or servlet one), check Authorization header, if it is OAuth then delegate to the superclass, alternatively - proceed with authenticating the end users:

Code Block
java
java

public class SecurityFilter extends org.apache.cxf.rs.security.oauth.filters.OAuthRequestFilter {
   @Context
   private HttpHeaders headers;

   public Response handleRequest(ClassResourceInfo cri, Message message) {
       String header = headers.getRequestHeaders().getFirst("Authorization");
       if (header.startsWith("OAuth ")) {
           return super.handleRequest(cri, message);
       } else {
           // authenticate the end user
       }
   }

} 

The next issue is how to enforce that the end users can only access the resources they've been authorized to access.
For example, consider the following JAX-RS resource class:

Code Block
java
java

@Path("calendar")
public class CalendarResource {

   @GET
   @Path("{id}")
   public Calendar getPublicCalendar(@PathParam("id") long id) {
       // return the calendar for a user identified by 'id'
   }

   @GET
   @Path("{id}/private")
   public Calendar getPrivateCalendar(@PathParam("id") long id) {
       // return the calendar for a user identified by 'id'
   }

   @PUT
   @Path("{id}")
   public void updateCalendar(@PathParam("id") long id, Calendar c) {
       // update the calendar for a user identified by 'id'
   }
}

...

As noted above, Client, AccessToken (in its Token superclass) and OAuthPermission all have an optional URIs property. Thus one way to solve the problem with the private calendar is to add, say, a uri "/calendar/{id}" or "/calendar/1" (etc) property to OAuthPermission (representing a scope like "readCalendar") and the OAuth filter will make sure no subresources beyond "/calendar/{id}" can be accessed. Note, adding a "*" at the end of a given URI property, for example, "/a*" will let the consumer to access "/a", "/a/b", etc.

...