Spring Безопасность @PreAuthorization пропускает перечисления напрямую
Мой вопрос является дубликатом Пользовательской аннотации с spring безопасностью, но он остался без ответа, и я считаю, что должно быть простое решение проблемы.
В основном вместо этого:
@PreAuthorize("hasPermission(T(fully.qualified.Someclass).WHATEVER, T(fully.qualified.Permission).READ")
Я хотел бы сделать:
@PreAuthorize(Someclass.WHATEVER, Permission.READ)
или, возможно, некоторые пользовательские аннотации, которые легко соединяются с помощью spring security
Это кажется мне намного чище, и я хотел бы сделать это, если смогу.
Ответы
Ответ 1
Опираясь на ту же проблему, я закончил гибридное решение. Я использую Spring -El и пользовательский bean для предоставления моего собственного метода 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 );
}
Где Разрешение - это просто нормальное определение перечисления:
public enum Permission {
USER_LIST,
USER_EDIT,
USER_ADD,
USER_ROLE_EDIT
}
Надеюсь, это поможет кому-то другому в будущем.
Ответ 2
На самом деле вы можете реализовать пользовательские строго типизированные аннотации безопасности, хотя это довольно утомительно.
Объявите аннотацию
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()));
}
}
Ответ 3
Я сделал так:
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 - значения Concat MyEnum в @PreAuthorize
@PreAuthorize("hasPermission('myDomain', '"+ MyEnum.Names.ENUM_A+"')")
Ответ 4
Вы можете создавать статические аннотации следующим образом:
@ReadPermission
Переместив аннотацию @PreAuthorize
в определение @ReadPermission
:
@Inherited
@PreAuthorize("hasRole(T(fully.qualified.Permission).READ.roleName())")
public @interface ReadPermission {
}
Преимущество этого заключается в том, что вы можете изменить выражение SPEL Spring в одном месте, а не изменять его в каждом методе. Еще одним плюсом является то, что вы можете использовать эту аннотацию на уровне класса - каждый метод будет защищен этой аннотацией. Это полезно для AdminControllers и т.д..