Spring Security 使用自定义规则实现访问控制

2ic8powd  于 2023-08-05  发布在  Spring
关注(0)|答案(1)|浏览(117)

我正在为基于B2B SaaS的应用程序构建一个安全模块(身份验证和授权)。我们有一个简单的身份验证机制,即用户名和密码与MFA。然而,授权部分有点复杂。规则如下:
我们有两个域对象,我们称之为D1和D2。D2是D1的子节点,具有从D1到D2的一对多Map。D1和D2都链接有其他域对象或模块,但是对它们的访问可以基于对D1或D2的访问来计算。现在,对于授权部分,我们需要3个级别的角色。级别1角色不知道可以在平台上执行任何操作的任何域对象(如global_admin)。2级角色需要知道D1标识符,并且它们只能对直接链接到这些D1对象的资源进行操作,3级角色的情况也是如此,其中底层域对象将是D3。
还有一个术语叫做权限组。因此,每当我们将所有这3个角色分配给用户时,我们都会将其保存到权限组,因此用户不会直接Map到角色,而是通过称为权限组的中间层,以便我们可以为其他用户重用以前的权限组,如果需要任何更改,我们将对权限组进行更改,效果将适用于所有链接的用户。
服务器使用Sping Boot 编写,安全性通过Spring Security处理。我正在探索使用Auth0等身份提供程序实现此功能的可能性。
什么是可能的方式来实现这一点?

k4aesqcs

k4aesqcs1#

对于第一级角色,这只是传统的RBAC:角色与用户的关联以及基于该角色的访问控制。Auth0支持角色,Spring安全性也是如此(他们称之为GrantedAuthority)。您所要做的就是提供一个Converter<Jwt, ? extends Collection<? extends GrantedAuthority>>并在Converter<Jwt, JwtAuthenticationToken>中使用它。就像这样:

static class AuthoritiesConverter implements Converter<Jwt, List<SimpleGrantedAuthority>> {
    @Override
    public List<SimpleGrantedAuthority> convert(Jwt source) {
        final List<String> auth0Roles = (List<String>) source.getClaims().getOrDefault("roles", List.of());
        return auth0Roles.stream().map(SimpleGrantedAuthority::new).toList();
    }
}

@Bean
SecurityFilterChain filterChain(
        HttpSecurity http,
        Converter<Jwt, ? extends Collection<? extends GrantedAuthority>> authoritiesConverter) throws Exception {
    http.oauth2ResourceServer(oauth2 -> 
        oauth2.jwt(jwtResourceServer -> 
            jwtResourceServer.jwtAuthenticationConverter(jwt -> 
                new JwtAuthenticationToken(
                        jwt,
                        authoritiesConverter.convert(jwt),
                        jwt.getClaimAsString(StandardClaimNames.SUB)))));

    ...

    return http.build();
}

字符串
对于第二和第三级别,我看到两个选项,这取决于用户和D1或D2之间的基数:

  • 如果用户可以链接到的实体数量适合JWT(JWT的大小不受限制,但它在HTTP头中传输,大多数服务器将头的限制设置为大约8kb),我会:
  • 使用给定用户可以访问的D1和D2的列表公开REST端点
  • 编写Auth0操作以在用户登录时调用此端点
  • 将结果作为私有声明添加到访问令牌
  • 定义一个我自己的Authentication(而不是JwtAuthenticationToken,使用AbstractAuthenticationToken作为基类),其中包含隐藏私有声明访问细节的方法,并使SpEL语法更轻。
  • 如果基数太高,那么我将公开一个授权服务,它将Authentication示例和D1或D2标识符作为参数,并返回一个布尔值进行访问。由于您可以在SpEL中引用bean,因此调用这样的服务非常简单。

在任何情况下,SpEL中的authentication“魔术”变量以及对@PathVariable的引用在编写@PreAuthorize表达式时都会有很大的帮助。
在JWT中使用私有声明的解决方案有一个很大的优势:每次令牌发布仅发生一次数据库访问以获得用户与D1、D2之间的关联,这将使其更好地扩展。

@RequestMapping("/d1s")
@RestController
public class D1Controller {

    @GetMapping("/{d1Id}/by-private-claim")
    @PreAuthorize("hasAuthority('roleLevel1') || authentication.canReadD1(#d1.id)")
    public D1 getD1WithAccessByPrivateClaim(@PathVariable(name = "d1Id") D1 d1) {
        return d1;
    }

    @GetMapping("/{d1Id}/using-authorization-service-bean")
    @PreAuthorize("hasAuthority('roleLevel1') || @authorizationService.canReadD1(authentication, #d1.id)")
    public D1 getD1WithAccessByAuthorizationService(@PathVariable(name = "d1Id") D1 d1) {
        return d1;
    }
}


第一种方法需要提供一个AuthenticationcanReadD1方法(使用私有声明),第二种方法需要提供一个authorizationService bean和canReadD1方法(使用数据库)。

相关问题