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