Versions Compared

Key

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

...

3. Validating access tokens

 


CXF offers several JAX-RS service implementations that can be used to create the OAuth2 servers fast: AuthorizationCodeGrantService and ImplicitGrantService for managing the redirection-based flows, as well as AccessTokenService for exchanging the grants for new tokens.

...

CXF ships several default provider implementations, see the section on wirting the providers below.

 


Authorization Service

The main responsibility of OAuth2 Authorization Service is to present an end user with a form asking the user to allow or deny the client accessing some of the user resources. CXF offers  AuthorizationCodeGrantService and ImplicitGrantService for accepting the redirection requests, challenging the end users with the authorization forms, handling the end user decisions and returning the results back to the clients.

...

Note that ServerAuthorizationGrant and Client can also be encrypted.

 


The simplest strategy is to encrypt and decrypt the tokens with the symmetric/secret keys. Every new token can be encrypted with a unique secret key or all of them can be encrypted with a single secret key. The utilities provide few methods for creating secret keys with the default and advanced properties, in addition there are many examples around on how to create the keys with the specific properties.

...

Here is a typical code demonstrating how the encryption/decryption works: 


Code Block
SecretKey key = CryptoUtils.getSecretKey();

// create a new token, encrypt its state and return

ServerAccessToken token = new BearerAccessToken(client, 3600L);

String encryptedToken = ModelEncryptionSupport.encryptAccessToken(token, key);

token.setTokenKey(encryptedToken);

return token;

// decrypt a token given a token key

ModelEncryptionSupport.decryptAccessToken(this, encryptedToken, key);
 

JWT Tokens

Starting from CXF 3.1.8 some of CXF OAuthDataProvider implementations (JCache and JPA2 based, as well as EhCache 2.x prior to CXF 3.3.0) support Access Token representations in JWT. This means that ServerAccessTokens created by data providers are converted to a sequence of JSON JWT claims and then JWS signed and/or JWE encrypted.

...

Code Block
<bean id="jwtTokenValidator" class="org.apache.cxf.rs.security.oauth2.filters.JwtAccessTokenValidator"/>
<bean id="oAuthFilterLocalValidation" class="org.apache.cxf.rs.security.oauth2.filters.OAuthRequestFilter">
    <property name="tokenValidator" ref="jwtTokenValidator"/>
</bean>
   
<jaxrs:server 
    depends-on="tls-config" 
    address="https://localhost:${testutil.ports.jaxrs-oauth2-filtersJwt}/securedLocalValidation">
    <jaxrs:serviceBeans>
        <ref bean="serviceBean"/>
    </jaxrs:serviceBeans>
    <jaxrs:providers>
        <ref bean="oAuthFilterLocalValidation"/>
    </jaxrs:providers>
    <jaxrs:properties>
         <entry key="rs.security.signature.in.properties" value="org/apache/cxf/systest/jaxrs/security/alice.rs.properties"/>
    </jaxrs:properties>
</jaxrs:server>

...


When to use JWT ? The pros are: might be easier to align with some newer OAuth2 related specifications, might be possible to avoid a remote validation call, possible OAuth2 server storage optimization. Cons: the extra cost of validating (or decrypting), access token value reported to and used by clients becomes larger. If JWS only is used - care should be taken to avoid putting some sensitive JWT claims given that JWS payload can be introspected.

See JAX-RS JOSE wiki page for more information on how to sign and encrypt JSON Web Tokens. Specifically, if you need to create JWT values in your custom providers, then have a look at  this section: one can delegate to or extend JoseJwtConsumer or JoseJwtProducer. Addtionally org.apache.cxf.rs.security.oauth2.provider.OAuthJoseJwtConsumer (and OAuthJoseJwtProducer) can help in cases where OAuth2 Client secret is used as a key for HMAC based signatures or encryptions, while OAuthServerJoseJwtConsumer (and OAuthServerJoseJwtProducer) can also use OAuth2 Client certificates. 


Custom tokens

If needed, users can use their own custom token types, with the only restriction that the custom token type implementations have to extend org.apache.cxf.rs.security.oauth2.common.ServerAccessToken.

...

Please also see JAXRS OAuth2 Assertions section for more information. 


Custom Grants

If you need to customize the way the well-known grant requests are handled then consider extending one of the grant handlers listed in the previous sub-sections.

...

Code Block
java
java
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.cxf.rs.security.oauth2.client.ClientTokenContext;

