Spring Security @PreAuthorization передает перечисления напрямую

Мой вопрос является дубликатом Пользовательская аннотация с Spring Security, но он остался без ответа, и я считаю должно быть простое решение проблемы.

В основном вместо того, чтобы делать:

@PreAuthorize("hasPermission(T(fully.qualified.Someclass).WHATEVER, T(fully.qualified.Permission).READ")

Я хотел бы сделать:

@PreAuthorize(Someclass.WHATEVER, Permission.READ)

или, возможно, какая-то пользовательская аннотация, которая будет легко подключаться к Spring Security

Это кажется мне намного чище, и я хотел бы иметь возможность сделать это, если смогу.


person user1751547    schedule 10.10.2013    source источник
comment
Удачи решить это? У меня такая же проблема сегодня.   -  person Eric B.    schedule 22.03.2014
comment
Нет, я укусил пулю и использовал струны =(   -  person user1751547    schedule 09.04.2014


Ответы (4)


Действительно, вы можете реализовать пользовательскую строго типизированную аннотацию безопасности, хотя это довольно утомительно. Объявите свою аннотацию

enum Permission {
    USER_LIST,
    USER_EDIT,
    USER_ADD,
    USER_ROLE_EDIT
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Permissions {
    Permission[] value();
}

Объявите пользовательскую реализацию org.springframework.security.access.ConfigAttribute для использования конвейером безопасности.

class SecurityAttribute implements ConfigAttribute {
    private final List<Permission> permissions;

    public SecurityAttribute(List<Permission> permissions) {
        this.permissions = permissions;
    }

    @Override
    public String getAttribute() {
        return permissions.stream().map(p -> p.name()).collect(Collectors.joining(","));
    }
}

Объявите пользовательскую реализацию org.springframework.security.access.method.MethodSecurityMetadataSource для создания экземпляров SecurityAttribute из аннотаций.

class SecurityMetadataSource extends AbstractMethodSecurityMetadataSource {
    @Override
    public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {

      //consult https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/access/prepost/PrePostAnnotationSecurityMetadataSource.java
      //to implement findAnnotation  
      Permissions annotation = findAnnotation(method, targetClass, Permissions.class);
        if (annotation != null) {
            return Collections.singletonList(new SecurityAttribute(asList(annotation.value())));
        }
        return Collections.emptyList();
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    } 

}

Наконец объявите пользовательскую реализацию org.springframework.security.access.AccessDecisionVoter

public class PermissionVoter implements AccessDecisionVoter<MethodInvocation> {
    @Override
    public boolean supports(ConfigAttribute attribute) {
        return attribute instanceof SecurityAttribute;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return MethodInvocation.class.isAssignableFrom(clazz);
    }

    @Override
    public int vote(Authentication authentication, MethodInvocation object, Collection<ConfigAttribute> attributes) {
        Optional<SecurityAttribute> securityAttribute = attributes.stream()
                .filter(attr -> attr instanceof SecurityAttribute).map(SecurityAttribute.class::cast).findFirst();
        if(!securityAttribute.isPresent()){
            return AccessDecisionVoter.ACCESS_ABSTAIN;
        }
        //authorize your principal from authentication object
        //against permissions and return ACCESS_GRANTED or ACCESS_DENIED

    }

}

а теперь собери их всех вместе в своем MethodSecurityConfig

@Configuration
@EnableGlobalMethodSecurity
class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {

    @Override
    protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
        return new ScpSecurityMetadataSource();
    }

    @Override
    protected AccessDecisionManager accessDecisionManager() {
        return new AffirmativeBased(Collections.singletonList(new PermissionVoter()));
    }
}
person Dima Korn    schedule 06.08.2018
comment
Хотя это действительно не волшебный ответ с одной строкой, он по-прежнему предлагает отличное решение для добавления пользовательской аннотации безопасности! Я просто следовал логике реализации, и было не так сложно понять общую логику. - person Tazaf; 23.04.2021

Столкнувшись с той же проблемой, я остановился на гибридном решении. Я использую Spring-El и пользовательский компонент для предоставления собственного метода hasPermission(), который принимает Enum. Учитывая, что Spring выполняет автоматическое преобразование string->enum, во время выполнения я получу исключение времени выполнения, что конкретное перечисление не существует, если в строке есть опечатка. Не идеальное решение (лучше было бы что-то, что не удалось во время компиляции), но приемлемый компромисс. Это дает мне некую полубезопасность.

@Component("securityService")
public class SecurityService {
    public boolean hasPermission( Permission...permissions){
        // loop over each submitted role and validate the user has at least one
        Collection<? extends GrantedAuthority> userAuthorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
        for( Permission permission : permissions){
            if( userAuthorities.contains( new SimpleGrantedAuthority(permission.name())))
                return true;
        }

        // no matching role found
        return false;
    }
}

Используется следующим образом:

@PreAuthorize("@securityService.hasPermission({'USER_ADD'})")
public User addUser(User user){
    // create the user
    return userRepository.save( user );
}

Где Permission — это обычное определение перечисления:

public enum Permission {
    USER_LIST,
    USER_EDIT,
    USER_ADD,
    USER_ROLE_EDIT
}

Надеюсь, это может помочь кому-то еще в будущем.

person Eric B.    schedule 09.04.2014

Вы можете создавать статические аннотации следующим образом:

@ReadPermission

Переместив аннотацию @PreAuthorize в @ReadPermissiondefinition:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole(T(fully.qualified.Permission).READ.roleName())")
public @interface ReadPermission {
    
}

Преимущество этого заключается в том, что вы можете изменить выражение Spring SPEL в одном месте, вместо того, чтобы изменять его для каждого метода.

Еще одним плюсом является то, что вы можете использовать эту аннотацию на уровне класса - тогда каждый метод будет защищен этой аннотацией. Это полезно для AdminControllers и т.д.

person Gondy    schedule 05.09.2018
comment
кажется, что Spring Security игнорирует эти пользовательские аннотации - person wutzebaer; 19.12.2019
comment
кажется, что @Retention(RetentionPolicy.RUNTIME) отсутствует, теперь вроде работает - person wutzebaer; 19.12.2019

Я сделал так:

1 - Определите свое перечисление, ссылающееся на общедоступную конечную статическую строку «VALUE», как это

public enum MyEnum {
    ENUM_A(Names.ENUM_A);

    private String value;

    private MyEnum (String value) {
        this.value = value;
    }

    public static class Names {

        public  final static String ENUM_A = "ENUM_A";
    }
}

2 - Объединить значения MyEnum в @PreAuthorize

@PreAuthorize("hasPermission('myDomain', '"+ MyEnum.Names.ENUM_A+"')")
person e.g78    schedule 13.03.2017
comment
Когда я пытаюсь сделать это, я получаю ошибку компиляции: значение атрибута должно быть постоянным. Вы обошли это? - person Scott Carlson; 23.04.2019
comment
вы получаете ошибку компиляции, если вы используете MyEnum.Names.ENUM_A? Вы определили его как общедоступную конечную статическую строку, как в образце? - person e.g78; 23.04.2019
comment
разве это не упускает из виду то, что я хочу определить свои имена ролей только один раз? Мне нужно обновить статические имена вручную - person wutzebaer; 19.12.2019
comment
Таким образом, вы можете определить только один раз, в перечислении - person e.g78; 19.12.2019