Spring Security @预授权直接传递枚举

vwkv1x7d  于 2022-11-24  发布在  Spring
关注(0)|答案(6)|浏览(204)

我的问题是Custom annotation with spring security的重复,但它没有得到回答,我相信应该有一个简单的解决方案来解决这个问题。
基本上不是在做:

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

我想做的是:

@PreAuthorize(Someclass.WHATEVER, Permission.READ)

或者可能是一些自定义注解,这些注解可以很容易地与Spring Security连接起来
这对我来说似乎干净多了,如果可以的话,我希望能够做到。

kd3sttzy

kd3sttzy1#

实际上,您可以实现一个自定义的强类型安全注解,尽管这相当麻烦。

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

kuhbmx9i2#

您可以建立如下所示的静态注解:

@ReadPermission

通过移动@PreAuthorize注解到@ReadPermission定义:

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

这样做的好处是,您可以在一个位置更改Spring SPEL表达式,而不是在每个方法上修改它。
还有一个好处是,你可以在类级别上使用这个注解--每个方法都可以用这个注解来保护。它对AdminControllers等很有用。

f0ofjuux

f0ofjuux3#

面对同样的问题,我最终采用了一个混合解决方案,我使用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 );
}

其中Permission只是一个普通的枚举定义:

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

希望这能在将来帮助其他人摆脱困境。

gg58donl

gg58donl4#

何月道:
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("hasPermission('myDomain', '"+ MyEnum.Names.ENUM_A+"')")
lg40wkob

lg40wkob5#

我创建了自己的注解,它在参数中获取枚举。在实现注解时,我创建了一个方法,它获取注解中指定的所有角色,并验证当前用户是否至少拥有其中一个角色。如果没有匹配的角色,程序将抛出异常。

枚举:

public enum MyRoles {
    ADMIN("ROLE_ADMIN"),
    USER("ROLE_USER"),
    GUEST("ROLE_GUEST");

    private String name;

    private MyRoles(String name) {
        this.name = name;
    }
}

注解界面:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface AllowedRoles {
    MyRoles[] value();
}

注解的实作:

@Aspect
@Component
public class AllowedRolesAspect {
    @Around("@annotation(com.myproject.annotations.AllowedRoles)")
    public Object doSomething(ProceedingJoinPoint jp) throws Throwable {

        Set<MyRoles> roles = Arrays.stream(((MethodSignature) jp.getSignature()).getMethod()
                .getAnnotation(AllowedRoles.class).value()).collect(Collectors.toSet());

        HttpServletRequest httpServletRequest = getRequest();

        for(MyRoles role : roles){
            if(httpServletRequest.isUserInRole(role)){
                return jp.proceed();
            }
        }

        throw new AccessDeniedException("");
    }

    private HttpServletRequest getRequest() {

        ServletRequestAttributes servletRequestAttributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        return servletRequestAttributes.getRequest();
    }
}

用法:

@AllowedRoles({MyRoles.ADMIN, MyRoles.USER})
@GetMapping("/myrequest/{id}")
public MyResponse getResponse(
        @PathVariable Long id
) {
    /*Do something...*/
}
ne5o7dgx

ne5o7dgx6#

我找到了一个解决方案。虽然有点麻烦,但很有效。

public enum Type {
        MASTER_ADMIN, ADMIN, ACCESS_ADMIN, PUBLISHER, EDITOR, AGENCY, VIEWER;

        @Component("AccountRole")
        @Getter
        static class SpringComponent {
            private final Type MASTER_ADMIN = Type.MASTER_ADMIN;
            private final Type EDITOR = Type.EDITOR;
            ...
        }
    }

...

@Service("AccountRoleAccess")
public class AccountRoleAccess {
    public boolean hasAnyRole(Authentication authentication, AccountId id, AccountRole.Type... roles) {
        ...
    }
}

...

@PreAuthorize("@AccountRoleAccess.hasAnyRole(authentication, #accountId, @AccountRole.MASTER_ADMIN, @AccountRole.EDITOR)")

相关问题