Use Enum type as parameter value for @ RolesAllowed-Annotation

I am developing an enterprise Java application currently engaged in Java EE security to restrict access to certain features to specific users. I set up the application server and all that, and now I use the RolesAllowed annotation to protect the methods:

@Documented @Retention (RUNTIME) @Target({TYPE, METHOD}) public @interface RolesAllowed { String[] value(); } 

When I use such annotation, it works fine:

 @RolesAllowed("STUDENT") public void update(User p) { ... } 

But this is not what I want, since I have to use String here, refactoring becomes difficult, and typos can occur. So instead of using String, I would like to use the Enum value as a parameter for this annotation. Enum is as follows:

 public enum RoleType { STUDENT("STUDENT"), TEACHER("TEACHER"), DEANERY("DEANERY"); private final String label; private RoleType(String label) { this.label = label; } public String toString() { return this.label; } } 

So, I tried using Enum as a parameter as follows:

 @RolesAllowed(RoleType.DEANERY.name()) public void update(User p) { ... } 

But then I get the following compiler error, although Enum.name just returns a string (which is always constant, right?).

The value of the RolesAllowed.value annotation attribute must be a constant expression `

The next thing I tried is to add an extra final line to my Enum:

 public enum RoleType { ... public static final String STUDENT_ROLE = STUDENT.toString(); ... } 

But this also does not work as a parameter, leading to the same compiler error:

 // The value for annotation attribute RolesAllowed.value must be a constant expression @RolesAllowed(RoleType.STUDENT_ROLE) 

How can I achieve the desired behavior? I even implemented my own interceptor to use my own annotations, which is nice, but too many lines of code for such a small problem.

RENOUNCEMENT

This question was originally a Scala question. I found out that Scala is not the source of the problem, so I will try to do this in Java first.

+50
java enums java-ee annotations java-ee-6
Jul 17 '10 at 13:29
source share
4 answers

I don't think your approach to using enumerations will work. I found that the compiler error disappeared if I changed the STUDENT_ROLE field in your last example to a constant string, unlike the expression:

 public enum RoleType { ... public static final String STUDENT_ROLE = "STUDENT"; ... } 

However, this means that enum values ​​will not be used anywhere, because instead you will use string constants in annotations.

It seems to me that you would be better off if your RoleType class contains nothing more than a bunch of static final string constants.




To find out why your code did not compile, I looked at the Java Language Specification (JLS). JLS for annotations states that for annotations with a parameter of type T and a value of V,

if T is a primitive type or String , V is a constant expression.

A constant expression includes, among other things,

Qualified form names of TypeName. An identifier that refers to constant variables

and a constant variable is defined as

a variable, of primitive type or String type, which is final and is initialized by the expression of a compile-time constant

+30
Jul 17 '10 at 19:01
source share

How about this?

 public enum RoleType { STUDENT(Names.STUDENT), TEACHER(Names.TEACHER), DEANERY(Names.DEANERY); public class Names{ public static final String STUDENT = "Student"; public static final String TEACHER = "Teacher"; public static final String DEANERY = "Deanery"; } private final String label; private RoleType(String label) { this.label = label; } public String toString() { return this.label; } } 

And in the annotation you can use it as

 @RolesAllowed(RoleType.Names.DEANERY) public void update(User p) { ... } 

One small problem is that for any modification we must change in two places. But since they are in the same file, it is unlikely to be missed. In turn, we benefit from the use of raw strings and the prevention of a complex mechanism.

Or does that sound completely stupid? :)

+16
Aug 21 '17 at
source share

It uses a solution using an additional interface and meta annotations. I included a utility class to help make reflection, to get role types from a set of annotations and check a little for it:

 /** * empty interface which must be implemented by enums participating in * annotations of "type" @RolesAllowed. */ public interface RoleType { public String toString(); } /** meta annotation to be applied to annotations that have enum values implementing RoleType. * the value() method should return an array of objects assignable to RoleType*. */ @Retention(RetentionPolicy.RUNTIME) @Target({ANNOTATION_TYPE}) public @interface RolesAllowed { /* deliberately empty */ } @RolesAllowed @Retention(RetentionPolicy.RUNTIME) @Target({TYPE, METHOD}) public @interface AcademicRolesAllowed { public AcademicRoleType[] value(); } public enum AcademicRoleType implements RoleType { STUDENT, TEACHER, DEANERY; @Override public String toString() { return name(); } } public class RolesAllowedUtil { /** get the array of allowed RoleTypes for a given class **/ public static List<RoleType> getRoleTypesAllowedFromAnnotations( Annotation[] annotations) { List<RoleType> roleTypesAllowed = new ArrayList<RoleType>(); for (Annotation annotation : annotations) { if (annotation.annotationType().isAnnotationPresent( RolesAllowed.class)) { RoleType[] roleTypes = getRoleTypesFromAnnotation(annotation); if (roleTypes != null) for (RoleType roleType : roleTypes) roleTypesAllowed.add(roleType); } } return roleTypesAllowed; } public static RoleType[] getRoleTypesFromAnnotation(Annotation annotation) { Method[] methods = annotation.annotationType().getMethods(); for (Method method : methods) { String name = method.getName(); Class<?> returnType = method.getReturnType(); Class<?> componentType = returnType.getComponentType(); if (name.equals("value") && returnType.isArray() && RoleType.class.isAssignableFrom(componentType)) { RoleType[] features; try { features = (RoleType[]) (method.invoke(annotation, new Object[] {})); } catch (Exception e) { throw new RuntimeException( "Error executing value() method in " + annotation.getClass().getCanonicalName(), e); } return features; } } throw new RuntimeException( "No value() method returning a RoleType[] type " + "was found in annotation " + annotation.getClass().getCanonicalName()); } } public class RoleTypeTest { @AcademicRolesAllowed({DEANERY}) public class DeaneryDemo { } @Test public void testDeanery() { List<RoleType> roleTypes = RolesAllowedUtil.getRoleTypesAllowedFromAnnotations(DeaneryDemo.class.getAnnotations()); assertEquals(1, roleTypes.size()); } } 
+9
Oct 06 '11 at 18:29
source share

I solved this problem by adding the @RoleTypesAllowed annotation and adding the metadata source. This works very well if there is only one type of enum that needs to be supported. For several types of enum, see Post anomolos.

In the RoleType below RoleType my role is enum.

 @Documented @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RoleTypesAllowed { RoleType[] value(); } 

Then I added the following metadata source in Spring ...

 @Slf4j public class CemsRolesAllowedMethodSecurityMetadataSource extends AbstractFallbackMethodSecurityMetadataSource { protected Collection<ConfigAttribute> findAttributes(Class<?> clazz) { return this.processAnnotations(clazz.getAnnotations()); } protected Collection<ConfigAttribute> findAttributes(Method method, Class<?> targetClass) { return this.processAnnotations(AnnotationUtils.getAnnotations(method)); } public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } private List<ConfigAttribute> processAnnotations(Annotation[] annotations) { if (annotations != null && annotations.length != 0) { List<ConfigAttribute> attributes = new ArrayList(); for (Annotation a : annotations) { if (a instanceof RoleTypesAllowed) { RoleTypesAllowed ra = (RoleTypesAllowed) a; RoleType[] alloweds = ra.value(); for (RoleType allowed : alloweds) { String defaultedAllowed = new RoleTypeGrantedAuthority(allowed).getAuthority(); log.trace("Added role attribute: {}", defaultedAllowed); attributes.add(new SecurityConfig(defaultedAllowed)); } return attributes; } } } return null; } } 
0
Jan 21 '19 at 12:26
source share



All Articles