首页 > 解决方案 > 我可以根据来自我的控制器方法的一些信息使用 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-bootspring-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);
    }
}

但是,在这里我们开始对您的设置做出其他假设。

这里的一般要点是,您可以

  1. 实现将 aUserDetailsService转换InternalAuthInfo为一组GrantedAuthoritys - 然后这些将成为Authentication#getAuthorities

这里的优点是表示被简化了,仅在身份验证语句中引用 Spring Security 公民。

  1. 实现一个UserDetailsService桥接 Spring Security 的User域对象和InternalAuthInfo对象 - 然后这些将成为Authentication#getPrincipal

这里的优点是身份验证语句包含 Spring Security 公民以及InternalAuthInfo用于更复杂检查的实例。

最后,这不完全是您的问题,但我想指出,用户可以在方法级别授权和过滤级别之间进行选择。它们各有优缺点,但我建议您查看过滤器级别,因为您可以使用表达式来描述整个应用程序的规则,而不是逐个方法。我在这里逐个使用方法只是为了便于演示。


推荐阅读