Waiting for the Seam3 security module, I wrote a simple security interceptor (inspired by the Seam2 security annotation). You can use it like this:
1 2 | @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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /** * @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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | @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:
1 2 3 | <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