(React) Keycloakify 로 keycloak theme 개발하기 (2)

cleopatra·2022년 2월 14일
0

keycloakify를 써보자

목록 보기
2/3
post-custom-banner

📋 오늘의 목표

✔ keycloakify 설치하기
✔ 로그인 화면 커스텀 하기
✔ 회원 가입 화면 커스텀 하기

이 시리즈에서 개발한 키클락 앱 코드는 깃헙에 올려두었다.
👉 https://github.com/cindy-choi/keycloak-sample-ts

완성된 코드 말고 처음부터 따라하고 싶은 사람은 아래에 나오는 보일러플레이트를 사용해도 된다.

프로젝트 구조 설명

다음은 keycloakify 의 메인 페이지에서 확인 할 수 있는 gif 이다.

gif를 보면 Onyxia 라는 이름의 검은색 테마의 페이지들이 있고,
쌩뚱맞은 파란색 테마의 로그인 페이지가 보이는데

검은 페이지들이 일반 서비스 UI 이고, 테마가 맞지 않은 파란 페이지들이 Keycloak UI이다.

잘 보면 keycloak UI 에 진입했을 때 URL이 기존과는 다르게 몹시 긴 것을 볼 수 있다. 완전히 다른 두 개의 UI를 개발해야 하는 것이다.


이 시리즈에서 진행하는 프로젝트는 하나의 repository에 서비스 UI 와 Keycloak UI 코드를 한 번에 관리&빌드 할 수 있도록 구성했다.

시작하자

시작하기 전에, 개발 하기 전에 세팅해놓는 것을 좋아해서 미리 만들어둔 boilerplate 를 사용했다.

샘플 코드를 만드는 사람이라면 이 보일러플레이트를 클론 받아서 같이 시작하면 되겠다.

👉 https://github.com/cindy-choi/cindy-boilerplate-ts

git clone https://github.com/cindy-choi/cindy-boilerplate-ts.git


1. keycloakify 설치하기

다음의 명령어로 keycloakify 와 친구들을 설치한다.

npm install --save-dev keycloakify @emotion/react tss-react powerhooks

2. keyclok context 사용하기

keycloak 관련 전역 변수 등을 관리하기 위해 매니저 파일을 만든다.
파일 명과 경로는 마음대로..

@/utils/keycloakManager.ts

import { getKcContext } from 'keycloakify';
 
export const { kcContext } = getKcContext<{
  pageId: 'login.ftl',
}>({ 
  mockData: [
    {
      pageId: 'login.ftl',
    },
  ],
});
 
const keycloakManager = {
  kcContext,
};
 
export type KcContextType = NonNullable<typeof kcContext>;
export default keycloakManager;

keycloakify 로 UI를 빌드할 때 kcContext 값을 사용해서

  • 어떤 페이지를 빌드 할 건지
  • 빌드 디버깅 옵션을 사용할 것인지
  • 빌드 시 mockData를 적용할 건지, 적용한다면 어떤 값인지

등등을 결정한다.

3. 로그인 페이지 개발하기

이렇게 생긴 로그인 페이지를... 만들어보자.
프론트엔드 개발자 타이틀 달고 퀄리티가 말이 아니다.
아무튼...


keycloak 관련 페이지는 keycloak 경로에 다 묶어두었다.
서비스 UI 랑 같이 하나의 repo 에서 관리하려면 이 방법이 편하다.

@/pages/keycloak/Login.tsx

