我的问题是Custom annotation with spring security的副本,但没有得到答复,我相信应该有一个简单的解决方案。
基本上不是做:
@PreAuthorize("hasPermission(T(fully.qualified.Someclass).WHATEVER, T(fully.qualified.Permission).READ")
我想这样做:
@PreAuthorize(Someclass.WHATEVER, Permission.READ)
或者可能是一些自定义注释,可以很容易地与弹簧安全性连接
这对我来说似乎更清洁,如果可以,我希望能够做到这一点。
答案 0 :(得分:7)
面对同样的问题,我最终得到了一个混合解决方案。我使用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
}
希望将来可以帮助其他人。
答案 1 :(得分:3)
实际上,您可以实现自定义的强类型安全注释,尽管这很麻烦。 声明您的注释
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()));
}
}
答案 2 :(得分:2)
您可以像这样创建静态注释:
@ReadPermission
通过将@PreAuthorize
注释移至@ReadPermission
定义:
@Inherited @PreAuthorize("hasRole(T(fully.qualified.Permission).READ.roleName())") public @interface HasAdminRole { }
这样做的好处是,您可以在一个地方更改Spring SPEL表达式,而不必在每种方法上都进行修改。另外一个好处是,您可以在类级别使用此注释-然后,将使用此注释保护每个方法。这对AdminControllers等很有用。
答案 3 :(得分:1)
我这样做了:
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 - @PreAuthorize中的Concat MyEnum值
@PreAuthorize("hasPermission('myDomain', '"+ MyEnum.Names.ENUM_A+"')")