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; }