feat(hmm): align backoffice identity administration (#702)

This commit is contained in:
devmrko
2026-07-22 13:49:59 +09:00
parent 64965c294b
commit 572dcd9a67
11 changed files with 226 additions and 158 deletions

View File

@@ -1,25 +1,25 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('검증 세션')}"></head>
<head th:replace="~{fragments/layout :: head('HMM 액세스 토큰')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<div class="page-title">
<h1>검증 세션</h1>
<h1>HMM 액세스 토큰</h1>
<details class="explanation-details">
<summary>도움말</summary>
<p>이해당사자 원장에 등록된 업무 사용자를 토큰에 연결합니다. 토큰 원문은 한 번만 표시하며, DB에는 해시와 식별용 prefix만 보관합니다. 역할·채널·행 권한은 토큰이 아니라 행 접근 규칙에서 동적으로 계산합니다.</p>
<p>HMM HR 직원에게 접근 토큰을 발급합니다. 토큰 원문은 한 번만 표시하며, DB에는 SHA-256 해시와 식별용 prefix만 보관합니다.</p>
</details>
</div>
<div class="alert alert-success" th:if="${message}" th:text="${message}"></div>
<section class="issued-token-card" th:if="${issued}" th:attr="data-issued-key-id=${issued.keyId()}">
<span class="architecture-kicker">발급 완료 · 지금 한 번만 표시됩니다</span>
<h2>이 토큰 원문을 복사한 뒤 접근 검증으로 이동하세요.</h2>
<h2>이 토큰 원문을 복사해 안전한 위치에 보관하세요.</h2>
<code class="token-value" id="issued-token-value" th:text="${issued.plainToken()}"></code>
<div class="action-stack mt-3">
<button class="btn rw-btn-primary" type="button" data-copy-source="issued-token-value">토큰 원문 복사</button>
<a class="btn rw-btn-secondary" href="/probe">접근 검증으로 이동</a>
<a class="btn rw-btn-secondary" href="/users">사용자 역할 관리</a>
</div>
<p class="form-hint mb-0">화면을 떠난 뒤에는 원문을 다시 볼 수 없습니다. 잃어버리면 기존 토큰을 복구하지 말고 새로 발급하세요.</p>
</section>
@@ -27,19 +27,19 @@
<section class="content-band">
<div class="section-heading">
<div>
<h2>이해당사자 토큰 발급</h2>
<p class="section-subtitle">모든 KB 이해당사자에게 토큰을 발급할 수 있습니다. 지점장·설계사 등의 실제 행 범위는 행 접근 규칙에 연결된 역할로 결정되므로, 토큰을 다시 만들지 않아도 권한 변경이 반영됩니다.</p>
<h2>직원 토큰 발급</h2>
<p class="section-subtitle">활성 HMM 직원에게 토큰을 발급합니다. 직접 역할과 접근 그룹 역할은 토큰 재발급 없이 조회 시점에 반영됩니다.</p>
</div>
</div>
<form method="post" action="/tokens" class="form-grid">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<label>
이해당사자
<select class="form-select" name="stakeholderUserId" required>
<option value="" selected disabled>토큰을 발급할 이해당사자 선택</option>
<option th:each="stakeholder : ${stakeholders}"
th:value="${stakeholder.stakeholderUserId()}"
th:text="${stakeholder.displayLabel()}"></option>
HMM 직원
<select class="form-select" name="userId" required>
<option value="" selected disabled>토큰을 발급할 직원 선택</option>
<option th:each="user : ${users}"
th:value="${user.userId()}"
th:text="${user.empNo() + ' / ' + user.username() + ' / ' + user.deptCode()}"></option>
</select>
</label>
<label>
@@ -74,13 +74,12 @@
</div>
<div class="table-responsive">
<table class="table table-sm align-middle">
<thead><tr><th>ID</th><th>이해당사자</th><th>역할·채널</th><th>용도</th><th>식별용 Prefix</th><th>만료</th><th>회수</th><th></th></tr></thead>
<thead><tr><th>ID</th><th>직원</th><th>용도</th><th>식별용 Prefix</th><th>만료</th><th>회수</th><th></th></tr></thead>
<tbody>
<tr th:each="token : ${tokens}"
th:attr="data-key-id=${token.keyId()},data-description=${token.description()},data-revoked=${token.revokedAt() != null}">
<td th:text="${token.keyId()}">1</td>
<td th:text="${token.username()}">user</td>
<td th:text="${token.stakeholderRole() == null ? '기존 앱 사용자' : token.stakeholderRole() + ' / ' + token.stakeholderChannel()}">지점장 / 설계사채널</td>
<td th:text="${token.description()} ?: '-'">용도</td>
<td><code th:text="${token.keyPrefix() + '****'}">vpd_****</code></td>
<td th:text="${token.expiresAt()}">2026</td>
@@ -94,7 +93,7 @@
</form>
</td>
</tr>
<tr th:if="${#lists.isEmpty(tokens)}"><td colspan="8" class="text-muted">아직 발급된 토큰이 없습니다.</td></tr>
<tr th:if="${#lists.isEmpty(tokens)}"><td colspan="7" class="text-muted">아직 발급된 토큰이 없습니다.</td></tr>
</tbody>
</table>
</div>