Используйте тип Enum в качестве параметра значения для @RolesAllowed-Annotation
Я занимаюсь разработкой корпоративного приложения Java, в настоящее время занимающегося безопасностью Java EE, чтобы ограничить доступ к определенным функциям определенным пользователям. Я настроил сервер приложений и все такое, и теперь я использую аннотацию RolesAllowed для защиты методов:
@Documented
@Retention (RUNTIME)
@Target({TYPE, METHOD})
public @interface RolesAllowed {
String[] value();
}
Когда я использую такую аннотацию, она отлично работает:
@RolesAllowed("STUDENT")
public void update(User p) { ... }
Но это не то, что я хочу, так как я должен использовать String здесь, рефакторинг становится трудным, и опечатки могут произойти. Поэтому вместо использования String я хотел бы использовать значение 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 просто возвращает строку (которая всегда постоянна, не так ли?).
Значение атрибута аннотации 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. Я узнал, что Scala не является источником проблемы, поэтому я сначала попытаюсь сделать это на Java.
Ответы
Ответ 1
Я не думаю, что ваш подход к использованию перечислений будет работать. Я обнаружил, что ошибка компилятора исчезла, если я изменил поле STUDENT_ROLE
в вашем последнем примере на константную строку, в отличие от выражения:
public enum RoleType {
...
public static final String STUDENT_ROLE = "STUDENT";
...
}
Однако это означает, что значения enum не будут использоваться нигде, потому что вместо этого вы будете использовать строковые константы в аннотациях.
Мне кажется, что вам будет лучше, если ваш класс RoleType
содержит не что иное, как кучу статических окончательных строковых констант.
Чтобы узнать, почему ваш код не компилировался, я просмотрел Java Language Specification (JLS). В JLS для аннотаций указано, что для аннотации с параметром типа T и значением V,
если T - примитивный тип или String
, V - постоянное выражение.
A константное выражение включает, среди прочего,
Квалифицированные имена формы TypeName. Идентификатор, который ссылается на постоянные переменные
и константная переменная определяется как
переменная, примитивного типа или типа String
, которая является окончательной и инициализируется выражением константы времени компиляции
Ответ 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) { ... }
Одна небольшая проблема заключается в том, что для любой модификации мы должны измениться в двух местах. Но поскольку они находятся в одном файле, его вряд ли удастся упустить. В свою очередь, мы получаем выгоду от использования необработанных строк и предотвращения сложного механизма.
Или это звучит совершенно глупо?:)
Ответ 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
Я решил эту проблему, добавив аннотацию @RoleTypesAllowed
и добавив источник метаданных. Это работает очень хорошо, если есть только один тип enum, который нужно поддерживать. Для нескольких типов enum см. Пост anomolos.
В приведенном ниже RoleType
моя роль enum.
@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;
}
}