I am trying to connect a Vaadin frontend to a backend API with Keycloak as a centralized authorization server.
Currently I have an API-Service written in FastAPI which can be used to submit data and receive the results of an analysis for it after the data has been processed. The results are stored in different database schemas per tenant/usergroup. This API should be used to integrate the service in many ways, e.g other applications.
Now I want to provide a user interface with Vaadin so that my APIs functionality is easier to use for non-technical users. I would like to allow a login to the application with the credentials stored in Keycloak. I have prior experience with Vaadin from another plain Java application, but I'm new to using Spring, Spring Security and OAuth2/Keycloak.
I think the flow should be:
I managed to implement the login for the user following these blogs: https://martinelli.ch/vaadin-oauth2-and-keycloak/ https://rene-wilby.de/blog/hilla-oauth-authorization-code-flow-keycloak/
@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends VaadinWebSecurity {
private final KeycloakLogoutHandler keycloakLogoutHandler;
public SecurityConfiguration(KeycloakLogoutHandler keycloakLogoutHandler) {
this.keycloakLogoutHandler = keycloakLogoutHandler;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(
authorize -> authorize.requestMatchers(new AntPathRequestMatcher("/images/*.png")).permitAll());
// Icons from the line-awesome addon
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers(new AntPathRequestMatcher("/line-awesome/**/*.svg")).permitAll());
http.oauth2Login(Customizer.withDefaults()).logout( logout ->
logout.addLogoutHandler(keycloakLogoutHandler)
);
super.configure(http);
setOAuth2LoginPage(http, "/oauth2/authorization/keycloak");
}
@Bean
public GrantedAuthoritiesMapper userAuthoritiesMapperForKeycloak() {
return authorities -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
var authority = authorities.iterator().next();
if (authority instanceof OidcUserAuthority oidcUserAuthority) {
var userInfo = oidcUserAuthority.getUserInfo();
if (userInfo.hasClaim("realm_access")) {
var realmAccess = userInfo.getClaimAsMap("realm_access");
var roles = (Collection<String>) realmAccess.get("roles");
mappedAuthorities.addAll(roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
.toList());
}
}
return mappedAuthorities;
};
}
}
I receive an OicdUser and am able to access it's information. However, the OicdUser only contains an ID-Token, which is not what should be sent to the API (from my understanding).
How can I receive the access-token and use it to make a request to the backend API? Is that a proper approach to the problem or do I have some misunderstandings?
Thanks in advance!
You can get access tokens from the OAuth2AuthorizedClientManager
bean.
To query a resource server, you'll need a REST client, probably one of RestClient
, WebClient
, RestTemplate
or @FeignClient
(the last two being in maintenance mode and should probably be avoided in new integrations). Each has its own instructions for integrating with authorized clients, refer to the docs of the one you choose.
This starter I just wrote provides with some auto-configuration for OAuth2 authorization with RestClient
and WebClient
. You can give it a try.