IT Share you

@ RolesAllowed-Annotation의 값 매개 변수로 Enum 형식 사용

shareyou 2021. 1. 8. 21:48
반응형

@ RolesAllowed-Annotation의 값 매개 변수로 Enum 형식 사용


저는 현재 Java EE 보안 작업을 수행하여 특정 기능에 대한 액세스를 특정 사용자로 제한하는 Java 엔터프라이즈 애플리케이션을 개발 중입니다. 응용 프로그램 서버와 모든 것을 구성했으며 이제 RolesAllowed 주석을 사용하여 메서드를 보호하고 있습니다.

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

이와 같은 주석을 사용하면 잘 작동합니다.

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

그러나 이것은 내가 원하는 것이 아닙니다. 여기서 문자열을 사용해야하므로 리팩토링이 어려워지고 오타가 발생할 수 있습니다. 따라서 문자열을 사용하는 대신이 주석에 대한 매개 변수로 Enum 값을 사용하고 싶습니다. Enum은 다음과 같습니다.

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은 String (항상 상수, 그렇지 않습니까?)을 반환하지만 다음 컴파일러 오류가 발생합니다.

주석 속성 RolesAllowed.value의 값은 상수 표현식이어야합니다.

다음으로 시도한 것은 Enum에 최종 문자열을 추가하는 것입니다.

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에서 이것을 시도합니다.


열거 형을 사용하는 방법이 작동하지 않을 것이라고 생각합니다. STUDENT_ROLE마지막 예제 필드를식이 아닌 상수 문자열로 변경하면 컴파일러 오류가 사라진 것을 발견했습니다 .

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

그러나 이것은 주석에서 문자열 상수를 대신 사용하기 때문에 열거 형 값이 어디에도 사용되지 않음을 의미합니다.

RoleType클래스에 정적 최종 문자열 상수 만 포함 되어 있으면 더 나을 것 같습니다 .


코드가 컴파일되지 않는 이유를 확인하기 위해 JLS ( Java Language Specification)를 살펴 보았습니다 . 주석에 대한 JLS 는 매개 변수 유형이 T 이고 값이 V주석의 경우 ,

만약 T를 기본 유형이거나 String, V는 일정 식이다.

상수 표현은 , 다른 것들 사이에 포함

TypeName 형식의 정규화 된 이름입니다 . 상수 변수를 참조하는 식별자

그리고 상수 변수 로서 정의되고

기본 유형 또는 유형의 변수, String최종적이고 컴파일 타임 상수 표현식으로 초기화 됨


이것은 어떤가요?

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

한 가지 작은 관심사는 수정을 위해 두 곳에서 변경해야한다는 것입니다. 그러나 그들은 같은 파일에 있기 때문에 놓칠 가능성이 거의 없습니다. 그 대가로 우리는 원시 문자열을 사용하지 않고 정교한 메커니즘을 피하는 이점을 얻고 있습니다.

아니면 완전히 멍청하게 들리나요? :)


다음은 추가 인터페이스와 메타 주석을 사용하는 솔루션입니다. 주석 집합에서 역할 유형을 가져 오는 반영을 수행하는 데 도움이되는 유틸리티 클래스를 포함하고 약간의 테스트를 수행했습니다.

/**
 * 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());
    }
}

주석 @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;
  }
}

참조 URL : https://stackoverflow.com/questions/3271659/use-enum-type-as-a-value-parameter-for-rolesallowed-annotation

반응형