In a recent project, we had the challenge of implementing SSL on certain APIs and restricting access to certain APIs by the state of the identity resource (RESTful description of what a certain consumer has done before like choose a country etc.).  Using custom annotations with pre-processors turned out to be a manageable and elegant solution.

 

The "SSL is required" annotation:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SSL {


}

 

The "specific identity state is required" annotation:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IdentityState {

          boolean anonymous() default false;

          boolean guest() default false;

          boolean registered() default false;
}

 

The SSL pre-processor:

 

SecurityContext context = ResteasyProviderFactory.getContextData(SecurityContext.class);
  if (!context.isSecure()) {
    if (request.getSession() != null) {
      // session would only be created if an identity state has been established and a token issued
      // Kill session as tokens may have been exposed
        request.getSession().invalidate();
        logger.info("SSL was required, but not found, killing session . . . ");
    }
    throw new UnauthorizedException("This API requires SSL");
  }

 

The identity state pre-processor:

 

if (remoteState == null ) { 
  throw new UnauthorizedException(
    "This API requires you to have called another API before"); 
}


if (!remoteState.getSite().equals(country)) {
  throw new UnauthorizedException(
    "The country ID must be the same as the one you used before - for example, 'US' or 'CA' "); 
}

 

Security note

Unexpected results may be produced by annotations at the method and the class level.  Use something like this in your accept() method to harmonize the two:

 

SSL ssl = (SSL) declaring.getAnnotation(SSL.class);
SSL methodssl = (SSL) method.getAnnotation(SSL.class);
if (methodssl != null) {
  ssl = methodssl;
}