# PHP 애플리케이션 연동

Keycloak과 PHP 애플리케이션을 연동하면 OpenID Connect 또는 OAuth 2.0을 활용해 인증·인가 기능을 간편하게 구현할 수 있다. 이 장에서는 Keycloak에서 PHP 애플리케이션을 위한 클라이언트를 설정하고, PHP 코드 측에서 인증 흐름을 처리하는 기본 절차를 다룬다.

### Keycloak 클라이언트 설정

Keycloak에서 PHP 애플리케이션을 연동하려면, 먼저 Keycloak 관리자 콘솔에서 클라이언트를 생성해야 한다.

* 클라이언트 생성 시 프로토콜을 `openid-connect`로 설정한다.
* 클라이언트 ID는 PHP 애플리케이션에 대응되는 임의의 문자열을 사용한다.
* ‘공개(Open Access Type)’ 또는 ‘기밀(Confidential)’ 설정을 선택한다.
  * 웹 서버에서 클라이언트 시크릿(secret)을 안전하게 보관할 수 있다면 기밀(Confidential) 클라이언트를 사용할 수 있다.
* 유효한 리디렉션 URI(Valid redirect URIs)를 PHP 애플리케이션의 콜백 경로와 일치하도록 설정한다. 예를 들어, `https://example.com/callback` 형태로 지정한다.
* 로그인 흐름에 필요한 인증 방식을 선택한다. 일반적으로 OAuth 2.0 표준의 Authorization Code Flow를 사용한다.

설정 완료 후, 필요한 경우 클라이언트 시크릿도 확인한다. 이는 PHP 애플리케이션에서 토큰을 교환하거나 검증할 때 사용된다.

### PHP 라이브러리 선택

PHP 애플리케이션 측에서는 Keycloak을 직접 구현하기보다는 OpenID Connect 또는 OAuth 2.0 표준을 지원하는 라이브러리를 사용하는 것이 편리하다. 대표적으로 다음과 같은 라이브러리가 있다.

* `jumbojett/OpenID-Connect-PHP`
* `keycloak-php` (커뮤니티에서 관리하는 비공식 라이브러리)
* 기타 OAuth 2.0 호환 라이브러리

이 중 OpenID-Connect-PHP 라이브러리를 예시로 들어 설명한다.

### PHP 애플리케이션 구성

PHP 애플리케이션에서 Keycloak 인증을 적용하기 위해 다음 단계를 거친다.

#### 라이브러리 설치

Composer를 사용해 OpenID-Connect-PHP 라이브러리를 설치한다.

```
composer require jumbojett/openid-connect-php
```

#### 환경 설정

`.env` 파일이나 별도의 환경 설정 파일에 Keycloak 서버 정보 및 클라이언트 정보를 지정한다. 예시는 다음과 같다.

```
KEYCLOAK_BASE_URL=https://my-keycloak-server.example.com
KEYCLOAK_REALM=myrealm
KEYCLOAK_CLIENT_ID=my-php-client
KEYCLOAK_CLIENT_SECRET=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
KEYCLOAK_REDIRECT_URI=https://my-php-app.example.com/callback
```

#### 로그인 흐름 구현

아래 예시 코드는 OpenID-Connect-PHP 라이브러리를 사용해 Keycloak 로그인을 처리하는 간단한 방식을 보여준다.

```php
<?php
require_once 'vendor/autoload.php';

use Jumbojett\OpenIDConnectClient;

session_start();

$oidc = new OpenIDConnectClient(
    getenv('KEYCLOAK_BASE_URL') . '/realms/' . getenv('KEYCLOAK_REALM'),
    getenv('KEYCLOAK_CLIENT_ID'),
    getenv('KEYCLOAK_CLIENT_SECRET')
);

$oidc->setRedirectURL(getenv('KEYCLOAK_REDIRECT_URI'));
$oidc->addScope('openid profile email'); // 필요한 범위(scope) 설정

// 로그인 처리
if (!isset($_SESSION['user_logged_in'])) {
    // 사용자가 아직 로그인하지 않았다면 인증 요청
    $oidc->authenticate();
    
    // 인증 성공 후 액세스 토큰과 유저 정보 확인
    $accessToken =$oidc->getAccessToken();
    $userInfo =$oidc->requestUserInfo();
    
    // 세션에 로그인 상태 저장
    $_SESSION['user_logged_in'] = true;
    $_SESSION['user_email'] =$userInfo->email;
    $_SESSION['access_token'] =$accessToken;
}

// 이미 로그인된 상태라면 이후 비즈니스 로직 처리
echo "안녕하라, " . $_SESSION['user_email'] . "님!";
```

1. `OpenIDConnectClient` 객체를 생성할 때 Keycloak 서버 및 클라이언트 정보를 설정한다.
2. 사용자 인증이 필요하면 `authenticate()` 메서드를 통해 Keycloak 로그인 페이지로 리다이렉트한다.
3. 사용자가 성공적으로 로그인하면, 콜백 URL에서 액세스 토큰을 획득하고 사용자 정보를 가져온다.
4. 세션에 사용자 상태를 저장한 뒤, 애플리케이션 내부에서 인증된 사용자에게 서비스를 제공한다.

