Skip to content

Add ReactiveJwtIssuerAuthenticationManagerResolver and Reactive Multi Tentant Examples #7857

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
davidmelia opened this issue Jan 23, 2020 · 4 comments · Fixed by #7887
Closed
Assignees
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) type: enhancement A general enhancement
Milestone

Comments

@davidmelia
Copy link

Hi,

I'm trying to add oauth resource server multi tenancy support (by issuer) to my existing webflux stack (Boot 2.2, Spring Security 5.2.1) and am really struggling.

I have a couple of requests:

  1. Could you add a reactive counterpart to Add JwtIssuerAuthenticationManagerResolver #7733

  2. All the examples in https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2resourceserver-multitenancy are none reactive and I'm really struggling to get reactive to work. I've figured out the below but have had to cut and paste code from ServerBearerTokenAuthenticationConverter which seems wrong:

public class TenantAuthenticationManagerResolver implements ReactiveAuthenticationManagerResolver<ServerHttpRequest> {
  private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-._~+/]+)=*$", Pattern.CASE_INSENSITIVE);

  private final Map<String, String> tenants;

  private final Map<String, JwtReactiveAuthenticationManager> authenticationManagers = new ConcurrentHashMap<>();

  public TenantAuthenticationManagerResolver() {
    this.tenants = Map.of("https://url.ii.co.uk/", "https://url.ii.co.uk/");
  }

  @Override
  public Mono<ReactiveAuthenticationManager> resolve(ServerHttpRequest request) {
    return Mono.just(this.authenticationManagers.computeIfAbsent(toTenant(request), this::fromTenant));
  }

  private String toTenant(ServerHttpRequest request) {
    try {
      String token = token(request);
      return JWTParser.parse(token).getJWTClaimsSet().getIssuer();
    } catch (Exception e) {
      throw new IllegalArgumentException(e);
    }
  }

  private JwtReactiveAuthenticationManager fromTenant(String tenant) {
    return Optional.ofNullable(this.tenants.get(tenant)).map(ReactiveJwtDecoders::fromIssuerLocation).map(JwtReactiveAuthenticationManager::new)
        .orElseThrow(() -> new IllegalArgumentException("unknown tenant"));
  }

  private String token(ServerHttpRequest request) {
    String authorizationHeaderToken = resolveFromAuthorizationHeader(request.getHeaders());
    if (authorizationHeaderToken != null) {
      return authorizationHeaderToken;
    }
    return null;
  }

  private static String resolveFromAuthorizationHeader(HttpHeaders headers) {
    String authorization = headers.getFirst(HttpHeaders.AUTHORIZATION);
    if (StringUtils.startsWithIgnoreCase(authorization, "bearer")) {
      Matcher matcher = authorizationPattern.matcher(authorization);

      if (!matcher.matches()) {
        BearerTokenError error = invalidTokenError();
        throw new OAuth2AuthenticationException(error);
      }

      return matcher.group("token");
    }
    return null;
  }

  private static BearerTokenError invalidTokenError() {
    return new BearerTokenError(BearerTokenErrorCodes.INVALID_TOKEN, HttpStatus.UNAUTHORIZED, "Bearer token is malformed", "https://tools.ietf.org/html/rfc6750#section-3.1");
  }
}

Ideally I would like to implement a reactive version of https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#parsing-the-claim-only-once but cannot figure out NimbusReactiveJwtDecoder. Could you give me any pointers?

Thanks

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jan 23, 2020
@jzheaux jzheaux added in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Jan 28, 2020
@jzheaux
Copy link
Contributor

jzheaux commented Jan 28, 2020

Hi, @davidmelia, thanks for reaching out about this - I think a reactive version of JwtIssuerAuthenticationManagerResolver is something that makes sense to add. I agree that you shouldn't have to copy from ServerBearerTokenAuthenticationConverter. I wonder if something like this would work:

public Mono<AuthenticationManager> resolve(ServerWebExchange exchange) {
    BearerTokenAuthenticationToken bearerToken = (BearerTokenAuthenticationToken)
            this.authenticationConverter.convert(exchange);
    String token = bearerToken.getToken();
    // extract issuer claim
    return this.issuerAuthenticationManagerResolver.resolve(exchange);
}

Would you be interested in working with me on a PR for JwtIssuerReactiveAuthenticationManagerResolver?

As for your question about parsing only once - I'd first recommend assessing how much of your request is actually being spent on parsing the JWT. Because Nimbus doesn't yet have a reactive API, the amount of time and complexity involved in ironing that out is most likely an early optimization.

@davidmelia
Copy link
Author

@jzheaux I could work on the PR with you.

I think one big issue here to your suggestion is that AuthenticationWebFilter requires the following

private final ReactiveAuthenticationManagerResolver<ServerHttpRequest> authenticationManagerResolver;

and therefore we cannot get hold of the ServerWebExchange unless I write a new AuthenticationWebFilter which I don't really want to do.

@jzheaux
Copy link
Contributor

jzheaux commented Jan 29, 2020

Good point, @davidmelia. I believe #7872 will address that.

If you agree that such would address your concern, maybe we start with that ticket?

Thinking ahead just a bit, it'd be nice to address these tickets before RC1 next week. Do you think you've got time to submit a PR by Friday? If not, no worries, and I can submit the PR and have you review it.

@davidmelia
Copy link
Author

@jzheaux I won't have time to submit the PR but I would definitely help review your PR by Friday.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants