signon_v2는 기존 v1.5 인증 서비스를 개선한 중앙 인증 서비스입니다. 대상 사이트에 Works 본인인증 버튼을 붙이는 역할은 동일하지만, 표준 OAuth 2.0 방식을 채택해 구조가 더 명확하고 보안이 강화됐습니다.
대상 사이트는 인증 화면과 Works 메시지 발송을 직접 만들지 않습니다. 사이트 담당자는 등록 정보를 IT팀에 요청하고, IT팀이 발급한 값을 사용해 버튼과 callback만 구현하면 됩니다.
client_secret 기반 서버 간 인증, state 파라미터로 CSRF 방지, 인증 코드 2분 유효·1회 사용 제한이 적용됩니다.allowed_users로 사이트마다 허용 사용자를 지정할 수 있습니다.실제 사이트에 붙이기 전 v2 흐름을 테스트할 수 있습니다.
signon_v2 TEST 바로가기본인인증 버튼을 붙일 사이트 담당자는 아래 정보를 IT Infra 팀에 전달합니다.
| 요청 항목 | 예시 | 설명 |
|---|---|---|
| 사이트명 | MRTG, 관리자 포털 | 인증 화면에 표시될 서비스 이름입니다. |
| client_id | mrtg, admin-portal | URL에 들어갈 사이트 식별자입니다. 미기입 시 IT팀이 지정합니다. |
| callback URL | https://site.example.com/auth/signon/callback | 인증 완료 후 돌아갈 대상 사이트의 서버 URL입니다. HTTPS여야 합니다. |
| 허용 사용자 목록 선택 | gildong.hong, sunhee.kim | 인증을 허용할 Works 아이디 목록입니다. 미기입 시 Works 계정이 있는 모든 임직원에게 허용됩니다. |
| 부가 정보 (scope) 선택 | profile | 인증 여부 외에 사용자 프로필(이름·부서)도 함께 받을지 여부입니다. 화면에 이름 표시나 부서별 권한 처리가 필요한 경우 요청합니다. 미기입 시 기본 인증 정보만 반환합니다. |
/token 응답의 email을 기반으로 직접 구현합니다.
IT팀은 signon_v2에 사이트를 등록한 뒤 아래 값을 사이트 담당자에게 전달합니다.
| 전달 항목 | 예시 | 사이트에서 사용하는 곳 |
|---|---|---|
SIGNON_CLIENT_ID | mrtg | /authorize URL과 /token 요청에 사용합니다. |
SIGNON_CLIENT_SECRET | 랜덤 문자열 | 서버에서 /token을 호출할 때만 사용합니다. 외부에 노출하면 안 됩니다. |
SIGNON_REDIRECT_URI | https://site.example.com/auth/signon/callback | 등록된 callback URL입니다. /authorize와 /token에 동일하게 사용합니다. |
SIGNON_CLIENT_SECRET은 서버 설정 파일 또는 환경변수에만 저장합니다. HTML, JavaScript, URL, 브라우저 저장소에 노출하면 안 됩니다.
아래는 IT팀이 signon_v2 운영 서버의 clients.json에 등록하는 값의 예시입니다. 사이트 담당자는 이 파일을 직접 수정하지 않습니다.
{
"mrtg": {
"name": "MRTG",
"client_secret": "replace-with-long-random-secret",
"redirect_uris": [
"https://site.example.com/auth/signon/callback"
],
"bot_id": "00000000",
"allowed_users": ["gildong.hong", "sunhee.kim"],
"scopes": []
}
}
/token 응답에서 요청할 수 있는 부가 정보 범위입니다. 기본 인증만 필요하면 []로 두고, 프로필 정보가 필요하면 ["profile"], 접근 가능 사이트 목록이 필요하면 ["profile", "sites"]를 설정합니다. IT팀에 요청 시 필요한 scope를 함께 명시하세요.
/authorize로 이동시킵니다.code(승인) 또는 error(거부/만료)를 전달합니다./token에 code를 제출해 사용자 정보를 받습니다. code는 2분 안에 1회만 사용 가능합니다.인증 서버 기본 URL: https://sap.mdvplab.com:5010
| 엔드포인트 | HTTP 메서드 | 설명 | 누가 호출하나 |
|---|---|---|---|
/authorize | GET | 인증 시작 화면 | 사용자 브라우저 |
/token | POST | callback code를 사용자 정보로 교환 | 대상 사이트 서버 |
/healthz | GET | 서비스 상태 확인 | 관리/점검용 |
사이트 담당자는 IT팀이 전달한 SIGNON_CLIENT_ID, SIGNON_REDIRECT_URI를 사용해 인증 시작 URL을 만듭니다.
https://sap.mdvplab.com:5010/authorize
?client_id=[SIGNON_CLIENT_ID]
&redirect_uri=[SIGNON_REDIRECT_URI]
&state=[CSRF 방지용 임의 문자열]
&scope=[요청할 부가정보, 선택]
client_id: IT팀이 전달한 SIGNON_CLIENT_IDredirect_uri: IT팀이 등록하고 전달한 SIGNON_REDIRECT_URIstate: 대상 사이트 서버가 매 요청마다 생성하고 세션에 저장하는 임의 문자열scope: 부가 정보 요청 범위 (선택). 공백으로 구분. 예: profile, profile sites<a href="AUTHORIZE_URL">Works 본인인증으로 로그인</a>
<?php
session_start();
$signonBaseUrl = 'https://sap.mdvplab.com:5010';
$clientId = 'YOUR_CLIENT_ID'; // IT팀이 전달한 값
$redirectUri = 'YOUR_REDIRECT_URI'; // IT팀에 등록한 callback URL
$state = bin2hex(random_bytes(16));
$_SESSION['signon_state'] = $state;
$loginUrl = $signonBaseUrl . '/authorize?' . http_build_query([
'client_id' => $clientId,
'redirect_uri' => $redirectUri,
'state' => $state,
]);
?>
<a href="<?php echo htmlspecialchars($loginUrl); ?>">Works 본인인증으로 로그인</a>
callback URL은 IT팀에 요청한 URL과 정확히 같아야 합니다. 경로, 도메인, 포트, https 여부가 다르면 차단됩니다.
https://site.example.com/auth/signon/callback?code=[임시 코드]&state=[처음 보낸 state]
https://site.example.com/auth/signon/callback?error=rejected&state=[처음 보낸 state]
https://site.example.com/auth/signon/callback?error=expired&state=[처음 보낸 state]
rejected: 사용자가 Works 인증을 거부했습니다.expired: 5분 안에 인증이 완료되지 않아 요청이 만료되었습니다.state가 포함됩니다. signon_v2는 code와 error 응답 모두에 state를 함께 전달합니다. callback에서는 state 유무를 진입 조건으로 사용하고, error/code 분기는 state 검증 이후에 처리해야 합니다.
<?php
session_start();
// signon_v2는 성공(code)과 실패(error) 모두 state를 포함해 돌아옴
// → state 유무로 진입하고, 검증 후 error/code 분기 처리
if (isset($_GET['state'])) {
if (!isset($_SESSION['signon_state']) || !hash_equals($_SESSION['signon_state'], $_GET['state'])) {
die('인증 세션이 유효하지 않습니다. <a href="index.php">다시 시도</a>');
}
unset($_SESSION['signon_state']);
if (isset($_GET['error'])) {
$msg = ['rejected' => '인증이 거부되었습니다.', 'expired' => '인증 시간이 만료되었습니다.'][$_GET['error']] ?? '인증에 실패했습니다.';
die(htmlspecialchars($msg) . ' <a href="index.php">다시 시도</a>');
}
if (isset($_GET['code'])) {
// 6장: /token으로 code를 교환해 사용자 정보 획득
}
}
callback으로 받은 code는 반드시 대상 사이트 서버에서 /token으로 검증해야 합니다. code는 발급 후 2분간 유효하고 1회만 사용할 수 있습니다.
/token cURL 요청 시 아래 옵션을 추가해야 합니다. 공인 인증서 환경에서는 사용하지 마세요.
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
POST https://sap.mdvplab.com:5010/token
Content-Type: application/json
{
"client_id": "mrtg",
"client_secret": "IT팀이 전달한 SIGNON_CLIENT_SECRET",
"redirect_uri": "https://site.example.com/auth/signon/callback",
"code": "CALLBACK_CODE"
}
기본 응답 (scope 없음 — 항상 반환):
{
"active": true,
"client_id": "mrtg",
"email": "user@works-domain.com",
"username": "user",
"user_id": "75672d66-68c3-4217-17b0-03d5cdfa5106"
}
scope=profile 추가 시:
{
"active": true,
"client_id": "mrtg",
"email": "user@works-domain.com",
"username": "user",
"user_id": "75672d66-68c3-4217-17b0-03d5cdfa5106",
"display_name": "홍길동(Gildong)",
"department": "IT Infra"
}
대상 사이트는 응답의 email 또는 user_id로 사용자를 식별하고 자체 세션을 생성합니다.
username: 도메인을 제외한 Works 아이디. 화면 표시용으로 사용하세요.user_id: Works에서 발급한 고유 UUID. 영구적인 사용자 식별에 사용하세요.display_name: Works 프로필 이름 — scope=profile 설정 시 반환.department: 소속 부서 — scope=profile 설정 시 반환./token 요청이 실패하면 HTTP 4xx와 함께 아래 형식의 JSON이 반환됩니다.
{ "error": "오류_코드" }
| 오류 코드 | HTTP | 원인 |
|---|---|---|
invalid_client | 401 | client_id 또는 client_secret이 틀렸습니다. |
invalid_request | 400 | code 또는 redirect_uri가 누락되었습니다. |
invalid_grant | 400 | redirect_uri가 code 발급 시점과 다릅니다. |
invalid_code | 400 | 존재하지 않는 code입니다. |
code_expired | 400 | code 유효 시간(2분)이 초과했습니다. |
code_already_used | 400 | 이미 사용된 code입니다. |
rate_limited | 429 | 단시간에 요청이 너무 많습니다. |
| 증상 | 원인 | 확인할 것 |
|---|---|---|
| 허용되지 않은 redirect_uri | 요청 URL과 등록 URL이 다름 | 스킴, 도메인, 포트, 경로, 끝 슬래시까지 같은지 확인 |
| invalid_client | client_id 또는 client_secret 불일치 | IT팀이 전달한 값을 서버 설정에 넣었는지 확인 |
| invalid_request | code 또는 redirect_uri 누락/불일치 | /authorize와 /token의 redirect_uri가 정확히 같은지 확인 |
| code_expired | callback 수신 후 /token 호출이 너무 늦음 | callback 처리 즉시 /token을 호출하는지 확인. code 유효 시간은 2분입니다. |
| code_already_used | 같은 code를 두 번 제출 | callback 페이지 새로고침이나 중복 호출이 없는지 확인 |
| state 검증 실패 | 세션이 바뀌었거나 state 저장/검증 누락 | 버튼 생성 시 state를 세션에 저장하고 callback에서 비교하는지 확인 |
| Works 사용자를 찾을 수 없음 | Works 계정이 없거나 아이디가 다름 | 사용자가 올바른 Works 아이디를 입력했는지 확인 |
| 뒤로가기 시 인증 메시지 재발송 | 브라우저 POST 재전송 | signon_v2가 PRG 패턴으로 처리하므로 별도 조치 불필요. callback 페이지 새로고침 방지는 대상 사이트에서 직접 처리하세요. |
| 부가 정보(display_name 등) 미수신 | scope 미설정 또는 미허용 | /authorize URL에 scope=profile을 추가하고, IT팀에 해당 scope 허용을 요청했는지 확인 |
SIGNON_CLIENT_SECRET은 서버에서만 보관합니다.state는 매 요청마다 새로 생성하고 callback에서 검증합니다.redirect_uri는 IT팀에 등록한 값과 정확히 일치시킵니다./token 호출은 브라우저가 아닌 서버에서 수행합니다.사내 MRTG 모니터링 사이트가 v2 표준 구현 사례입니다. 이 가이드의 모든 예시 코드는 MRTG 구현을 기준으로 작성됐습니다.
| 항목 | 값 |
|---|---|
| 서비스 URL | https://sap.mdvplab.com:1443/mrtg/fw/ |
| 구현 파일 | /var/www/html/mrtg/fw/index.php |
| 구현 방식 | 단일 PHP 파일에서 버튼 생성·callback 처리·세션 관리를 모두 담당 |