docs: "주요 기능 및 사용법" 섹션 간소화

dev
박성영 4 months ago
parent 59da4330f4
commit c3044b97fc

@ -19,40 +19,20 @@
- [4.8 데이터베이스 스키마](#database-schema)
- [5. 주요 기능 및 사용법](#5-주요-기능-및-사용법)
- [5.1 프로젝트 구조 패턴](#project-structure-pattern)
- [5.2 사용자 관리 기능 예시](#user-management-example)
- [5.2.1 Controller](#controller)
- [5.2.2 Service](#service)
- [5.2.3 Mapper](#mapper)
- [5.3 로그인 기능](#login-function)
- [5.3.1 Controller](#login-controller)
- [5.4 공통 유틸리티](#common-utilities)
- [5.4.1 API 응답 유틸리티](#api-response-util)
- [5.5 레이아웃 구성](#layout-configuration)
- [5.6 보안 기능](#security-features)
- [5.6.1 XSS 필터](#xss-filter)
- [5.6.2 리퍼러 체크](#referer-check)
- [5.6.3 권한 관리](#permission-management)
- [5.6.4 사용자 > 그룹 > 역할 > 메뉴 구조](#user-group-role-menu-structure)
- [5.7 배치 작업 관리](#batch-management)
- [5.2 핵심 기능 흐름](#user-management-example)
- [5.3 공통 유틸리티](#common-utilities)
- [5.4 레이아웃 구성](#layout-configuration)
- [5.5 보안 기능](#security-features)
- [5.6 배치 작업 관리](#batch-management)
- [6. 개발 가이드라인](#6-개발-가이드라인)
- [6.1 코드 작성 규칙](#code-writing-rules)
- [6.2 디렉토리 구조 가이드](#directory-structure-guide)
- [6.3 UI 컴포넌트 가이드](#ui-component-guide)
- [7. 배포 가이드](#7-배포-가이드)
- [7.1 빌드 방법](#build-method)
- [7.1.1 WAR 파일 빌드](#war-file-build)
- [7.1.2 bootWar 파일 빌드](#bootwar-file-build)
- [7.2 프로필 설정](#profile-settings)
- [7.2.1 기본 프로필](#default-profile)
- [7.2.2 프로필 활성화 방법](#profile-activation)
- [7.3 외부 WAS를 이용한 배포](#external-was-deployment)
- [7.3.1 Tomcat에 WAR 파일 배포](#tomcat-war-deployment)
- [7.3.2 외부 WAS에서 프로필 설정](#external-was-profile-settings)
- [7.4 WAR로 배포 및 실행](#war-deployment-execution)
- [7.4.1 bootWar 파일 실행](#bootwar-execution)
- [7.4.2 외부 WAS에 배포하여 실행](#external-was-execution)
- [7.4.3 백그라운드 실행 (Linux/macOS)](#background-execution)
- [7.4.4 Windows 서비스로 등록](#windows-service-registration)
- [7.5 배포 환경 설정](#deployment-environment-settings)
- [7.6 배포 체크리스트](#deployment-checklist)
- [8. 참고 자료](#8-참고-자료)
@ -441,699 +421,56 @@ MyBatis 매퍼 파일은 `src/main/resources/mybatis/mapper/` 디렉토리에
<h3 id="project-structure-pattern">5.1 프로젝트 구조 패턴</h3>
XIT Framework는 MVC(Model-View-Controller) 패턴을 기반으로 하며, 각 기능별로 다음과 같은 계층 구조를 가집니다:
1. **Controller**: 클라이언트 요청을 처리하고 응답을 반환합니다.
2. **Service**: 비즈니스 로직을 처리합니다.
3. **Mapper**: 데이터베이스 접근을 담당합니다.
4. **Model**: 데이터 구조를 정의합니다.
<h3 id="user-management-example">5.2 사용자 관리 기능 예시</h3>
사용자 관리 기능은 다음과 같은 구조로 구현되어 있습니다:
<h4 id="controller">5.2.1 Controller</h4>
```java
@Controller
@RequestMapping("/system/user")
public class UserController {
@Resource(name = "UserService")
private UserService userService;
// 사용자 목록 페이지
@RequestMapping("/list.do")
public String userList(SystemUserVO paramVO, Model model) {
return "system/user/list.base";
}
// 사용자 목록 조회 AJAX
@PostMapping("/list.ajax")
public ResponseEntity<?> getUserListAjax(@ModelAttribute SystemUserVO paramVO) {
int totalCount = userService.selectUserListTotalCount(paramVO);
paramVO.setTotalCount(totalCount);
paramVO.setPagingYn("Y");
List<SystemUserVO> userList = userService.selectUserList(paramVO);
return ApiResponseUtil.successWithGrid(userList, paramVO);
}
// 사용자 등록 페이지
@GetMapping("/register.do")
public String registerUser(Model model) {
model.addAttribute("user", new SystemUserVO());
return "system/user/form.base";
}
// 사용자 등록 처리 AJAX
@PostMapping("/register.ajax")
public ResponseEntity<?> registerUserAjax(@ModelAttribute SystemUserVO userVO) {
// 사용자 ID 자동 생성
String userId = userService.generateUserId();
userVO.setUserId(userId);
// 기본 비밀번호 설정
String defaultPassword = env.getProperty("Globals.DefaultPassword", "xitpassword");
userVO.setPasswd(EgovFileScrty.encryptPassword(defaultPassword, userId));
int result = userService.insertUser(userVO);
if (result > 0) {
return ApiResponseUtil.success("사용자가 성공적으로 등록되었습니다.");
} else {
return ApiResponseUtil.error("사용자 등록에 실패했습니다.");
}
}
}
```
<h4 id="service">5.2.2 Service</h4>
```java
@Service("UserService")
public interface UserService {
// 사용자 정보 조회
SystemUserVO selectUser(String userId);
// 사용자 목록 조회
List<SystemUserVO> selectUserList(SystemUserVO vo);
// 사용자 목록 총 개수 조회
int selectUserListTotalCount(SystemUserVO vo);
// 사용자 ID 생성
String generateUserId();
// 사용자 등록
int insertUser(SystemUserVO vo);
// 사용자 수정
int updateUser(SystemUserVO vo);
}
```
<h4 id="mapper">5.2.3 Mapper</h4>
```java
@Mapper
public interface UserMapper {
// 사용자 정보 조회
SystemUserVO selectUser(String userId);
// 사용자 목록 조회
List<SystemUserVO> selectUserList(SystemUserVO vo);
// 사용자 목록 총 개수 조회
int selectUserListTotalCount(SystemUserVO vo);
// 사용자 ID 생성
String generateUserId();
// 사용자 등록
int insertUser(SystemUserVO vo);
// 사용자 수정
int updateUser(SystemUserVO vo);
}
```
<h3 id="login-function">5.3 로그인 기능</h3>
로그인 기능은 다음과 같은 구조로 구현되어 있습니다:
<h4 id="login-controller">5.3.1 Controller</h4>
```java
@Controller
@RequestMapping("/login")
public class LoginController {
@Autowired
private LoginService loginService;
// 로그인 페이지
@RequestMapping(value = "/login.do", method = RequestMethod.GET)
public String loginPage(Model model, HttpServletRequest request) {
// 쿠키에서 저장된 아이디 가져오기
Cookie[] cookies = request.getCookies();
String savedUserId = "";
boolean isSaveId = false;
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("savedUserId".equals(cookie.getName())) {
savedUserId = cookie.getValue();
isSaveId = true;
break;
}
}
}
model.addAttribute("savedUserId", savedUserId);
model.addAttribute("isSaveId", isSaveId);
return "login/login.login";
}
// 로그인 처리 AJAX
@PostMapping("/login.ajax")
public ResponseEntity<?> loginAjax(
@RequestParam("userAcnt") String userAcnt,
@RequestParam("passwd") String passwd,
@RequestParam(value = "saveId", required = false) String saveId,
HttpServletRequest request,
HttpServletResponse response) {
try {
// 로그인 처리
SessionVO sessionVO = loginService.login(userAcnt, passwd, request, response);
// 아이디 저장 처리
if ("Y".equals(saveId)) {
Cookie cookie = new Cookie("savedUserId", userAcnt);
cookie.setMaxAge(60 * 60 * 24 * 7);
cookie.setPath("/");
response.addCookie(cookie);
} else {
Cookie cookie = new Cookie("savedUserId", "");
cookie.setMaxAge(0);
cookie.setPath("/");
response.addCookie(cookie);
}
if (sessionVO != null) {
// 로그인 성공
Map<String, Object> data = new HashMap<>();
data.put("redirectUrl", "/main.do");
return ApiResponseUtil.success(data, "로그인에 성공하였습니다.");
} else {
// 로그인 실패
return ApiResponseUtil.error("아이디 또는 비밀번호가 일치하지 않습니다.");
}
} catch (Exception e) {
return ApiResponseUtil.error("로그인 처리 중 오류가 발생했습니다.");
}
}
// 로그아웃 처리
@GetMapping("/logout.do")
public String logout(HttpServletRequest request, HttpServletResponse response) {
try {
loginService.logout(request, response);
} catch (Exception e) {
log.error("로그아웃 처리 중 오류 발생", e);
}
return "redirect:" + loginProperties.getUrl();
}
}
```
<h3 id="common-utilities">5.4 공통 유틸리티</h3>
<h4 id="api-response-util">5.4.1 API 응답 유틸리티</h4>
```java
public class ApiResponseUtil {
// 성공 응답
public static ResponseEntity<?> success(Object data, String message) {
ApiResponseEntity response = new ApiResponseEntity();
response.setResult(true);
response.setMessage(message);
response.setData(data);
return ResponseEntity.ok(response);
}
// 성공 응답 (메시지만)
public static ResponseEntity<?> success(String message) {
return success(null, message);
}
// 그리드 데이터 응답
public static ResponseEntity<?> successWithGrid(List<?> data, PagingVO pagingVO) {
Map<String, Object> gridData = new HashMap<>();
gridData.put("contents", data);
gridData.put("pagination", pagingVO);
return success(gridData, "조회가 완료되었습니다.");
}
// 오류 응답
public static ResponseEntity<?> error(String message) {
ApiResponseEntity response = new ApiResponseEntity();
response.setResult(false);
response.setMessage(message);
return ResponseEntity.ok(response);
}
}
```
<h3 id="layout-configuration">5.5 레이아웃 구성</h3>
XIT Framework는 Apache Tiles를 사용하여 레이아웃을 구성합니다. 기본 레이아웃은 `src/main/webapp/WEB-INF/views/layouts/base/default.jsp`에 정의되어 있습니다.
```jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<!-- Cache Control -->
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Expires" content="0">
<meta http-equiv="Pragma" content="no-cache">
<title>XIT - Framework</title>
<link rel="shortcut icon" type="image/x-icon" href="<c:url value='/img/common/favicon.ico' />">
<!-- Font & Icon -->
<link rel="stylesheet" href="<c:url value='/font/roboto/roboto.min.css' />" id="font-css">
<link rel="stylesheet" href="<c:url value='/plugins/material-design-icons-iconfont/material-design-icons.min.css' />">
<link rel="stylesheet" href="<c:url value='/plugins/fontawesome-free/css/all.min.css' />">
<link rel="stylesheet" href="<c:url value='/css/materialdesignicons.css' />">
<!-- Plugins -->
<link rel="stylesheet" href="<c:url value='/plugins/DataTables/datatables.css' />">
<!-- Main Style -->
<link rel="stylesheet" href="<c:url value='/plugins/simplebar/simplebar.min.css' />">
<link rel="stylesheet" href="<c:url value='/css/style.min.css' />" id="main-css">
<link rel="stylesheet" href="<c:url value='/css/sidebar-royal.min.css' />" id="theme-css">
<link rel="stylesheet" href="<c:url value='/css/bootstrap-datepicker.min.css' />">
<link rel="stylesheet" href="<c:url value='/css/style_new.css' />">
<!-- Main Scripts -->
<script type="text/javascript" src="<c:url value='/js/jquery.min.js' />"></script>
<script type="text/javascript" src="<c:url value='/js/bootstrap.bundle.min.js' />"></script>
<script type="text/javascript" src="<c:url value='/js/popper/1.16.1/popper.min.js' />"></script>
<script type="text/javascript" src="<c:url value='/js/bootstrap-datepicker.min.js' />"></script>
<!-- Plugins -->
<script type="text/javascript" src="<c:url value='/plugins/simplebar/simplebar.min.js' />"></script>
<script type="text/javascript" src="<c:url value='/plugins/feather-icons/feather.min.js' />"></script>
<script type="text/javascript" src="<c:url value='/plugins/DataTables/datatables.js' />"></script>
<script type="text/javascript" src="<c:url value='/plugins/crypto-js/crypto-js-4.0.0.js' />"></script>
<script type="text/javascript" src="<c:url value='/plugins/inputmask-4.x/jquery.inputmask.bundle.js' />"></script>
<!-- TOAST UI Grid -->
<link rel="stylesheet" type="text/css" href="<c:url value='/plugins/tuiGrid/tui-date-picker.css' />">
<link rel="stylesheet" type="text/css" href="<c:url value='/plugins/tuiGrid/tui-pagination.css' />">
<link rel="stylesheet" type="text/css" href="<c:url value='/plugins/tuiGrid/tui-time-picker.css' />">
<link rel="stylesheet" type="text/css" href="<c:url value='/plugins/tuiGrid/tui-grid.css' />">
<script type="text/javascript" src="<c:url value='/plugins/tuiGrid/tui-code-snippet.js' />"></script>
<script type="text/javascript" src="<c:url value='/plugins/tuiGrid/tui-pagination.js' />"></script>
<script type="text/javascript" src="<c:url value='/plugins/tuiGrid/tui-date-picker.js' />"></script>
<script type="text/javascript" src="<c:url value='/plugins/tuiGrid/tui-time-picker.js' />"></script>
<script type="text/javascript" src="<c:url value='/plugins/tuiGrid/tui-grid.js' />"></script>
<!-- XIT 커스텀 -->
<link rel="stylesheet" type="text/css" href="<c:url value='/xit/xit-common.css' />">
<link rel="stylesheet" type="text/css" href="<c:url value='/xit/xit-tui-grid.css' />">
<script type="text/javascript" src="<c:url value='/xit/xit-tui-grid.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/xit-common.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/xit-validation.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/menu-path.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/common_util.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/datatables_util.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/moment.js' />"></script>
<!-- 파일 업로드 스타일 -->
<link rel="stylesheet" href="<c:url value="/resources/css/xit-multi-fileupload.css"/>" />
</head>
<body>
<!-- Sidebar -->
<div class="sidebar">
<tiles:insertAttribute name="menu_header" />
<tiles:insertAttribute name="menu" />
</div>
<!-- /Sidebar -->
<!-- Main -->
<div class="main">
<tiles:insertAttribute name="main_header" />
<tiles:insertAttribute name="main" />
</div>
<!-- /Main -->
<script type="text/javascript" src="<c:url value='/js/script.min.js' />"></script>
</body>
</html>
```
### 레이아웃 구성 요소
1. **Sidebar (사이드바)**
- `menu_header`: 메뉴 헤더 영역
- `menu`: 메뉴 네비게이션 영역
2. **Main (메인 영역)**
- `main_header`: 메인 헤더 영역
- `main`: 실제 콘텐츠 영역
### 주요 CSS/JS 라이브러리
- **Bootstrap**: 반응형 UI 프레임워크
- **Material Design Icons**: 아이콘 라이브러리
- **FontAwesome**: 아이콘 라이브러리
- **DataTables**: 데이터 테이블 플러그인
- **TOAST UI Grid**: 고성능 그리드 컴포넌트
- **SimpleBar**: 커스텀 스크롤바
- **Crypto-JS**: 암호화 라이브러리
- **InputMask**: 입력 마스킹
- **Moment.js**: 날짜/시간 처리
### XIT 커스텀 파일
- **xit-common.css/js**: 공통 스타일 및 유틸리티
- **xit-tui-grid.css/js**: TOAST UI Grid 커스터마이징
- **xit-validation.js**: 폼 유효성 검사
- **menu-path.js**: 메뉴 경로 처리
- **common_util.js**: 공통 유틸리티 함수
- **datatables_util.js**: DataTables 유틸리티
- **xit-multi-fileupload.css**: 다중 파일 업로드 스타일
### 캐시 제어
브라우저 캐시를 방지하기 위해 메타 태그와 HTTP 헤더를 설정하여 항상 최신 콘텐츠를 제공합니다.
<h3 id="security-features">5.6 보안 기능</h3>
XIT Framework는 다양한 보안 기능을 제공하여 애플리케이션의 안전성을 강화합니다.
XIT Framework는 MVC(Model-View-Controller) 패턴을 기반으로 하며, 각 기능은 **Controller, Service, Mapper, Model** 계층으로 명확하게 분리됩니다.
<h4 id="xss-filter">5.6.1 XSS 필터</h4>
- **Controller**: HTTP 요청을 처리하고, Service를 호출한 뒤, 결과를 View 또는 JSON으로 응답합니다.
- **Service**: 비즈니스 로직을 구현합니다.
- **Mapper**: MyBatis를 통해 데이터베이스 SQL 쿼리를 실행합니다.
- **Model (VO)**: 데이터의 구조를 정의하는 객체입니다.
XSS(Cross-Site Scripting) 공격을 방지하기 위한 필터를 제공합니다. 이 필터는 모든 요청 파라미터를 검사하고 잠재적인 XSS 공격 코드를 제거합니다.
<h3 id="user-management-example">5.2 핵심 기능 흐름</h3>
```java
// XSS 필터 설정 클래스
@Configuration
public class XssFilterConfig {
대부분의 기능은 '사용자 관리'와 유사한 흐름을 따릅니다.
private final XssUtil xssUtil;
1. **Controller**: `@RequestMapping`으로 URL을 정의하고, 사용자 입력을 `VO`에 바인딩하여 Service로 전달합니다.
- `@PostMapping("/list.ajax")`: 목록 조회
- `@PostMapping("/register.ajax")`: 등록/수정 처리
2. **Service**: 비즈니스 로직을 처리하고, 필요한 경우 Mapper를 호출하여 DB와 상호작용합니다.
3. **Mapper**: XML에 정의된 SQL을 실행하여 결과를 반환합니다.
public XssFilterConfig(XssUtil xssUtil) {
this.xssUtil = xssUtil;
}
<h3 id="common-utilities">5.3 공통 유틸리티</h3>
@Bean
public FilterRegistrationBean<XssFilter> xssFilterRegistration() {
FilterRegistrationBean<XssFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new XssFilter(xssUtil));
registrationBean.addUrlPatterns("/*"); // 모든 URL에 적용
registrationBean.setName("xssFilter");
registrationBean.setOrder(1); // 필터 순서 (낮은 숫자가 먼저 실행)
return registrationBean;
}
}
```
XSS 필터는 다음과 같은 기능을 제공합니다:
1. **HTML 태그 이스케이프**: 일반 텍스트 필드에서 HTML 태그를 이스케이프 처리합니다.
2. **HTML 에디터 내용 정화**: HTML 에디터 내용에서는 허용된 태그만 남기고 위험한 스크립트를 제거합니다.
3. **파일명 정화**: 파일명에서 위험한 문자를 제거합니다.
4. **보안 헤더 설정**: XSS 방지를 위한 HTTP 헤더를 설정합니다.
```java
// XSS 필터 구현
public class XssFilter extends OncePerRequestFilter {
private final XssUtil xssUtil;
public XssFilter(XssUtil xssUtil) {
this.xssUtil = xssUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// XSS 방지를 위한 헤더 설정
// X-XSS-Protection: 브라우저의 XSS 필터를 켜고, 공격이 감지되면 페이지를 차단.
response.setHeader("X-XSS-Protection", "1; mode=block");
// X-Content-Type-Options: 브라우저가 서버가 지정한 MIME 타입만 사용하게 하여, 잘못된 타입 해석으로 인한 보안 문제를 방지.
response.setHeader("X-Content-Type-Options", "nosniff");
// 요청을 래핑하여 XSS 필터링 적용
XssRequestWrapper xssRequestWrapper = new XssRequestWrapper(request, xssUtil);
// 필터 체인 실행
filterChain.doFilter(xssRequestWrapper, response);
}
}
```
<h4 id="referer-check">5.6.2 리퍼러 체크</h4>
리퍼러(Referer) 헤더를 검사하여 직접 URL을 입력하거나 외부 사이트에서의 접근을 제한합니다. 이를 통해 CSRF(Cross-Site Request Forgery) 공격을 방지하고 애플리케이션의 보안을 강화합니다.
```java
// AuthInterceptor 클래스의 preHandle 메서드 내부
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
// 리퍼러 체크 로직
if (!isRefererCheckExcluded(requestURI)) {
String referer = request.getHeader("Referer");
if (referer == null || referer.isEmpty()) {
// Referer 헤더가 없는 경우 (직접 URL 입력 또는 북마크 등)
log.warn("Referer 헤더 없음: {}", requestURI);
// AJAX 요청인 경우 JSON 응답 반환
if (HttpServletUtil.isAjaxRequest(request) || HttpServletUtil.isRealAjaxRequest(request)) {
handleRefererMissing(response);
return false;
}
// 일반 요청인 경우 로그인 페이지로 리다이렉트
response.setContentType("text/html; charset=UTF-8");
response.getWriter().write("<script type='text/javascript'>");
response.getWriter().write("alert('잘못된 접근입니다. 로그인 페이지로 이동합니다.');");
response.getWriter().write("location.href = '" + request.getContextPath() + loginProperties.getUrl() + "';");
response.getWriter().write("</script>");
return false;
}
}
// 나머지 로직...
return true;
}
```
리퍼러 체크는 다음과 같은 특징을 가집니다:
1. **예외 URL 설정**: 로그인 페이지, 정적 리소스 등 일부 URL은 리퍼러 체크에서 제외됩니다.
2. **AJAX 요청 처리**: AJAX 요청의 경우 JSON 형식으로 오류 응답을 반환합니다.
3. **일반 요청 처리**: 일반 요청의 경우 경고 메시지와 함께 로그인 페이지로 리다이렉트합니다.
<h4 id="permission-management">5.6.3 권한 관리</h4>
사용자의 권한에 따라 접근 가능한 기능과 메뉴를 제한하는 권한 관리 시스템을 제공합니다. 권한 관리는 인터셉터를 통해 구현되며, 사용자의 세션 정보와 요청 URL을 기반으로 접근 권한을 검사합니다.
```java
// 권한 관리 인터셉터
@Slf4j
@RequiredArgsConstructor
public class AuthInterceptor implements HandlerInterceptor {
private final LoginService loginService;
private final AntPathMatcher pathMatcher = new AntPathMatcher();
private final XssUtil xssUtil = new XssUtil();
@Autowired
private InterceptorProperties interceptorProperties;
@Autowired
private LoginProperties loginProperties;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
// Referer 헤더 검사 로직...
try {
// 세션 정보 조회
SessionVO sessionVO = loginService.getSessionInfo(request);
// 세션이 없거나 로그인 상태가 아닌 경우
if (sessionVO == null || !sessionVO.isLogin()) {
// 방문자 권한 확인
if (sessionVO != null && sessionVO.isVisitor()) {
// 방문자 권한으로 접근 가능한지 확인
if (hasAccess(sessionVO, requestURI)) {
return true;
}
}
// 세션 만료 처리...
return false;
}
// 로그인 상태인 경우 접근 권한 확인
if (hasAccess(sessionVO, requestURI)) {
return true;
}
// 접근 권한이 없는 경우
log.warn("접근 권한 없음: {} - {}", sessionVO.getUser().getUserAcnt(), requestURI);
response.sendError(HttpServletResponse.SC_FORBIDDEN, "접근 권한이 없습니다.");
return false;
} catch (Exception e) {
// 예외 처리...
return false;
}
}
// 접근 권한 확인 메서드
private boolean hasAccess(SessionVO sessionVO, String requestURI) {
List<MenuVO> menus = flattenMenuTree(sessionVO.getMenus());
if (menus.isEmpty()) {
return false;
}
// 메뉴의 URL 패턴과 일치하는지 확인
for (MenuVO menu : menus) {
if (menu.getUrlPattern() != null && !menu.getUrlPattern().isEmpty()) {
String[] patterns = xssUtil.unescape(menu.getUrlPattern()).split(",");
for (String pattern : patterns) {
if (pathMatcher.match(pattern.trim(), requestURI)) {
return true;
}
}
}
}
return false;
}
}
```
<h4 id="user-group-role-menu-structure">5.6.4 사용자 > 그룹 > 역할 > 메뉴 구조</h4>
XIT Framework는 사용자, 그룹, 역할, 메뉴를 연결하는 권한 관리 구조를 제공합니다. 이 구조는 데이터베이스 테이블 간의 관계를 통해 구현되며, 접근 제어가 가능합니다.
1. **데이터 구조 개요**
XIT Framework의 권한 관리 시스템은 다음과 같은 주요 엔티티로 구성됩니다:
<h4 id="api-response-util">5.3.1 API 응답 유틸리티</h4>
- **사용자(User)**: 시스템을 사용하는 개인 계정
- **그룹(Group)**: 사용자들의 집합
- **역할(Role)**: 특정 기능에 대한 권한 집합
- **메뉴(Menu)**: 시스템의 기능 단위와 UI 구성 요소
- **URL 패턴**: 각 메뉴에 연결된 실제 접근 경로
일관된 형식의 Ajax 응답을 위해 `ApiResponseUtil`을 사용합니다.
2. **엔티티 간 관계**
- **사용자-그룹 관계**: 사용자는 하나의 그룹에 소속됩니다. 그룹은 여러 사용자를 포함할 수 있습니다. (N:1 관계)
- **그룹-역할 관계**: 그룹은 여러 역할을 가질 수 있으며, 역할은 여러 그룹에 할당될 수 있습니다. (N:M 관계)
- **역할-메뉴 관계**: 역할은 여러 메뉴에 대한 접근 권한을 가지며, 메뉴는 여러 역할에 의해 접근될 수 있습니다. (N:M 관계)
- **메뉴-URL 패턴 관계**: 메뉴는 하나 이상의 URL 패턴과 연결됩니다. (1:N 관계)
3. **데이터베이스 테이블 구조**
- **TB_USER**: 사용자 정보 저장 (USER_ID, USER_ACNT, USER_NM, PASSWD, GRP_ID 등)
- **TB_GROUP**: 그룹 정보 저장 (GRP_ID, GRP_NM, GRP_DC 등)
- **TB_ROLE**: 역할 정보 저장 (ROLE_ID, ROLE_NM, ROLE_DC 등)
- **TB_GROUP_ROLE**: 그룹-역할 매핑 정보 (GRP_ID, ROLE_ID)
- **TB_MENU**: 메뉴 정보 저장 (MENU_ID, MENU_NM, MENU_URL, URL_PATTERN, UPPER_MENU_ID 등)
- **TB_ROLE_MENU**: 역할-메뉴 매핑 정보 (ROLE_ID, MENU_ID)
4. **권한 확인 프로세스**
사용자가 특정 URL에 접근하려고 할 때, 시스템은 다음과 같은 과정으로 권한을 확인합니다:
1. 사용자 식별: 로그인한 사용자의 USER_ID 확인
2. 그룹 확인: 사용자가 속한 그룹(GRP_ID) 조회
3. 역할 확인: 그룹에 할당된 역할(ROLE_ID) 목록 조회
4. 메뉴 확인: 역할에 연결된 메뉴(MENU_ID) 목록 조회
5. URL 패턴 확인: 메뉴에 연결된 URL 패턴과 요청 URL 비교
6. 접근 허용/거부: 일치하는 URL 패턴이 있으면 접근 허용, 없으면 거부
5. **권한 관리의 장점**
이러한 계층적 권한 구조는 다음과 같은 이점을 제공합니다:
- **효율적인 권한 관리**: 그룹과 역할을 통해 다수의 사용자에게 일괄적으로 권한 부여 가능
- **세밀한 접근 제어**: URL 패턴 기반으로 세밀한 접근 제어 가능
- **유연한 권한 설계**: 사용자-그룹-역할-메뉴의 다단계 구조로 다양한 권한 정책 구현 가능
- **동적 메뉴 구성**: 사용자의 권한에 따라 UI 메뉴를 동적으로 구성 가능
- **관리 용이성**: 관리자 화면에서 그룹, 역할, 메뉴, 권한을 통합적으로 관리 가능
<h3 id="batch-management">5.7 배치 작업 관리</h3>
XIT Framework는 Quartz 기반의 배치 스케줄링 시스템을 내장하고 있습니다. 배치 작업은 등록, 실행, 일시정지, 재개, 삭제, 실행 이력 및 로그 관리 등 다양한 기능을 제공합니다.
### 5.7.1 전체 구조
- **Controller**: `BatchJobController` - 배치 작업의 웹/REST API 제공
- **Service**: `BatchJobService` - 배치 작업의 비즈니스 로직 처리
- **Job**: `SampleBatchJob`, `SampleBatchJob2`, `SampleBatchJob3` 등 - 실제 실행되는 배치 잡 구현체
- **Config**: `QuartzConfig`, `BatchJobInitializer`, `QuartzJobListener`, `QuartzListenerConfig` - 스케줄러 및 리스너 설정, 초기화
- **Mapper**: `BatchJobMapper` - 배치 작업/실행/로그 DB 연동
- **Model**: `BatchJobInfoVO`, `BatchJobExecutionVO`, `BatchJobLogVO` - 배치 정보/실행/로그 VO
- **Util**: `BatchJobLogUtil`, `ServerInfoUtil` - 배치 로그, 서버 정보 등 유틸리티
### 5.7.2 주요 기능 및 API
- **배치 작업 목록 조회**: `/batch/list.do`, `/batch/list.ajax`
- **배치 작업 즉시 실행**: `/batch/trigger.ajax` (POST)
- **배치 작업 일시정지/재개/삭제**: `/batch/pause.ajax`, `/batch/resume.ajax`, `/batch/delete.ajax` (POST)
- **실행 이력/로그 조회**: `/batch/execution.do`, `/batch/execution.ajax`, `/batch/log.do`, `/batch/log.ajax`
- **잡 등록/수정/삭제**: `/batch/register.ajax`
#### 예시: 배치 작업 즉시 실행
```http
POST /batch/trigger.ajax
Content-Type: application/x-www-form-urlencoded
jobId=...&jobName=SampleBatchJob&jobGroup=DEFAULT
```
### 5.7.3 배치 잡 예시
```java
@Component
@DisallowConcurrentExecution
public class SampleBatchJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 실제 배치 로직 구현
}
}
```
- **성공**: `ApiResponseUtil.success(data, "메시지");`
- **성공 (그리드)**: `ApiResponseUtil.successWithGrid(list, pagingVO);`
- **실패**: `ApiResponseUtil.error("에러 메시지");`
### 5.7.4 DB 연동 및 로그 관리
<h3 id="layout-configuration">5.4 레이아웃 구성</h3>
- **잡 정보/실행/로그**는 각각 TB_BATCH_JOB_INFO, TB_BATCH_JOB_EXECUTION, TB_BATCH_JOB_LOG 테이블에 저장
- `BatchJobMapper`를 통해 CRUD 및 이력/로그 관리
- `BatchJobLogUtil`로 실행 중 로그를 DB에 저장 가능
UI 레이아웃은 **Apache Tiles**를 사용하여 관리합니다.
### 5.7.5 배치 스케줄러 및 리스너 설정
- **기본 레이아웃**: `src/main/webapp/WEB-INF/views/layouts/base/default.jsp`
- **주요 구성 요소**: `menu_header`, `menu`, `main_header`, `main` 등의 속성으로 페이지의 각 부분을 조립합니다.
- **주요 UI 라이브러리**: Bootstrap, TOAST UI Grid, DataTables 등이 사용되며, `xit-common.js`, `xit-tui-grid.js` 등 공통 스크립트에서 제어됩니다.
- `QuartzConfig`, `QuartzListenerConfig`에서 Quartz 스케줄러 및 JobListener 등록
- `BatchJobInitializer`에서 DB에 등록된 잡을 애플리케이션 시작 시 자동 등록
<h3 id="security-features">5.5 보안 기능</h3>
### 5.7.6 확장 및 커스터마이징
- **XSS 필터 (`XssFilter.java`)**: 모든 요청에 대해 XSS 공격을 방지하기 위해 파라미터를 필터링합니다.
- **리퍼러 체크 및 권한 관리 (`AuthInterceptor.java`)**:
- 허용되지 않은 외부 도메인에서의 요청을 차단합니다.
- 사용자 세션과 권한을 체크하여 인가되지 않은 페이지 접근을 막습니다.
- **권한 구조**: **사용자 > 그룹 > 역할 > 메뉴**의 계층적 구조를 통해 유연하고 세밀한 권한 관리를 지원합니다. 권한 정보는 DB 테이블(`TB_USER`, `TB_GROUP`, `TB_ROLE`, `TB_MENU` 등)을 통해 관리됩니다.
- 새로운 배치 잡은 `Job` 인터페이스 구현 후 등록
- 잡 등록/수정/삭제, 스케줄 변경 등은 Controller/Service/DB를 통해 관리
- 실행 이력, 평균 소요시간, 최근 실패 등 다양한 통계 제공
<h3 id="batch-management">5.6 배치 작업 관리</h3>
### 5.7.7 참고 VO 구조
**Quartz 스케줄러**를 기반으로 배치 작업을 관리합니다.
- **BatchJobInfoVO**: 잡ID, 이름, 그룹, 클래스, 크론, 상태, 설명 등
- **BatchJobExecutionVO**: 실행ID, 잡이름/그룹, 시작/종료시간, 상태, 종료코드/메시지, 서버정보 등
- **BatchJobLogVO**: 로그ID, 실행ID, 로그레벨, 메시지, 시간 등
- **주요 기능**: DB에 등록된 배치 작업의 조회, 즉시 실행, 스케줄 변경, 일시정지/재개, 실행 이력 및 로그 관리가 가능합니다.
- **구현**: `Job` 인터페이스를 구현하여 새로운 배치 작업을 작성하고, DB(`TB_BATCH_JOB_INFO`)에 등록하여 사용합니다.
- **API**: `BatchJobController`를 통해 REST API 형식으로 배치 제어 기능을 제공합니다.
## 6. 개발 가이드라인
@ -1386,4 +723,4 @@ Windows에서는 [winsw](https://github.com/winsw/winsw)와 같은 도구를 사
- [전자정부 프레임워크 가이드](https://www.egovframe.go.kr/wiki/doku.php)
- [MyBatis 공식 문서](https://mybatis.org/mybatis-3/ko/index.html)
- [TOAST UI Grid 공식 문서](https://ui.toast.com/tui-grid)
- [TOAST UI Editor 공식 문서](https://ui.toast.com/tui-editor)
- [TOAST UI Editor 공식 문서](https://ui.toast.com/tui-editor)
Loading…
Cancel
Save