spring-boot - 我可以根据来自我的控制器方法的一些信息使用 Spring 安全性为我的端点实现授权/过滤吗?
问题描述
我有一个看起来像这样的控制器方法
@GetMapping
public ResponseEntity<String> getInformation(@PathVariable("id") String id, InternalAuthInfo info) {
// do something
return new ResponseEntity(value, HttpStatus.OK);
}
我想要做的是根据来自 InternalAuthInfo 信息对象的信息过滤/授权一些 get 调用,我不想在控制器内部实现过滤逻辑。
无论如何我可以使用spring security来实现这个吗?
解决方案
这实际上取决于您想要获得的复杂程度,但让我们简单地开始吧。
在 Spring Security 中,有用于检索用户和用户权限的服务 API。一个例子是UserDetailsService
。一般来说,如果您尝试从数据库中获取用户,例如基于用户名,那么这是一个很好的选择。这也是 Spring Security 默认选择的用于用户名/密码认证的 API。
因此,您可以像这样创建一个委托UserDetailsService
:
public class InternalAuthInfoUserDetailsService implements UserDetailsService {
UserDetailsService delegate = new JdbcUserDetailsManager();
public UserDetails loadUserByUsername(String username) {
User user = this.delegate.loadUserByUsername(username);
InternalAuthInfo info = myWayToLookThisUp(username);
Collection<GrantedAuthority> roles = myWayToMapInfo(info);
return new User(user.getUsername(), user.getPassword(), roles);
}
}
然后你会注册你的自定义UserDetailsService
:
@Bean
UserDetailsService userDetailsService() {
return new InternalAuthInfoUserDetailsService();
}
这种方法的想法是,您可以将详细信息映射InternalAuthInfo
到您将决定的各种授权角色,例如ROLE_USER
. 此时,有几种支持的过滤方式:
@PreAuthorize("hasRole('USER')")
@GetMapping(...
正如另一张海报所说的那样,这是一种非常简单的方法。
或者,如果您想将您的InternalAuthInfo
实例作为用户主体的一部分进行维护,那么您可以在您的自定义中创建一个桥接对象UserDetailsService
:
public class InternalAuthInfoUserDetailsService
implements UserDetailsService {
UserDetailsService delegate = new JdbcUserDetailsManager();
public UserDetails loadUserByUsername(String username) {
User user = this.delegate.loadUserByUsername(username);
InternalAuthInfo info = myWayToLookThisUp(username);
return new MyUser(info, user);
}
private static class MyUser extends InternalAuthInfo
implements UserDetails {
private final UserDetails delegate;
public MyUser(InternalAuthInfo info, UserDetails user) {
super(info);
this.delegate = user;
}
// ... delegate methods
}
}
其余接线相同,但用法略有不同。
使用此设置,您可以InternalAuthInfo
直接在您的授权表达式中引用:
@PreAuthorize("principal?.someAuthInfoProperty == 'some-value'")
@GetMapping(...
我假设InternalAuthInfo
有像getSomeAuthInfoProperty
.
顺便说一句,由于假设了一组特定的数据库表和列,因此通常UserDetailsService
会完全自定义。JdbcUserDetailsManager
因此,您可能有一个利用 Spring Data JPA 之类的实现:
public class InternalAuthInfoUserDetailsService
implements UserDetailsService {
@Autowired
private InternalAuthInfoRepository repository;
@Override
public User loadUserByUsername(String username) {
InternalAuthInfo info = this.repository.findByUsername(username);
return new MyInternalAuthInfoUser(info);
}
}
但是,在这里我们开始对您的设置做出其他假设。
这里的一般要点是,您可以
- 实现将 a
UserDetailsService
转换InternalAuthInfo
为一组GrantedAuthority
s - 然后这些将成为Authentication#getAuthorities
这里的优点是表示被简化了,仅在身份验证语句中引用 Spring Security 公民。
- 实现一个
UserDetailsService
桥接 Spring Security 的User
域对象和InternalAuthInfo
对象 - 然后这些将成为Authentication#getPrincipal
这里的优点是身份验证语句包含 Spring Security 公民以及InternalAuthInfo
用于更复杂检查的实例。
最后,这不完全是您的问题,但我想指出,用户可以在方法级别授权和过滤级别之间进行选择。它们各有优缺点,但我建议您查看过滤器级别,因为您可以使用表达式来描述整个应用程序的规则,而不是逐个方法。我在这里逐个使用方法只是为了便于演示。