Keycloakでマルチプロジェクト

### はじめに
複数プロジェクトでマイクロサービスを利用するために、リクエストごとにKeycloakのレルムの書き換えを行ってみたが、レルムごとにユーザー管理するのは現実的ではないことに気付き、Keycloakで行っていた認証・認可の認可の部分を切り離すことにした。
### 試したこと
バックエンドのマイクロサービスをSpring Bootで実装していたので、SpringのSecurity Filter Chainに、新しい認可情報を付加するフィルターを追加してみた。

プロジェクトごとに電子メールアドレスをユーザーの一意識別子としてメンバーを登録すれば、KeycloakのJWTトークンの電子メールアドレスでユーザーの名寄せができるという理屈です。

同じユーザーが複数のプロジェクトに参加するユースケースでは、プロジェクトごとに認可情報を管理したいのでKeycloakから切り離した方が自由度が高くなります。

尚、新しいフィルターの追加場所は、SessionManagementFilter.classの後が良さそうです。
### ソースコード
```java
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);
    }
}

```
### まとめ
Keycloakの認証・認可の認可の部分を切り離すことにより、マルチプロジェクト（~~マルチテナント~~）に対応することができた。認可情報をプロジェクトごとに管理できるので、自由度の高いプロジェクト運用ができそうです。

### 参考資料
* [Keycloakでマルチテナント](https://qiita.com/ari_san/items/69ed86825e41f2ecb4e6)
* [業務システムにおけるロールベースアクセス制御](https://qiita.com/kawasima/items/8dd7eda743f2fdcad78e)
* [Jenkins : Project-Based Matrix Authorization Strategy](https://medium.com/@anusha.sharma3010/jenkins-project-based-matrix-authorization-strategy-277edba36112)