본문 바로가기

기타/Keycloak

react-keycloak, js-adapter

react 로 이뤄진 페이지에서 js-adapter 를 사용하여 keycloak 로그인을 진행하는 과정에 대해 정리하고자 한다.

구현하려는 그림

구현하고자 하는 로그인 flow 는 아래와 같다.

각 단계가 구체적으로 어떻게 진행되는지는 js-adapter 에 관해 먼저 알아보고 설명할 것이다.

사용할 라이브러리들

keycloak-js adapter

javascript 용 keycloak adapter 이다.

keycloak 을 이용한 로그인, token 갱신 등 작업들을 해준다.

frontend 단에 적용하는 adapter 이기 때문에, token 교환이 frontend 에서 발생한다.

github.com/keycloak/keycloak-documentation/blob/master/securing_apps/topics/oidc/javascript-adapter.adoc

react-keycloak/web

js-adapter 를 react 에서 쓰기 쉽게 wrapping 해 놓은 것이다.
자세한 사용법은 아래 guide 에 자세히 나와있다.

www.npmjs.com/package/@react-keycloak/web

로그인 구현

위에서 말한 flow 를 구현하기 위해 keycloak 설정부분부터 살펴보자.

1. keycloak 서버 페이지 설정

client access type 은 public 으로 해준다.

인증과 토큰 교환을 브라우저단에서 진행하기 때문이다.

backend 서버에서 token 교환을 진행할 거라면 confidentail/barier 타입을 선택해야 한다.

redirect uri/cors 는 * 로 설정해준다. (어떤 주소의 url 에서든 로그인 진행하는것을 허용하게 된다.)

특정 주소만 허용해주고 싶으면 * 대신 주소를 적어줘야 한다.

2. react-keycloak 설정

react-keycloak 사용법은 간단하다. ReactKeycloakProvider 로 최상위에서 감싸주고, keycloak-js optoin 들을 넣어주면 된다.

import React from 'react';
import ReactDOM from 'react-dom';
import { ReactKeycloakProvider } from '@react-keycloak/web';
// 따로 정의한 파일이다. keycloak instance, option 들을 가져온다.
import keycloak, { initOptions, onKeycloakEvent } from './keycloak/keycloak';

import App from './view/app';

ReactDOM.render(
  <React.StrictMode>
    <ReactKeycloakProvider
      authClient={keycloak}
      initOptions={initOptions}
      onEvent={onKeycloakEvent}
    >
        <App/>
    </ReactKeycloakProvider>
  </React.StrictMode>,
  document.getElementById('root'),
);

authClient 에는 keycloak-js Instance 를, initOptions 에는 keycloak 옵션들을 넣어주면 된다.
위의 예시에서는 keycloak 옵션들을 정의해놓은 keycloak.ts 파일을 따로 만들어 두었다.

react-keycloak 문서에는 react-redux 같은 다른 provider 들도 ReactKeycloakProvider 내부에 정의하는 것을 권장하고 있다.

N.B. If your using other providers (such as react-redux) it is recommended to place them inside ReactKeycloakProvider.

아래가 따로 정의한 keycloak.ts 파일의 내용이다. typescript 를 사용했다.

import Keycloak from 'keycloak-js';
const KEYCLOAK_REALM_NAME = 'Test' // keycloak realm 
const KEYCLOAK_CLIENT_ID = 'my-client'; // 사용할 client 
const KEYCLOAK_URL = 'http://ip/auth/'; // keycloak url

// 위에 설정한 realm, client_id, url 로 keycloak instance 를 생성한다.
const keycloak: Keycloak.KeycloakInstance = Keycloak({
  realm: KEYCLOAK_REALM_NAME,
  clientId: KEYCLOAK_CLIENT_ID,
  url: KEYCLOAK_URL
});

// initOption 지정
export const initOptions = {
  onLoad: 'login-required',
  checkLoginIframe: false,
};

// keycloak Event 를 보기 위한 함수 정의
// keycloak provider 의 onEvent 에 넣어준다.
export const onKeycloakEvent = (event: any, error: any) => {
  console.log('keycloak event ', event, error);
  switch (event) {
    case 'onAuthLogout':
      keycloak.logout();
      break;
    case 'onAuthRefreshError':
      keycloak.logout();
      break;
    case 'onAuthRefreshSuccess':
         console.log('auth token:  ' + keycloak.token);
      console.log('refresh token:  ' + keycloak.refreshToken);
      break;
    default:
      break;
  }
};

export default keycloak;

initOptions

keycloak.init() 함수의 인자로 들어가게 된다.
keycloak 로그인 방식에 대한 설정을 지정할 수 있다.

onLoad

  • login-reuqired : 프론트 처음 접속시 로그인이 되어 있지 않으면 바로 keycloak 로그인 페이지를 띄워준다.
  • check-sso : 사용자가 로그인 되어져 있지 않으면 프론트 페이지로 redirect 시켜준다. 프론트는 그냥 로그인되지 않은 채로 남아있게 된다.

checkLoginIframe