@Path("reserve")
public class ReservationService {

private WebClient socialService;
private WebClient restaurantService;

@GET
@Path("table")
@Produces("text/html")
public Response reserve(@Context ClientTokenContext context) {
    // Check if token is available
    if (context.getToken() == null) {
       return redirectToFailureHandler(NO_CODE_GRANT);
    }
    // Prepare Authorization header
    socialService.authorization(context.getToken());
    
    // Get the state that was captured by the filter before redirecting the user to OAuth2 server
    ReservationRequest request = context.getState(ReservationRequest.class);
    
    // Work with the service on behalf of a user    
    Calendar c = null;
    try {
      c = socialService.get(Calendar.class);
    } catch (RuntimeException ex) {
      return redirectToFailureHandler(CALENDAR_ACCESS_PROBLEM);
    }
        
    CalendarEntry entry = c.getEntry(request.getHour());
    if (entry.getEventDescription() == null || entry.getEventDescription().trim().isEmpty()) { 
        String address = restaurantService.post(new Form().param("name", request.getReserveName()) 
                                         .param("phone", request.getContactPhone()) 
                                         .param("hour", Integer.toString(request.getHour())),
                                          String.class);
        if (address == null) {
            return redirectToFailureHandler(NO_RESERVATION);
        }
            
        // update the user's calendar
        Response response = socialService.form(new Form().param("hour", Integer.toString(request.getHour()))
                                         .param("description", "Table reserved at " + address));
        boolean calendarUpdated = response.getStatus() == 200 || response.getStatus() == 204;
            
        return Response.ok(new ReservationConfirmation(address, request.getHour(), calendarUpdated))
                           .build();
        } else {
            return redirectToFailureHandler(CALENDAR_BUSY);
        }
    }
}

...


The filter is configured as follows:

 


Code Block
languagexml
<beans>
    
<jaxrs:server id="reservationsServer" address="/reservations">
    <jaxrs:serviceBeans>
       <ref bean="restaurantReserveService"/>
    </jaxrs:serviceBeans>
    <jaxrs:providers>
       <!-- other providers -->

       <bean class="oauth2.thirdparty.CustomClientTokenContextProvider"/>
       <bean class="org.apache.cxf.rs.security.oauth2.client.ClientCodeRequestFilter">
           <property name="authorizationServiceUri" value="http://localhost:8080/services/authorize"/>
           <property name="accessTokenServiceClient" ref="atServiceClient"/>
           <property name="startUri" value="reserve/table"/>
           <property name="clientCodeStateManager" ref="codeManager"/>
           <property name="consumer" ref="consumer"/>
       </bean>
    </jaxrs:providers>
</jaxrs:server>
     
<bean id="codeManager" class="oauth2.thirdparty.ClientCodeStateManagerImpl"/>

<!-- the consumer pre-registered with OAuth2 servers -->
<bean id="consumer" class="org.apache.cxf.rs.security.oauth2.client.Consumer">
    <property name="key" value="123456789"/>
    <property name="secret" value="987654321"/>
</bean>

<!-- WebClient for communicating with OAuth2 AccessTokenService -->
<jaxrs-client:client id="atServiceClient" serviceClass="org.apache.cxf.jaxrs.client.WebClient"
    address="http://localhost:8080/services/oauth2Token/token">
    <jaxrs-client:headers>
       <entry key="Accept" value="application/json"/>
    </jaxrs-client:headers>
</jaxrs-client:client>

</beans>

...

CXF also ships a default ClientTokenContext implementation, a simple org.apache.cxf.rs.security.oauth2.client.ClientTokenContextImpl bean. Finally CXF ships org.apache.cxf.rs.security.oauth2.client.ClientTokenContextProvider to ensure ClientTokenContext can be available as JAX-RS context. Note though that the above configuration registers a custom ClientTokenContext provider ('oauth2.thirdparty.CustomClientTokenContextProvider') instead - this is optional and is only needed if it is preferred for the application code to access the state in a type safe way (example, via ReservationRequest type as shown above), such a provider can be implemented like this:

 


Code Block
java
java
import javax.ws.rs.core.MultivaluedMap;
import org.apache.cxf.jaxrs.ext.ContextProvider;
import org.apache.cxf.message.Message;
import org.apache.cxf.rs.security.oauth2.client.ClientTokenContext;
import org.apache.cxf.rs.security.oauth2.client.ClientTokenContextProvider;
import org.apache.cxf.rs.security.oauth2.common.ClientAccessToken;

