使用 Enum 类型作为 @RolesAllowed-Annotation 的值参数

Posted

技术标签:

【中文标题】使用 Enum 类型作为 @RolesAllowed-Annotation 的值参数【英文标题】:Use Enum type as a value parameter for @RolesAllowed-Annotation 【发布时间】:2011-03-17 08:17:49 【问题描述】:

我正在开发一个 Java 企业应用程序,目前正在做 Java EE 安全工作,以限制特定用户对特定功能的访问。我配置了应用程序服务器和所有内容,现在我使用 RolesAllowed-annotation 来保护方法:

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

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

@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 问题。我发现 Scala 不是问题的根源,所以我首先尝试在 Java 中执行此操作。

【问题讨论】:

对不起,这与解决您的问题有点无关,但我想我会提到,如果您只想让它们与枚举构造函数相同,则可以省去枚举构造函数的 String 参数你给他们起名字;您可以通过在枚举上调用 .name() 来访问相同的值。我相信 toString() 方法无论如何都委托给 name()。 How to supply Enum value to an annotation from a Constant in Java的可能重复 【参考方案1】:

我认为您使用枚举的方法行不通。我发现如果我将最后一个示例中的 STUDENT_ROLE 字段更改为常量字符串,而不是表达式,编译器错误就会消失:

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

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

在我看来,如果你的 RoleType 类只包含一堆静态的 final String 常量,你会更好。


要了解您的代码未编译的原因,我查看了Java Language Specification (JLS)。 annotations 的 JLS 声明对于具有类型参数 T 和值 V 的注释,

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

constant expression 包括,除其他外,

TypeName 形式的限定名称。 标识符表示常量变量

constant variable 定义为

原始类型或String 类型的变量,它是最终的并使用编译时常量表达式进行初始化

【讨论】:

感谢您的努力!有趣的事实。尤其是最后一个。一直以为决赛已经是常态了。好的,这就是它无法工作的原因。在我看来,这已经是答案了。尽管我对此并不满意;)(顺便说一句,我确实需要枚举,而不仅仅是注释) 我确实遇到了an example,它声明了public interface Roles String REGISTERED = "registered"; ,然后使用了@RolesAllowed(Roles.REGISTERED)。当然,一个使用enum的例子并不意味着enum会产生问题,但是很好;-)【参考方案2】:

这个怎么样?

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)  ... 

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

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

【讨论】:

谢谢,我真的很喜欢这个,我会用它。我最初提出这个问题是因为我正在寻找一种更简洁的方法来指定杰克逊的 @JsonSubTypes.Type 注释中的“名称”属性,这样定义这些逻辑名称的枚举可以在注释中使用,也可以作为可用于应用程序的其他部分。 很高兴你喜欢@Trevor 见:***.com/questions/13253624/… 我认为内部的“名称”应该是一个接口。【参考方案3】:

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

/**
 * 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】:

我通过使用 Lombok 注释 FieldNameConstants 解决了这个问题:

@FieldNameConstants(onlyExplicitlyIncluded = true)
public enum EnumBasedRole 
    @FieldNameConstants.Include ADMIN,
    @FieldNameConstants.Include EDITOR,
    @FieldNameConstants.Include READER;

接下来你可以按如下方式使用它:

@RestController
@RequestMapping("admin")
@RolesAllowed(EnumBasedRole.Fields.ADMIN)
public class MySecuredController 

   @PostMapping("user")
   public void deleteUser(...) 
       ...
   

【讨论】:

【参考方案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;
  

【讨论】:

以上是关于使用 Enum 类型作为 @RolesAllowed-Annotation 的值参数的主要内容,如果未能解决你的问题,请参考以下文章

打字稿:使用“枚举”值作为“类型”

java怎么声明枚举类型

Enum枚举类型实战总结,保证有用!

在 C# 中使用枚举作为泛型类型参数 [重复]

C++中的enum枚举

C#在Dictionary中使用枚举作为键