login 되어져 있는지 자동으로 계속 모니터링 해준다. default 가 true 다.
true 로 해주면 keycloak url 의 iframe 이 생성된다.

checkLoginIframeInterval

login status 모니터링 하는 주기를 설정해준다. default 로 5초마다 검사한다.

이외에도 다른 옵션들이 존재한다. 다른 옵션들은 keycloak-js 문서를 참고하자.

useKeycloak

react-keycloak 이 제공한 hook 이다.
아래처럼 keycloak instance 를 가져오거나 초기화 여부를 알 수 있게 해준다.

import React, { useEffect } from 'react';
import { useKeycloak } from '@react-keycloak/web';

export default function App() {
  const { keycloak, initialized } = useKeycloak();

  // 아직 초기화 되지 않았으면 렌더링 하지 않는다.
  //  react-keycloak 의 LoadingComponent 를 이용해도 될 듯 싶다.
  if (!initialized) {
    return <></>;
  }

  return (
    <div>
      <div>{`User is ${
        !keycloak.authenticated ? 'NOT ' : ''
      }authenticated`}</div>

      // usekeycloak 에서 authenticated 는 idToken 과 token 여부로 결정된다.
      {!!keycloak.authenticated && (
        <button type="button" onClick={() => keycloak.logout()}>
          Logout
        </button>
      )}
    </div>
}

userKeycloak 의 실제 구현은 다음과 같다.
구현체를 보면 알수 있듯이 react context 를 사용하고 있다. context 에서 초기화 상태와 keycloak instance 를 가져온다.

import { useContext } from 'react';
import { reactKeycloakWebContext } from './context';
export function useKeycloak() {
    var ctx = useContext(reactKeycloakWebContext);
    if (!ctx) {
        throw new Error('useKeycloak hook must be used inside ReactKeycloakProvider context');
    }
    if (!ctx.authClient) {
        throw new Error('authClient has not been assigned to ReactKeycloakProvider');
    }
    var authClient = ctx.authClient, initialized = ctx.initialized;
    return {
        initialized: initialized,
        keycloak: authClient,
    };
}

react keylcoak 에서는 keycloak instance 의 event 에 updateState 함수를 달아놓는다.
updateState 함수는 keyclok instance 가 초기화 되어있는지, 사용자가 인증되었는지를 판단해서 initliazed, authenticated 등의 값을 결정한다.

/* react-keycloak KeycloakProvider core 일부분*/

// mount 됐을 때, init 함수를 호출한다.
KeycloakProvider.prototype.componentDidMount = function () {
     this.init();
};

KeycloakProvider.prototype.init = function () {
            var _a = this.props, initOptions = _a.initOptions, authClient = _a.authClient;
            // Attach Keycloak listeners
            authClient.onReady = this.updateState('onReady');
            authClient.onAuthSuccess = this.updateState('onAuthSuccess');
            authClient.onAuthError = this.onError('onAuthError');
            authClient.onAuthRefreshSuccess = this.updateState('onAuthRefreshSuccess');
            authClient.onAuthRefreshError = this.onError('onAuthRefreshError');
            authClient.onAuthLogout = this.updateState('onAuthLogout');
            authClient.onTokenExpired = this.refreshToken('onTokenExpired');

            // keycloak-js 의 init 함수를 호출한다. 이때 initOptions 를 인자로 넘긴다.
            // 여기서 실제 keycloak-js 의 초기화 과정이 발생한다.
            authClient
                .init(__assign(__assign({}, defaultInitOptions), initOptions))
                .catch(this.onError('onInitError'));
 };

실제 Flow

1. Login 페이지 이동

onLoad 옵션을 'login-required' 로 설정해놓은 상태라면, 로그인 되어 있지 않으면 자동으로 keycloak 로그인 페이지로 이동한다.
check-sso 설정시에는 kecylok.login() 을 호출해줘야 한다.

로그인 페이지로 이동하면 url 의 pramaeter로 realm, client-id, 로그인 성공후 되돌아갈 주소인 redirect-uri 이 있는 것을 볼 수 있다.
redirect-uri 는 keycloak 설정시 옵션에 넣어줄 수 도 있고, 넣어주지 않았다면 default 로 로그인을 요청할 당시의 url 주소로 설정된다.

2. Lgoin 시도

Login 페이지에서 id/pw 입력후 로그인 버튼을 누르면 이는 keycloak 서버로 전송된다.

3. Frontend 로 redirect

keycloak 에서 valid 하다고 판단하면 redirect-uri 에 적어준 주소로 다시 이동한다.
(브라우저가 다시 frontend 페이지로 redirect 된다.)

이 때, code 방식의 token 교환방식을 설정했다면 url 뒤에 code 가 붙여서 온다.
이를 keycloak-js 가 init() 함수를 통해 초기화 할 때 url 을 파싱해 code 를 가져온다.

4. token 요청

post call 로 token 을 요청한다.

5. token 저장.

'기타 > Keycloak' 카테고리의 다른 글

keycloak js adapter 사용법 정리  (0) 2020.11.14