public class CustomClientTokenContextProvider extends ClientTokenContextProvider {
    @Override
    public ClientTokenContext createContext(Message m) {
        return new WrapClientTokenContext(super.createContext(m));
    }
    private static class WrapClientTokenContext implements ClientTokenContext {
        private ClientTokenContext ctx;
        public WrapClientTokenContext(ClientTokenContext ctx) { 
            this.ctx = ctx;
        }
        @Override
        public MultivaluedMap<String, String> getState() {
            return ctx.getState();
        }
        @Override
        public <T> T getState(Class<T> cls) {
            if (ReservationRequest.class == cls) {
                MultivaluedMap<String, String> state = getState();
                return (T)new ReservationRequest(state.getFirst("name"),
                                              state.getFirst("phone"),
                                              Integer.parseInt(state.getFirst("hour")));
            }
            return ctx.getState(cls);
        }
        @Override
        public ClientAccessToken getToken() {
            return ctx.getToken();
        }
    }
}

 


ClientCodeRequestFilter can also be configured with org.apache.cxf.rs.security.oauth2.client.ClientTokenContextManager ('clientTokenContextManager' property) to support a case where the same user is returning with the same request and the access token granted previously has not expired yet. At the moment CXF ships only MemoryClientTokenContextManager with the JOSE-aware provider to follow. If the manager is registered and the context (and the access token) for the current user is found then the request goes ahead without the redirection. In this case the filter can pro-actively refresh the token if it has already expired or close to being expired ('expiryThreshold' property, example, if it is set to 5 secs and the filter sees that the token will expire in less than 5 sec then it will try to refresh).

...

Here is a configuration example: 


Code Block
languagexml
<beans>
<bean id="consumer" class="org.apache.cxf.rs.security.oauth2.client.Consumer">
   <property name="clientId" value="1"/>
   <property name="clientSecret" value="2"/>
</bean>
<bean id="bearerAuthSupplier" class="org.apache.cxf.rs.security.oauth2.client.BearerAuthSupplier">
   <!-- access token -->
   <property name="accessToken" value="12345678"/>
   <!-- refresh token and the info needed to use it to refersh the expired access token proactively or in response to 401 --> 
   <property name="refreshToken" value="87654321"/>
   <!-- 
       Set this property for the authenticator to check the access token expiry date and refresh the token proactively.
       Note that this property can also become effective after the first token refresh as it is not known in advance when 
       the injected access token will expire
   -->
   <property name="refreshEarly" value="true"/>
   <!-- client OAuth2 id and secret - needed to use a refresh token grant --> 
   <property name="consumer" ref="consumer"/>
   <!-- address of OAuth2 token service that supports a refresh token grant
   <property name="accessTokenServiceUri" value="https://server/oauth2/accessToken"/>
</bean>
<conduit name="*.http-conduit" xmlns="http://cxf.apache.org/transports/http/configuration">
  <authSupplier>
     <ref bean="bearerAuthSupplier"/>
  </authSupplier>
</conduit>
</beans>

 


At the moment only BearerAuthSupplier supporting bearer access tokens is available; authenticators supporting other well known token types will be provided in the future.

org.apache.cxf.rs.security.oauth2.client.CodeAuthSupplier is also shipped. It is similar to BearerAuthSupplier except that it is initailized with an authorization code grant obtained out of band, uses this grant to get the tokens and then delegates to BearerAuthSupplier. Example: 


Code Block
languagexml
<beans>
<bean id="consumer" class="org.apache.cxf.rs.security.oauth2.client.Consumer">
   <property name="clientId" value="1"/>
   <property name="clientSecret" value="2"/>
</bean>
<bean id="codeAuthSupplier" class="org.apache.cxf.rs.security.oauth2.client.CodeAuthSupplier">
   <!-- authorization code -->
   <property name="code" value="12345678"/>

   <!-- Set this property for the authenticator to check the access token expiry date and refresh the token proactively -->
   <property name="refreshEarly" value="true"/>
   <!-- client OAuth2 id and secret - needed to use a refresh token grant --> 
   <property name="consumer" ref="consumer"/>
   <!-- address of OAuth2 token service that supports a refresh token grant
   <property name="accessTokenServiceUri" value="https://server/oauth2/accessToken"/>
</bean>
<conduit name="*.http-conduit" xmlns="http://cxf.apache.org/transports/http/configuration">
  <authSupplier>
     <ref bean="codeAuthSupplier"/>
  </authSupplier>
</conduit>
</beans>

...


Additionally, a basic JAX-RS 2.0 ClientRequestFilter, org.apache.cxf.rs.security.oauth2.client.BearerClientFilter, is shipped and is initialized with an "accessToken" property only. It might be used in cases where only a non-expiring access token is available.

...

OAuth2 and OIDC

CXF shipis ships OIDC RP and IDP service code which depends on its OAuth2 and JOSE implementations. See this page for more information.

...