#### 콜백 처리

위 예시에서는 `authenticate()` 내부적으로 자동으로 콜백을 처리하지만, 상황에 따라 콜백 경로를 별도로 구현해야 할 수도 있다. 그런 경우, 콜백 PHP 파일에서 다음과 같은 로직을 추가한다.

```php
<?php
require_once 'vendor/autoload.php';

use Jumbojett\OpenIDConnectClient;

session_start();

$oidc = new OpenIDConnectClient(
    getenv('KEYCLOAK_BASE_URL') . '/realms/' . getenv('KEYCLOAK_REALM'),
    getenv('KEYCLOAK_CLIENT_ID'),
    getenv('KEYCLOAK_CLIENT_SECRET')
);

$oidc->setRedirectURL(getenv('KEYCLOAK_REDIRECT_URI'));
$oidc->addScope('openid profile email');

// Keycloak에서 콜백이 들어왔을 때 토큰 교환
$oidc->authenticate();

// 사용자 정보를 가져와 세션에 저장
$userInfo =$oidc->requestUserInfo();
$_SESSION['user_logged_in'] = true;
$_SESSION['user_email'] =$userInfo->email;
$_SESSION['access_token'] =$oidc->getAccessToken();

// 이후 리다이렉트 혹은 다음 로직 처리
header('Location: /');
exit();
```

### 토큰 검증

Keycloak에서 발급된 ID 토큰이나 액세스 토큰을 서버 측에서 검증하려면 다음 사항을 확인한다.

* 토큰에 서명된 키가 Keycloak 서버의 공개 키(Realm Public Key)와 일치하는지 확인한다.
* 토큰의 `iss`, `aud`, `exp` 등 표준 클레임이 정상적인 값인지 확인한다.
* 필요에 따라 토큰의 특정 클레임(예: roles)을 점검해 접근 권한을 제어한다.

오픈소스 라이브러리를 사용하면 이러한 검증 로직이 대부분 자동 처리된다. 하지만 민감 정보 보호나 보안 검증을 위해, 내부적으로 어떠한 검증이 이뤄지는지 파악해 두는 것이 중요하다.

### 역할(Role) 기반 접근 제어

Keycloak에서 사용자를 특정 역할(Role)에 할당한 뒤, PHP 애플리케이션은 토큰의 `realm_access` 클레임 내에 있는 역할 정보를 읽어 접근을 제어할 수 있다. 예시로, 관리자 권한이 필요한 페이지라면 다음과 같은 방식으로 검사한다.

```php
if (!isset($_SESSION['access_token'])) {
    // 로그인이 필요
    header('Location: /login.php');
    exit();
}

$decoded = json_decode(base64_decode(explode('.',$_SESSION['access_token'])[1]), true);

if (!in_array('admin', $decoded['realm_access']['roles'])) {
    // 권한 없음
    http_response_code(403);
    echo "권한이 없다.";
    exit();
}

// 관리자 권한이 있는 경우에만 이후 로직 실행
echo "관리자 페이지에 접근하였다.";
```

이런 식으로 토큰의 페이로드를 확인해 사용자가 적절한 역할을 보유하고 있는지 검증한 뒤, 각 역할별 리소스 접근을 제한할 수 있다.

### 세션 및 자동 로그아웃 관리

Keycloak 세션은 Keycloak 서버가 관리하고, PHP 세션은 PHP 애플리케이션에서 관리한다. Keycloak에서 로그아웃이 일어났을 때 애플리케이션에도 이를 반영하려면 백채널 로그아웃(Backchannel Logout)이나 프론트채널 로그아웃(Frontchannel Logout)을 설정해야 한다.

단순히 서버 측 세션을 종료하는 방식을 쓰려면, 로그아웃 요청 시 Keycloak에 세션 종료를 알리고, PHP 애플리케이션 세션도 함께 파기한다. 예시로, 로그아웃 동작을 처리하는 코드는 다음과 같다.

```php
<?php
session_start();
session_destroy();

$keycloakLogoutUrl = getenv('KEYCLOAK_BASE_URL')
    . '/realms/' . getenv('KEYCLOAK_REALM')
    . '/protocol/openid-connect/logout'
    . '?redirect_uri=' . urlencode('https://my-php-app.example.com');

header('Location: ' . $keycloakLogoutUrl);
exit();
```

### 정리

PHP 애플리케이션과 Keycloak을 연동하면 OpenID Connect, OAuth 2.0 표준에 기반한 안전하고 확장성 있는 인증 인프라를 갖출 수 있다. Keycloak 관리자 콘솔에서 설정한 클라이언트 정보를 PHP 라이브러리에 전달하고, 로그인·콜백·로그아웃 흐름을 코드에 반영하면 전반적인 로그인 프로세스를 쉽게 구성할 수 있다. 토큰 검증과 역할 기반 접근 제어 같은 추가 보안 설정을 통해, PHP 애플리케이션 전반의 보안 수준을 크게 높일 수 있다.
