代码之家  ›  专栏  ›  技术社区  ›  Wolkenarchitekt

将枚举类型用作@rolesallowed批注的值参数

  •  51
  • Wolkenarchitekt  · 技术社区  · 14 年前

    我正在开发一个Java企业应用程序,目前正在做Java EE安全性的东西来限制特定用户对特定用户的访问。我配置了应用服务器和所有东西,现在我使用rolesallowed注释来保护这些方法:

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

    当我使用这样的注释时,它可以很好地工作:

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

    但这不是我想要的,因为我必须在这里使用一个字符串,重构变得很困难,并且可能会出现打字错误。因此,我不使用字符串,而是使用枚举值作为此批注的参数。枚举如下所示:

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

    所以我尝试使用枚举作为这样的参数:

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

    但是我得到了下面的编译器错误,尽管enum.name只返回一个字符串(它总是常量,不是吗?).

    批注属性rolesallowed.value的值必须是常量表达式`

    接下来我尝试向枚举中添加一个附加的最终字符串:

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

    但这也不能作为参数工作,从而导致相同的编译器错误:

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

    我怎样才能实现我想要的行为?我甚至实现了自己的拦截器来使用自己的注释,这很漂亮,但是对于这样的小问题,代码行太多了。

    免责声明

    这个问题最初是 Scala 问题。我发现斯卡拉不是问题的根源,所以我首先尝试用Java做这件事。

    4 回复  |  直到 5 年前
        1
  •  30
  •   Luke Woodward    14 年前

    我认为你使用枚举的方法不会奏效。我发现如果我更改了 STUDENT_ROLE 将最后一个示例中的字段转换为常量字符串,而不是表达式:

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

    然而,这意味着枚举值不会在任何地方使用,因为您将在注释中使用字符串常量。

    在我看来,如果你 RoleType 类只包含一组静态最终字符串常量。


    为了了解您的代码没有编译的原因,我研究了 Java Language Specification (JLS)。JLS annotations 声明对于参数类型为的批注 T 价值 V ,

    如果 T 是原始类型或 String , V 是常量表达式。

    constant expression 其中包括:

    窗体的限定名 类型名 . 标识符 指常量变量

    和A constant variable 定义为

    基本类型或类型的变量 ,它是最终的,并用编译时常量表达式初始化

        2
  •  16
  •   Samiron    7 年前

    这个怎么样?

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

    在注释中,你可以像

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

    一个小问题是,对于任何修改,我们需要在两个地方进行更改。但由于它们在同一个文件中,所以不太可能丢失。作为回报,我们得到了不使用原始字符串和避免复杂机制的好处。

    或者这听起来很愚蠢?:)

        3
  •  9
  •   anomolos    13 年前

    这里有一个使用附加接口和元注释的解决方案。我已经包含了一个实用程序类来帮助进行反射,以从一组注释中获取角色类型,并对其进行了一些测试:

    /**
     * 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());
        }
    }
    
        4
  •  0
  •   John B    5 年前

    我通过添加注释解决了这个问题 @RoleTypesAllowed 以及添加元数据源。如果只有一个枚举类型需要支持,那么这个方法非常有效。有关多个枚举类型,请参见anomolos的文章。

    在下面 RoleType 是我的角色枚举。

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

    然后我在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;
      }
    }