248 lines
6.5 KiB
Plaintext
248 lines
6.5 KiB
Plaintext
---
|
|
description:
|
|
globs:
|
|
alwaysApply: true
|
|
---
|
|
# 보안 가이드
|
|
|
|
## 인증 및 인가
|
|
|
|
### 세션 기반 인증
|
|
현재 시스템은 세션 기반 인증을 사용합니다:
|
|
|
|
```java
|
|
// 사용자 인증 정보 저장
|
|
PersonBean person = new PersonBean();
|
|
person.setUserId(userId);
|
|
person.setUserName(userName);
|
|
request.getSession().setAttribute(Constants.PERSON_BEAN, person);
|
|
|
|
// 인증 정보 조회
|
|
PersonBean person = (PersonBean)request.getSession().getAttribute(Constants.PERSON_BEAN);
|
|
if (person == null) {
|
|
// 로그인 페이지로 리다이렉트
|
|
}
|
|
```
|
|
|
|
### 권한 관리 구조
|
|
- **AUTH_GROUP**: 권한 그룹 정의
|
|
- **MENU_AUTH_GROUP**: 메뉴별 권한 그룹 매핑
|
|
- **USER_AUTH**: 사용자별 권한 할당
|
|
|
|
### 메뉴 접근 권한 체크
|
|
```java
|
|
public HashMap<String, Object> checkUserMenuAuth(HttpServletRequest request, Map<String, Object> paramMap) {
|
|
PersonBean person = (PersonBean)request.getSession().getAttribute(Constants.PERSON_BEAN);
|
|
String userId = person.getUserId();
|
|
|
|
paramMap.put("userId", userId);
|
|
paramMap.put("menuUrl", request.getRequestURI());
|
|
|
|
// 권한 체크 쿼리 실행
|
|
return sqlSession.selectOne("admin.checkUserMenuAuth", paramMap);
|
|
}
|
|
```
|
|
|
|
## 데이터 보안
|
|
|
|
### SQL 인젝션 방지
|
|
**올바른 방법** - 파라미터 바인딩 사용:
|
|
```xml
|
|
<select id="selectUser" parameterType="map" resultType="map">
|
|
SELECT * FROM user_info
|
|
WHERE user_id = #{userId} <!-- 안전한 파라미터 바인딩 -->
|
|
</select>
|
|
```
|
|
|
|
**위험한 방법** - 직접 문자열 치환:
|
|
```xml
|
|
<select id="selectUser" parameterType="map" resultType="map">
|
|
SELECT * FROM user_info
|
|
WHERE user_id = '${userId}' <!-- SQL 인젝션 위험 -->
|
|
</select>
|
|
```
|
|
|
|
### 패스워드 보안
|
|
```java
|
|
// 패스워드 암호화 (EncryptUtil 사용)
|
|
String encryptedPassword = EncryptUtil.encrypt(plainPassword);
|
|
|
|
// 패스워드 검증
|
|
boolean isValid = EncryptUtil.matches(plainPassword, encryptedPassword);
|
|
```
|
|
|
|
### 입력값 검증
|
|
```java
|
|
// CommonUtils를 사용한 입력값 정제
|
|
String safeInput = CommonUtils.checkNull(request.getParameter("input"));
|
|
if (StringUtils.isEmpty(safeInput)) {
|
|
throw new IllegalArgumentException("필수 입력값이 누락되었습니다.");
|
|
}
|
|
```
|
|
|
|
## 세션 보안
|
|
|
|
### 세션 설정
|
|
[web.xml](mdc:WebContent/WEB-INF/web.xml)에서 세션 타임아웃 설정:
|
|
```xml
|
|
<session-config>
|
|
<session-timeout>1440</session-timeout> <!-- 24시간 -->
|
|
</session-config>
|
|
```
|
|
|
|
### 세션 무효화
|
|
```java
|
|
// 로그아웃 시 세션 무효화
|
|
@RequestMapping("/logout.do")
|
|
public String logout(HttpServletRequest request) {
|
|
HttpSession session = request.getSession(false);
|
|
if (session != null) {
|
|
session.invalidate();
|
|
}
|
|
return "redirect:/login.do";
|
|
}
|
|
```
|
|
|
|
## 파일 업로드 보안
|
|
|
|
### 파일 확장자 검증
|
|
```java
|
|
public boolean isAllowedFileType(String fileName) {
|
|
String[] allowedExtensions = {".jpg", ".jpeg", ".png", ".gif", ".pdf", ".doc", ".docx", ".xls", ".xlsx"};
|
|
String extension = fileName.toLowerCase().substring(fileName.lastIndexOf("."));
|
|
return Arrays.asList(allowedExtensions).contains(extension);
|
|
}
|
|
```
|
|
|
|
### 파일 크기 제한
|
|
```java
|
|
// web.xml에서 파일 업로드 크기 제한
|
|
<multipart-config>
|
|
<max-file-size>10485760</max-file-size> <!-- 10MB -->
|
|
<max-request-size>52428800</max-request-size> <!-- 50MB -->
|
|
</multipart-config>
|
|
```
|
|
|
|
### 안전한 파일명 생성
|
|
```java
|
|
// FileRenameClass 사용하여 안전한 파일명 생성
|
|
String safeFileName = FileRenameClass.rename(originalFileName);
|
|
```
|
|
|
|
## XSS 방지
|
|
|
|
### 출력값 이스케이프
|
|
JSP에서 사용자 입력값 출력 시:
|
|
```jsp
|
|
<!-- 안전한 출력 -->
|
|
<c:out value="${userInput}" escapeXml="true"/>
|
|
|
|
<!-- 위험한 출력 -->
|
|
${userInput} <!-- XSS 공격 가능 -->
|
|
```
|
|
|
|
### JavaScript에서 데이터 처리
|
|
```javascript
|
|
// 안전한 방법
|
|
var safeData = $('<div>').text(userInput).html();
|
|
|
|
// 위험한 방법
|
|
var dangerousData = userInput; // XSS 공격 가능
|
|
```
|
|
|
|
## CSRF 방지
|
|
|
|
### 토큰 기반 CSRF 방지
|
|
```jsp
|
|
<!-- 폼에 CSRF 토큰 포함 -->
|
|
<form method="post" action="save.do">
|
|
<input type="hidden" name="csrfToken" value="${csrfToken}"/>
|
|
<!-- 기타 입력 필드 -->
|
|
</form>
|
|
```
|
|
|
|
```java
|
|
// 컨트롤러에서 CSRF 토큰 검증
|
|
@RequestMapping("/save.do")
|
|
public String save(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
|
|
String sessionToken = (String)request.getSession().getAttribute("csrfToken");
|
|
String requestToken = (String)paramMap.get("csrfToken");
|
|
|
|
if (!sessionToken.equals(requestToken)) {
|
|
throw new SecurityException("CSRF 토큰이 일치하지 않습니다.");
|
|
}
|
|
|
|
// 정상 처리
|
|
}
|
|
```
|
|
|
|
## 로깅 및 감사
|
|
|
|
### 보안 이벤트 로깅
|
|
```java
|
|
private static final Logger securityLogger = LoggerFactory.getLogger("SECURITY");
|
|
|
|
public void logSecurityEvent(String event, String userId, String details) {
|
|
securityLogger.info("Security Event: {} | User: {} | Details: {}", event, userId, details);
|
|
}
|
|
|
|
// 사용 예시
|
|
logSecurityEvent("LOGIN_SUCCESS", userId, request.getRemoteAddr());
|
|
logSecurityEvent("ACCESS_DENIED", userId, request.getRequestURI());
|
|
```
|
|
|
|
### 민감 정보 마스킹
|
|
```java
|
|
public String maskSensitiveData(String data) {
|
|
if (data == null || data.length() < 4) {
|
|
return "****";
|
|
}
|
|
return data.substring(0, 2) + "****" + data.substring(data.length() - 2);
|
|
}
|
|
```
|
|
|
|
## 환경별 보안 설정
|
|
|
|
### 개발 환경
|
|
- 상세한 오류 메시지 표시
|
|
- 디버그 모드 활성화
|
|
- 보안 제약 완화
|
|
|
|
### 운영 환경
|
|
- 일반적인 오류 메시지만 표시
|
|
- 디버그 모드 비활성화
|
|
- 엄격한 보안 정책 적용
|
|
|
|
```java
|
|
// 환경별 설정 예시
|
|
if (Constants.IS_PRODUCTION) {
|
|
// 운영 환경 설정
|
|
response.sendError(HttpServletResponse.SC_FORBIDDEN, "접근이 거부되었습니다.");
|
|
} else {
|
|
// 개발 환경 설정
|
|
response.sendError(HttpServletResponse.SC_FORBIDDEN, "권한 부족: " + detailedMessage);
|
|
}
|
|
```
|
|
|
|
## 보안 체크리스트
|
|
|
|
### 코드 레벨
|
|
- [ ] SQL 인젝션 방지 (#{} 파라미터 바인딩 사용)
|
|
- [ ] XSS 방지 (출력값 이스케이프)
|
|
- [ ] CSRF 방지 (토큰 검증)
|
|
- [ ] 입력값 검증 및 정제
|
|
- [ ] 패스워드 암호화 저장
|
|
|
|
### 설정 레벨
|
|
- [ ] 세션 타임아웃 설정
|
|
- [ ] 파일 업로드 제한
|
|
- [ ] 오류 페이지 설정
|
|
- [ ] HTTPS 사용 (운영 환경)
|
|
- [ ] 보안 헤더 설정
|
|
|
|
### 운영 레벨
|
|
- [ ] 정기적인 보안 점검
|
|
- [ ] 로그 모니터링
|
|
- [ ] 권한 정기 검토
|
|
- [ ] 패스워드 정책 적용
|
|
- [ ] 백업 데이터 암호화 |