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

时间:2020-04-20 09:29:53

标签: spring-boot spring-security 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));
    }
}

这是我的自定义 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);
    }
}

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

  • TrustedIssuerJwtAuthenticationManagerResolver
  • 返回的 AuthenticationManager
  • AuthenticationConverter
  • CustomAuthenticationToken ,它扩展了 JwtAuthenticationToken
  • CustomPrincipal

在我看来,很多重新发明轮子,我唯一需要的就是拥有一个自定义的Principal。

我发现的示例似乎不适合我的情况,因为它们引用了OAuth2Client或对Multitenancy而言并不困难。

我真的需要重新实现所有这些类/接口吗,还是有一种更聪明的方法?

1 个答案:

答案 0 :(得分:0)

我就是这样做的,没有重新实现大量的类。然而,这没有使用 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);
  }
}
@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);
  }
}