import React, { useState, useRef, memo, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import { Button, TextField } from '@mui/material';
 
import type { KcProps } from 'keycloakify/lib/components/KcProps';
import type { KcContextType } from '@/utils/keycloakManager';
 
type KcContext_Login = Extract<KcContextType, { pageId: 'login.ftl' }>;
 
const StyledLogin = styled.div`
  min-width: 100vw;
  min-height: 100vh;
  background-image: url(${bg});
  background-size: cover;
  background-repeat: no-repeat;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
`;
 
const LoginForm = styled.form`
  width: 25rem;
  height: 15rem;
  background-color: white;
  border-radius: 5px;
  box-shadow: 2px 2px 8px 0px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
`;
 
const LoginInput = styled(TextField)`
  width: 20rem;
  margin-bottom: 8px !important;
`;
 
const LoginButton = styled(Button)`
  width: 20rem;
`;
 
export const Login = memo(({ kcContext, ...props }: { kcContext: KcContext_Login } & KcProps) => {
    const { t } = useTranslation();
    const form = useRef<HTMLFormElement>(null);
    const { social, url, message, realm, } = kcContext;
    const isSessionOut = message?.summary.includes('attempt timed out') || message?.summary.includes('Timeout');
 
    console.log(kcContext);
 
    const handleSubmit = () => {
      form?.current?.submit();
    };
 
    return (
      <StyledLogin>
        <LoginForm ref={form} method="post" action={url.loginAction}>
          <LoginInput
            id="username"
            name="username"
            size="small"
            label={t('id')}
          />
          <LoginInput
            label={t('password')}
            id="password"
            name="password"
            type="password"
            size="small"
          />
          <LoginButton variant="contained" onClick={() => handleSubmit()}>{ t('login') }</LoginButton>
        </LoginForm>
      </StyledLogin>
    );
  },
);
 
export default Login;

벨로그에서는 line number 기능이 없군요

하여간 코드를 보면 알 수 있듯, 기존 React로 개발하던 방식 그대로 로그인 페이지를 만들 수 있다. no more ftl!

keycloak 서버가 UI를 렌더링 할 때 전달하는 변수들은 모두 kcContext 객체 안에 들어있다.

예를 들어 위 코드에서 다국어 지원을 i18next 로 하고 있지만, 서버에서 atrribute 로 전달하는 것을 사용해도 무방할 것이다. 난 안해봄. 잘되면 알려주세요.

4. 로그인 페이지 사용하기

로그인 페이지를 만들었으면, 이제 keycloakify 가 이걸 빌드하도록 설정 해주어야한다.

프로젝트의 root 디렉토리에 KeycloakApp.tsx 파일을 만들자.


@/KeycloakApp.tsx

import { memo } from 'react';
import { defaultKcProps } from 'keycloakify';
import { useTranslation } from 'react-i18next';
import type { KcContextType } from '@/utils/keycloakManager';
 
import Login from '@/pages/keycloak/Login';
// import Error404 from '@/pages/common/Error404';
 
import './KeycloakApp.scss';
 
export const KeycloakApp = memo(({ kcContext }: { kcContext: KcContextType; }) => {
  const { t } = useTranslation();
 
  console.log(kcContext);
 
  switch (kcContext.pageId) {
    case 'login.ftl':
      return <Login {...{ kcContext, ...defaultKcProps }} />;
 
    default:
      return undefined;
  }
});
 
export default KeycloakApp;

switch를 보면 서버에서 넘겨주는 kcContext의 pageId 에 따라 어떤 페이지를 렌더링 할지를 결정하는 것을 알 수 있다.

추후 추가로 회원 가입 페이지나, 404 에러 페이지를 개발하게 될 경우 swtich case 에 추가해주면 된다.

그리고 이 KeycloakApp.tsx 를 사용하도록 설정해주자.

@/index.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import reportWebVitals from './reportWebVitals';
 
// service ui
import App from './App';
// keycloak ui
import KeycloakApp from '@/KeycloakApp';
import { kcContext } from '@/utils/keycloakManager';
 
 
/**
 * keycloak 서버에서 제공할 때에만 kcContext 값이 활성화되며, 기본적으로는 App을 렌더링합니다.
 */
ReactDOM.render(
  <React.StrictMode>
    {
      // kcContext가 존재하면 키클락 App 을 렌더링하고, 그렇지 않으면 App을 렌더링합니다.
      kcContext !== undefined ? (
        <KeycloakApp kcContext={kcContext} />
      ) : (
        <App />
      )
    }
  </React.StrictMode>,
  document.getElementById('root'),
);
 
reportWebVitals();

App.tsx 와 KeycloakApp 을 상황에 따라 다르게 사용하는 것을 볼 수 있다.

구체적으로 말하자면 이 다음에 나오는 npm run keycloak 명령어를 때리면 keycloakify 로 우리가 개발한 웹 페이지를 빌드하는데, 이 때 kcContext가 전달된다고 보면 된다.

개발된 페이지를 keycloak theme로 내보내기

그냥 만들면 짠하고 keycloak UI 가 spa 로 동작하면 얼마나 좋을까.
이제 실제로 keycloakify 를 사용해서 키클락 theme 파일을 만들어보자.



우선 빌드 명령어를 추가해주어야 한다.

package.json

  "scripts": {
    "keycloak": "craco build && build-keycloak-theme",
    "start": "craco start",
    "build": "craco build",
    "test": "craco test",
    "eject": "react-scripts eject"
  },

맨 위에 있는 keycloak 을 추가해주면 된다.
나는 craco 를 쓰는데 npm을 사용중이라면 다음과 같이 변경하면 된다.

"keycloak": "npm build && build-keycloak-theme"

그리고 명령어를 때려보자.
우선 npm build 때린 것 처럼 빌드를 먼저 하고 진행하기 때문에 제법 오래 걸린다.

이렇게 긴~ 과정을 끝내면

프로젝트의 root 경로에 build_keycloak 이라는 경로가 생성되고
그 안에 keycloak 적용에 필요한 것들이 모두 포함되어 나온다.

그 중 우리에게 필요한 건 theme 폴더!
build_keycloak/src/main/resources/theme/ 경로 안에 있는 keycloakify-sampe 경로를 확인하자.

참고로 이 폴더의 이름은 package.json 에 명시된 프로젝트의 name 을 따라간다.

keycloakify-sample 폴더를 키클락이 설치된 곳에 복사해두면 끝!
다른 jar 파일들이 많이 생기는데, 내 경우 혹시 몰라서 jar 파일만 서버에 적용 해주었고 나머지는 손대지 않았지만 잘 동작했다.

여기까지 keycloakify 로 UI 빌드까지 끝냈다.
이제 로컬에 키클락을 띄우고 로그인이 필요한 페이지에서 리디렉션을 걸어서 실제 UI랑 연동을.. 내일 해보자.


profile
안녕나는클레오파트라세상에서제일가는포테이토칩
post-custom-banner

2개의 댓글

comment-user-thumbnail
2022년 3월 1일

안녕하세요 이 글 키클락 페이스북 한국 사용자 모임에 올려도 될까요?

1개의 답글