spring-authorization-server icon indicating copy to clipboard operation
spring-authorization-server copied to clipboard

How-to: Use Spring Cloud Gateway with Spring Authorization Server

Open sjohnr opened this issue 1 year ago • 10 comments

Publish a guide on how to set up Spring Cloud Gateway as an OAuth2 Client of Spring Authorization Server in order to use the gateway as a BFF (backend-for-frontend). This guide would demonstrate using the TokenRelay filter to adapt from a browser-based session (i.e. JSESSIONID cookie) to an Authorization header containing an access token (i.e. Bearer tokens) when making protected resources requests.

The guide should mention the main benefits of this architecture choice, which include:

  • Securely storing access tokens (and refresh tokens) on the server (backend) instead of in the browser
  • Centralizing OAuth2 Client configuration in the gateway
  • Simplifying authentication requirements for browser-based (frontend) applications

sjohnr avatar Aug 06 '24 17:08 sjohnr

Related gh-564, gh-297

sjohnr avatar Aug 06 '24 17:08 sjohnr

This will be extremely helpful for the community. I've been struggling trying to find a decent working tutorial that would help me to implemente a bff. Only thing i can find is Ch4mp redirecting to the baeldung tutorial he wrote. i tried to follow it, but it is pretty complex for a person who is starting in this.

lojc avatar Aug 24 '24 03:08 lojc

Thanks for the upvote @lojc. One thing I'm wondering for the benefit of this guide would be: What are the main challenges you have faced when implementing a BFF? Can you share any details about your use case?

sjohnr avatar Aug 26 '24 19:08 sjohnr

@sjohnr Would it be possible to also include Token-Mediating Backend?

HungUnicorn avatar Sep 13 '24 07:09 HungUnicorn

I don't think so, @HungUnicorn. That would be a separate guide, but not one that we would likely demonstrate as it is less secure than a BFF.

sjohnr avatar Sep 13 '24 15:09 sjohnr

@sjohnr Maybe something like this. Can support multiple frontend clients.

  • Authentication
        @GetMapping("/authorize")
	public Mono<ResponseEntity<Void>> index(ServerWebExchange webExchange, @RequestParam(value = APP_PARAM, required = false) String appName) {
		AppProperties.Frontend.App app = properties.getFrontend().getDefaultApp();
		if (StringUtils.hasText(appName)) {
			AppProperties.Frontend.App targetApp = properties.getFrontend().getApp().get(appName);
			app = targetApp != null ? targetApp : app;
		}
		
		String targetUrl = determineTargetUrl(webExchange, app);
		return Mono.just(ResponseEntity.status(HttpStatus.FOUND)
				.header(HttpHeaders.LOCATION, UriComponentsBuilder.fromUriString(targetUrl).toUriString())
				.build());
	}
  • Logout org.springframework.security.oauth2.client.oidc.web.server.logout.OidcClientInitiatedServerLogoutSuccessHandler
        //Dynamically obtain the logout address instead of the fixed value of the `postLogoutRedirectUri` property.
	@Nullable
	private String getPostLogoutRedirectUri(ServerHttpRequest request) {
		return Optional.ofNullable(request.getQueryParams().getFirst(APP_PARAM))
				.map(p -> properties.getFrontend().getApp().get(p))
				.map(AppProperties.Frontend.App::getPostLogoutRedirectUrl)
				.orElseGet(() -> this.properties.getFrontend().getDefaultApp().getPostLogoutRedirectUrl());
	}

loren-coding avatar Sep 24 '24 02:09 loren-coding

@loren-coding thanks for the suggestion. However, I feel like I might be missing some context. I don't think I see how multiple frontend applications are relevant to this guide. Also, if this code belongs in the authorization server, we should let the OAuth2 framework handle secure redirects using the redirect_uri and post_logout_redirect_uri parameters instead of reinventing the wheel here, so I don't think we would include anything like this. Having said that, it's also possible I'm misunderstanding what you're trying to demonstrate. Regardless, let's stay focused on discussing the focus for this guide which is Spring Cloud Gateway.

sjohnr avatar Sep 24 '24 21:09 sjohnr

It would be really great if we could also have a multi-tenant implementation of the Spring Cloud Gateway and Spring Authorization Server as a sample (maybe even with a Resource Server multi-tenant).

sebutzu avatar Sep 24 '24 22:09 sebutzu

@loren-coding thanks for the suggestion. However, I feel like I might be missing some context. I don't think I see how multiple frontend applications are relevant to this guide. Also, if this code belongs in the authorization server, we should let the OAuth2 framework handle secure redirects using the redirect_uri and post_logout_redirect_uri parameters instead of reinventing the wheel here, so I don't think we would include anything like this. Having said that, it's also possible I'm misunderstanding what you're trying to demonstrate. Regardless, let's stay focused on discussing the focus for this guide which is Spring Cloud Gateway.

Thank you very much for your reply. We have a scenario where a gateway is used by several systems at the same time. When logging in or out of a certain system, it is redirected back to the corresponding system address, so that the application experience will be better. Because some systems are relatively small, it seems unnecessary to establish a one-to-one BFF. Or is there an implementation plan that I don’t know about?

Otherwise, we can only create a method similar to a workbench or portal page to unify the login/logout address.

loren-coding avatar Sep 25 '24 02:09 loren-coding

@loren-coding it sounds like you are building something like (but perhaps not exactly) a multi-tenant gateway. Or at least a gateway with multiple frontend clients. I imagine this use case is quite common. Thank you for providing the code snippets, but I still feel this type of configuration is out of scope for this guide. I do appreciate the discussion and extra details though.

Because some systems are relatively small, it seems unnecessary to establish a one-to-one BFF. Or is there an implementation plan that I don’t know about?

There isn't an implementation plan that you are missing. If you are having challenges accomplishing this, please feel free to provide a minimal sample in a separate git repository or upload a project as an attachment, and I'd be happy to take a look.

Otherwise, we can only create a method similar to a workbench or portal page to unify the login/logout address.

You shouldn't have to do that unless desired. Instead, you can certainly implement a custom ServerLogoutSuccessHandler which computes a dynamic postLogoutRedirectUri for each client, possibly like what you have done in your code snippet above. I will caution that implementing custom redirection logic should be done with care to avoid accidentally exposing a vulnerability, such as open redirect.

sjohnr avatar Sep 26 '24 18:09 sjohnr