# Node.js(Express) 연동

Keycloak은 Node.js 환경에서도 손쉽게 통합할 수 있도록 공식 Node.js 어댑터를 제공한다. 여기서는 Express 프레임워크를 사용하여 Keycloak과 연동하는 과정을 단계별로 살펴본다.

### 개요

* Node.js Express 애플리케이션에서 Keycloak 인증·인가를 처리하기 위한 방법
* Keycloak에서의 클라이언트 설정
* keycloak-connect를 이용한 Express 미들웨어 구성

### 사전 준비

* Keycloak 서버 접근 권한 및 Realm 관리 권한
* Node.js가 설치되어 있고, Express 애플리케이션 프로젝트가 생성되어 있어야 한다
* npm 또는 Yarn 같은 패키지 매니저 사용 가능

### Keycloak에서 클라이언트 설정

1. Keycloak에 관리자 계정으로 접속한 뒤, 연동하고자 하는 Realm을 선택한다
2. Clients 메뉴로 이동해 새로운 클라이언트를 생성한다
3. Client 설정 시, 다음 사항을 확인하거나 설정한다
   * **Client Protocol**: `openid-connect`
   * **Client Type**: Public 혹은 Confidential (서버 환경에 따라 선택)
   * **Redirect URIs**: Express 애플리케이션이 인증 후 돌아올 URI (예: `http://localhost:3000/*`)
   * **Valid Redirect URIs**: `*` 대신 정확한 경로 지정 권장 (예: `http://localhost:3000/login/callback`)
4. **Access Type**을 Confidential로 설정한 경우, **Credentials** 탭에서 **Secret** 값을 확인해두어야 한다

### Node.js(Express) 프로젝트에 keycloak-connect 설치

프로젝트에서 keycloak-connect를 사용하기 위해 필요한 패키지를 설치한다. 세션 관리를 위해 express-session도 함께 사용한다.

```bash
npm install keycloak-connect express-session
```

또는

```bash
yarn add keycloak-connect express-session
```

### Express 애플리케이션 설정

Keycloak 연동을 위해서는 Express 애플리케이션에 세션 설정과 Keycloak 미들웨어를 적용해야 한다.

#### 세션 설정

Express에서 세션을 활성화하기 위해 express-session 모듈을 불러오고, 비밀키(secret) 등을 설정한다.

```js
const session = require('express-session');

// 세션 스토어 설정은 실제로는 Redis 등 외부 스토어 사용 권장
app.use(session({
  secret: 'mySecret',
  resave: false,
  saveUninitialized: true
}));
```

* secret: 세션 암호화에 사용되는 문자열
* resave: 세션이 변경되지 않아도 다시 저장할지 여부
* saveUninitialized: 초기화되지 않은 세션도 저장할지 여부

#### Keycloak 미들웨어 초기화

keycloak-connect에서 Keycloak 객체를 생성하고, Express에 미들웨어를 등록한다.

```js
const Keycloak = require('keycloak-connect');

const memoryStore = new session.MemoryStore();
app.use(session({
  secret: 'mySecret',
  resave: false,
  saveUninitialized: true,
  store: memoryStore
}));

// Keycloak 구성 옵션
const keycloakConfig = {
  "realm": "example-realm",
  "auth-server-url": "http://localhost:8080/auth",
  "ssl-required": "none",
  "resource": "example-client", // Keycloak에서 설정한 Client ID
  "public-client": true,        // public-client=true or confidential client는 false
  "confidential-port": 0
};

// keycloak-connect 초기화
const keycloak = new Keycloak({ store: memoryStore }, keycloakConfig);

// Keycloak 미들웨어 등록
app.use(keycloak.middleware());
```

* memoryStore: 세션 관리에 쓰일 메모리 스토어, 운영 환경에서는 외부 세션 스토어 사용 권장
* keycloakConfig: Keycloak에서 발급받은 정보를 JSON 형태로 설정
  * realm: 연동하려는 Realm 이름
  * auth-server-url: Keycloak 서버 접근 경로
  * resource: Keycloak에서 생성한 Client ID
  * public-client: 공개 클라이언트 여부 (Confidential 설정 시 false로 설정 후 client secret을 추가로 전달)

