如何在 OAuth2 资源服务器中提取自定义主体?

1Z1*_*Z10 6 spring-security spring-boot spring-security-oauth2

我使用Keycloak作为我的 OAuth2 授权服务器,并按照GitHub上的官方示例配置了用于多租户的 OAuth2 资源服务器。考虑到JWT令牌的Issuer字段,当前租户已解决。因此,根据相应OpenID Connect众所周知端点处公开的JWKS来验证令牌。

这是我的安全配置:

@EnableWebSecurity
@RequiredArgsConstructor
@EnableAutoConfiguration(exclude = UserDetailsServiceAutoConfiguration.class)
public class OrganizationSecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final TenantService tenantService;
    private List<Tenant> tenants;

    @PostConstruct
    public void init() {
        this.tenants = this.tenantService.findAllWithRelationships();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests().anyRequest().authenticated()
                .and()
                .oauth2ResourceServer()
                .authenticationManagerResolver(new MultiTenantAuthenticationManagerResolver(this.tenants));
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我的自定义AuthenticationManagerResolver

public class MultiTenantAuthenticationManagerResolver implements AuthenticationManagerResolver<HttpServletRequest> {

    private final AuthenticationManagerResolver<HttpServletRequest> resolver;

    private List<Tenant> tenants;

    public MultiTenantAuthenticationManagerResolver(List<Tenant> tenants) {
        this.tenants = tenants;

        List<String> trustedIssuers = this.tenants.stream()
                .map(Tenant::getIssuers)
                .flatMap(urls -> urls.stream().map(URL::toString))
                .collect(Collectors.toList());

        this.resolver = new JwtIssuerAuthenticationManagerResolver(trustedIssuers);
    }

    @Override
    public AuthenticationManager resolve(HttpServletRequest context) {
        return this.resolver.resolve(context);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,由于其设计org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver.TrustedIssuerJwtAuthenticationManagerResolverprivate,我认为提取自定义主体的唯一方法是重新实现以下所有内容:

  • TrustedIssuerJwtAuthenticationManagerResolver
  • 返回的AuthenticationManager
  • 身份验证转换
  • 扩展JwtAuthenticationTokenCustomAuthenticationToken
  • 海关委托人

对我来说,这似乎是很多重新发明轮子的事情,我唯一需要的是有一个定制的委托人。

我发现的示例似乎不适合我的情况,因为它们引用了 OAuth2Client 或者不适用于多租户。

我真的需要重新实现所有此类/接口还是有更聪明的方法?

kvb*_*vbx 1

我就是这样做的,没有重新实现大量的类。但这是在不使用 JwtAuthenticationToken 的情况下实现的。

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  ...

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    
    http
      ...
      .oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver()));
  }

  @Bean
  JwtIssuerAuthenticationManagerResolver authenticationManagerResolver() {

    List<String> issuers = ... // get this from list of tennants or config, whatever
    Predicate<String> trustedIssuer = issuers::contains;
    Map<String, AuthenticationManager> authenticationManagers = new ConcurrentHashMap<>();

    AuthenticationManagerResolver<String> resolver = (String issuer) -> {
      if (trustedIssuer.test(issuer)) {
        return authenticationManagers.computeIfAbsent(issuer, k -> {
          var jwtDecoder = JwtDecoders.fromIssuerLocation(issuer);
          var provider = new JwtAuthenticationProvider(jwtDecoder);
          provider.setJwtAuthenticationConverter(jwtAuthenticationService::loadUserByJwt);
          return provider::authenticate;
        });
      }
      return null;
    };
    
    return new JwtIssuerAuthenticationManagerResolver(resolver);
  }
}
Run Code Online (Sandbox Code Playgroud)
@Service
public class JwtAuthenticationService {

  public AbstractAuthenticationToken loadUserByJwt(Jwt jwt) {
    
    UserDetails userDetails = ... // or your choice of principal
    List<GrantedAuthority> authorities = ... // extract from jwt or db
    ...
    return new UsernamePasswordAuthenticationToken(userDetails, null, authorities);
  }
}
Run Code Online (Sandbox Code Playgroud)