英文:
Avoid repetitive values for @Secured annotation
问题
我正在尝试使用以下方式通过 `@Secured` 来保护我的服务方法:
```java
public interface IUserService {
@Secured({"ROLE_ROLE1", "ROLE_ROLE2"})
ResponseEntity saveUser(CreateUserDtoRequest userDto);
}
我想知道是否有一种方法可以将 {"ROLE_ROLE1", "ROLE_ROLE2"}
定义为一个变量,并从 properties
文件中读取其 value
值?
如果您能为我提供一个诀窍,那将非常好:
- 从其他方法中消除
{"ROLE_ROLE1", "ROLE_ROLE2"}
的重复 - 如果将来访问方法所需的角色发生更改,则无需更改代码,重新编译并再次部署。
请注意,由于您要求只返回翻译好的部分,上述即为您提供的文本的翻译。如果您有任何其他需要,请随时告诉我。
<details>
<summary>英文:</summary>
I am trying to secure my service methods using `@Secured` as below:
public interface IUserService {
@Secured({"ROLE_ROLE1", "ROLE_ROLE2"})
ResponseEntity saveUser(CreateUserDtoRequest userDto);
}
I wanna know is there a way to define `{"ROLE_ROLE1", "ROLE_ROLE2"}` in a variable and read its `value` from a `properties` file?
That would be great if you can suggest me a trick, to:
1. remove repetition of `{"ROLE_ROLE1", "ROLE_ROLE2"}` in other methods
2. In case of change in required roles to access a method in future, there would be no need to change the code, recompile and deploy it again.
</details>
# 答案1
**得分**: 1
有几种方法可以实现你的需求:
----------
## 开发自定义的 `MethodSecurityExpressionOperations` ##
在这个 [教程][1] 中,你会看到如何处理新的自定义安全方法(*第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) {
...
}
----------
**如果你不想每次更改这些角色时都重新编译/部署项目**,你可以将它们保存在外部存储中,比如数据库(在提供的示例中更新任何一个示例来处理这种情况应该不成问题)。在第二个示例中,我使用了一个属性来保持简单,但是在 `FromPropertyRoleSecurityCheck` 中包含一个 `Repository` 来从数据库获取它们也很容易。
附:提供链接的示例和自定义示例都是在 **Controller** 层开发的,但它们也应该适用于 **Service** 层。
[1]: https://www.baeldung.com/spring-security-create-new-custom-security-expression
<details>
<summary>英文:</summary>
There are several ways to do what you need:
----------
## Develop your custom `MethodSecurityExpressionOperations` ##
In this [tutorial][1] you will see how to deal with a new custom security method (*section 5*) or override the current `hasAuthority` one (*section 6*)
----------
## Develop your custom method to use in `SpEL` ##
Probably an esier option, the steps could be the following ones:
**1.** Include the allowed roles in your `application.yml` (or `properties`)
security:
rolesAllowed: ADMIN,USER
**2.** Define the class to check those roles and authorized user ones. For example:
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.** Add the security check:
@PreAuthorize("@fromPropertyRoleSecurityCheck.verifyRoles()")
public ResponseEntity<MyDto> findById(@PathVariable @Positive Integer id) {
...
}
----------
**If you don't want to recompile/deploy the project every time those roles change**, you can save them in an external storage like database for example (shouldn't be a problem to update any of provided examples to deal with such situations). In the second one I used a property to keep it simple, but is quite easy to include a `Repository` in `FromPropertyRoleSecurityCheck` to get them from database.
PD. Examples of provided link and custom one were developed in **Controller** layer, but they should work in the **Service** one too.
[1]: https://www.baeldung.com/spring-security-create-new-custom-security-expression
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论