首页 > 解决方案 > 避免 @Secured 注解的重复值

问题描述

我正在尝试使用@Secured以下方法保护我的服务方法:

public interface IUserService {

@Secured({"ROLE_ROLE1", "ROLE_ROLE2"})
    ResponseEntity saveUser(CreateUserDtoRequest userDto);

}

我想知道有没有办法{"ROLE_ROLE1", "ROLE_ROLE2"}在变量中定义并valueproperties文件中读取它?如果你能建议我一个技巧,那就太好了:

  1. {"ROLE_ROLE1", "ROLE_ROLE2"}删除其他方法中的重复
  2. 如果将来访问方法所需的角色发生变化,则无需更改代码、重新编译和再次部署。

标签: javaspring-bootspring-security

解决方案


有几种方法可以满足您的需要:


开发您的自定义MethodSecurityExpressionOperations

在本教程中,您将看到如何处理一种新的自定义安全方法(第 5 节)或覆盖当前的安全方法hasAuthority第 6 节)。


开发您的自定义方法以用于SpEL

可能是一个更简单的选择,步骤可能是以下步骤:

1.application.yml在您的(或properties)中包含允许的角色

security:
  rolesAllowed: ADMIN,USER

2.定义类以检查这些角色和授权用户。例如:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toSet;

@Component
public class FromPropertyRoleSecurityCheck {

  private final static String ROLE_SEPARATOR = ",";

  @Value("${security.rolesAllowed}")
  private String rawRolesAllowed;


  public boolean verifyRoles() {
    return getPrincipalAuthorities()
            .map(auth -> {
                Set<String> rolesAllowed = Stream.of(rawRolesAllowed.split(ROLE_SEPARATOR))
                        .map(String::trim)
                        .collect(toSet());
                return verifyAllowedRoles(rolesAllowed, auth);
            })
            .orElse(false);
  }


  private Optional<Collection<? extends GrantedAuthority>> getPrincipalAuthorities() {
    return ofNullable(SecurityContextHolder.getContext())
            .map(SecurityContext::getAuthentication)
            .map(Authentication::getAuthorities);
  }


  private boolean verifyAllowedRoles(final Collection<String> rolesAllowed,
                                     final Collection<? extends GrantedAuthority> principalAuthorities) {
    if (CollectionUtils.isEmpty(rolesAllowed)) {
        return true;
    }
    if (CollectionUtils.isEmpty(principalAuthorities)) {
        return false;
    }
    Set<String> rolesDiff = principalAuthorities.stream().map(GrantedAuthority::getAuthority).collect(toSet());
    rolesDiff.removeAll(rolesAllowed);
    return rolesDiff.size() != principalAuthorities.size();
  }

}

3.添加安全检查:

@PreAuthorize("@fromPropertyRoleSecurityCheck.verifyRoles()")
public ResponseEntity<MyDto> findById(@PathVariable @Positive Integer id) {
  ...
}

如果您不想在每次这些角色更改时重新编译/部署项目,您可以将它们保存在例如数据库之类的外部存储中(更新任何提供的示例以处理这种情况应该不是问题)。在第二个中,我使用了一个属性来保持简单,但是很容易包含一个从数据库RepositoryFromPropertyRoleSecurityCheck获取它们的属性。

PD。提供的链接和自定义链接的示例是在控制器层中开发的,但它们也应该在服务层中工作。


推荐阅读