[JAVA] Multi-project with Keycloak

Introduction

In order to use microservices in multiple projects, I tried to rewrite the Keycloak realm for each request, but realized that it is not realistic to manage users for each realm, and the authentication / authorization that was done with Keycloak I decided to separate the authorization part.

What I tried

Since I implemented the backend microservices in Spring Boot, I added a filter to the Security Filter Chain of Spring to add new authorization information.

The theory is that if you register a member with the email address as the user's unique identifier for each project, you can identify the user with the email address of the Keycloak JWT token.

In use cases where the same user participates in multiple projects, it is more flexible to separate it from Keycloak because you want to manage authorization information for each project.

In addition, the place to add a new filter seems to be good after SessionManagementFilter.class.

Source code

public class CustomKeycloakFilter implements Filter {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final ProjectMemberService service;

    @Override
    public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
        logger.debug("######## CUSTOM KEYCLOAK FILTER INITIALIZED ########");
    }

    public CustomKeycloakFilter(ProjectMemberService service) {
        this.service = service;
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        
        logger.debug("######## CUSTOM KEYCLOAK FILTER ########");

        // extract project id from the request header
        String projectId = request.getHeader("project-id");
        logger.debug("++++++++ PROJECT ID: {} ++++++++", projectId);
        
        // extract Kycloak authentication token
        Principal principal = request.getUserPrincipal();
        if (principal != null && principal instanceof KeycloakAuthenticationToken) {
            
            logger.debug("++++++++ AUTHENTICATED BY KEYCLOAK ++++++++", principal.getClass().getName());

            KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) principal;
            KeycloakSecurityContext context = (KeycloakSecurityContext)token.getAccount().getKeycloakSecurityContext();
            AccessToken accessToken = context.getToken();
            
            // extract email from the access token as a unique user identifier.
            String email = accessToken.getEmail();

            logger.debug("++++++++ email: {} ++++++++", email);

            if (projectId != null && email != null) {
                
                // retrieve project user permissions from the project database by project id and user email 
                Collection<? extends GrantedAuthority> authorities = service.getAuthz(UUID.fromString(projectId), email);

                List<String> authorityStringList = new ArrayList<>();
                authorities.forEach(o -> authorityStringList.add(o.getAuthority()));
                logger.debug("++++++++ AUTHOTITIES: {} ++++++++", authorityStringList);

                // generates a new authentication token with reloaded user permissions
                KeycloakAuthenticationToken newAuthenticationToken = new KeycloakAuthenticationToken(token.getAccount(), false, authorities);
                
                logger.debug("++++++++ NEW AUTHENTICATION TOKEN: {} ++++++++", newAuthenticationToken.toString());
                
                SecurityContext sc = SecurityContextHolder.getContext();
                
                // replaces authentication token with the new one
                sc.setAuthentication(newAuthenticationToken);
            }
        }
        chain.doFilter(request, response);
    }
}

Summary

By separating the authorization part of Keycloak's authentication / authorization, it was possible to support multi-project (~~ multi-tenant ~~). Since authorization information can be managed for each project, it seems that the project can be operated with a high degree of freedom.

Reference material

Recommended Posts

Multi-project with Keycloak
Multi-tenant with Keycloak
Achieve modular monoliths with Gradle's multi-project
Keycloak setup
Configure a multi-project with subdirectories in Gradle
I wanted to gradle spring boot with multi-project