#### 보호가 필요한 라우트 설정

Keycloak 미들웨어를 사용해 특정 라우트에 접근 제어를 적용할 수 있다.

```js
// 인증이 필요한 라우트
app.get('/secured', keycloak.protect(), (req, res) => {
  res.send('이 페이지는 인증된 사용자만 접근 가능한다.');
});

// 역할 기반 보호
app.get('/admin', keycloak.protect('realm:admin'), (req, res) => {
  res.send('이 페이지는 admin 역할이 있는 사용자만 접근 가능한다.');
});
```

* `keycloak.protect()`: 사용자 인증이 필요함을 의미
* `keycloak.protect('realm:admin')`: Realm 레벨의 admin 역할을 가진 사용자만 접근 가능

#### 로그아웃 처리

Keycloak에서는 세션 종료 시 Keycloak 측 로그아웃 URL에 리다이렉트하여 안전하게 세션을 종료할 수 있다.

```js
app.get('/logout', (req, res) => {
  req.logout(); // Express 세션에서 사용자 정보 제거
  res.redirect(keycloak.logoutUrl({ redirectUri: 'http://localhost:3000' }));
});
```

* `req.logout()`: 세션에서 사용자 정보 제거
* `keycloak.logoutUrl({ redirectUri })`: Keycloak 로그아웃 처리 후 돌아올 페이지 설정

### 전체 예시 코드

아래는 간단한 예시 구조를 담은 코드다. 실제 운영 환경에서는 에러 처리, HTTPS 설정, 세션 스토어 교체 등이 추가적으로 필요하다.

```js
const express = require('express');
const session = require('express-session');
const Keycloak = require('keycloak-connect');

const app = express();
const memoryStore = new session.MemoryStore();

app.use(session({
  secret: 'mySecret',
  resave: false,
  saveUninitialized: true,
  store: memoryStore
}));

const keycloakConfig = {
  "realm": "example-realm",
  "auth-server-url": "http://localhost:8080/auth",
  "ssl-required": "none",
  "resource": "example-client",
  "public-client": true,
  "confidential-port": 0
};

const keycloak = new Keycloak({ store: memoryStore }, keycloakConfig);
app.use(keycloak.middleware());

app.get('/', (req, res) => {
  res.send('홈 페이지');
});

app.get('/secured', keycloak.protect(), (req, res) => {
  res.send('이 페이지는 인증된 사용자만 접근 가능');
});

app.get('/admin', keycloak.protect('realm:admin'), (req, res) => {
  res.send('admin 역할을 가진 사용자만 접근 가능');
});

app.get('/logout', (req, res) => {
  req.logout();
  res.redirect(keycloak.logoutUrl({ redirectUri: 'http://localhost:3000' }));
});

app.listen(3000, () => {
  console.log('서버가 http://localhost:3000 에서 동작 중이다.');
});
```

### 주의 사항

* **보안**: 실제 서비스 환경에서는 HTTPS 적용, 안전한 세션 스토어(Redis, MongoDB 등) 활용, CSRF 대응이 필수다
* **역할(Role) 관리**: Keycloak에서 역할별 접근 제어를 미리 설계하고, 필요한 API마다 접근 권한을 세분화한다
* **토큰 갱신(Refresh Token)**: 클라이언트에서 토큰 갱신 시점을 주기적으로 확인하거나, session을 통해 갱신하도록 구성한다
* **Keycloak 서버 장애 대비**: Keycloak 연동 시 서버가 다운되면 인증 로직도 영향을 받으므로, HA 환경이나 캐싱 메커니즘 고려가 필요하다

위 과정을 통해 Keycloak과 Node.js(Express) 애플리케이션을 통합할 수 있다. 인증과 역할 기반 인가를 효율적으로 적용하려면 Keycloak과 Express 각각에서 올바른 설정과 보안 구성을 유지해야 한다.
