Waiting for the Seam3 security module, I wrote a simple security interceptor (inspired by the Seam2 security annotation). You can use it like this:
@Secure("#{loggedInUser.name == arg0.name}")
public List<Message> listMessages(User owner) { ... }
Here, loggedInUser
is a @Named
Weld (CDI) bean, and arg0
is the first argument of the method. This is a slight modification to the Seam2 security annotation, where it wasn’t possible to reference arguments. If the EL expression doesn’t evaluate to true
, an exception is thrown.
The implementation is pretty straightforward, as adding an interceptor in Weld is really simple. First, we need the annotation:
/**
* @author Adam Warski (adam at warski dot org)
*/
@Retention(RUNTIME)
@Target({TYPE, METHOD})
@InterceptorBinding
public @interface Secure {
/**
* @return The EL expression that should be evaluated. If it evaluates to
* {@code true}, access will be granted. The EL expression may reference
* any objects that are in any context, as well as the arguments of the method,
* under the names {@code arg0, arg1, arg2, ...}.
*/
@Nonbinding
String value();
}
The key components here is the @InterceptorBinding
meta-annotation, and specifying the value of to be @Nonbinding
. If the value element was binding, then we would need to define an interceptor for each possible String value.
Next, the interceptor itself:
@Interceptor
@Secure("")
public class SecurityInterceptor {
private String getArgName(int index) { return "arg" + index; }
@AroundInvoke
public Object invoke(InvocationContext ctx) throws Exception {
FacesContext facesCtx = FacesContext.getCurrentInstance();
ELContext elCtx = facesCtx.getELContext();
Secure secure = getSecureAnnotation(ctx.getMethod());
String expression = secure.value();
// Populating the request map so that parameters are available (arg0, ...)
Map<String, Object> requestMap = facesCtx.getExternalContext()
.getRequestMap();
for (int i = 0; i < ctx.getParameters().length; i++) {
Object parameter = ctx.getParameters()[i];
requestMap.put(getArgName(i), parameter);
}
Boolean expressionValue = (Boolean) facesCtx.getApplication()
.getExpressionFactory()
.createValueExpression(elCtx, expression, Boolean.class)
.getValue(elCtx);
// Removing the parameters (arg0, arg1, ...)
for (int i = 0; i < ctx.getParameters().length; i++) {
requestMap.remove(getArgName(i));
}
if (expressionValue == null || !expressionValue) {
throw new SecurityException();
}
return ctx.proceed();
}
private Secure getSecureAnnotation(Method m) {
for (Annotation a: m.getAnnotations()) {
if (a instanceof Secure) { return (Secure) a; }
}
for (Annotation a: m.getDeclaringClass().getAnnotations()) {
if (a instanceof Secure) { return (Secure) a; }
}
throw new RuntimeException("@Secure not found on method " + m.getName() +
" or its class " + m.getClass().getName());
}
}
And finally, we need an entry in beans.xml
, enabling the interceptor:
<interceptors>
<class>util.security.SecurityInterceptor</class>
</interceptors>
One shortcoming is that it’s not currently possible to place one @Secure
annotation on the class, and another on the methods (see this thread on the forum). The idea is that then the class-level annotation expresses general security constraints, which can be later refined on the method level.
Another missing feature, which could be easily added, is a message parameter to the annotation, which would be included in the exception in case the check fails.
Adam
comments powered by Disqus