환경
ES5
SpringBoot 2.3.4
Ajax/JQuery 1.12.4, 2.1.4 (코드 실행 순서에 따라 버전이 바뀔 수 있음)
JSP 2 (JSTL 사용)
Java 8 / JRE 1.8
MyBatis 3.4.1
Spring Security
Oracle 12c
0. 문제 발생
영어와 숫자는 정상적으로 DB에 입력되지만,
한글은 정상적으로 DB에 입력되지 않는 문제가 있었다.
기존 소스
<noticeDetail.jsp>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<div class="wrapper">
<!-- 생략 -->
<form
action="${pageContext.request.contextPath}/managerZone/${isNew ? 'saveNotice' : 'updateNotice'}"
method="post" id="form">
<!-- 생략 -->
</div>
<!-- CKEditor 4.16.2 -->
<script src="https://cdn.ckeditor.com/4.16.2/standard/ckeditor.js"></script>
<script>
// CKEditor 초기화
document.addEventListener("DOMContentLoaded", function() {
CKEDITOR.replace('content', {
language: "ko",
entities: false, // HTML 엔티티를 사용하지 않도록 설정
allowedContent: true // 모든 HTML 내용을 허용
});
// CKEditor에 내용 설정
CKEDITOR.instances.content.setData('${fn:escapeXml(noticeDto.content)}'); // XSS 방지
});
</script>
<ManagerController.java>
@Controller
@RequestMapping("/manager/*")
public class ManagerController {
// 공지사항 등록
@RequestMapping(value = "saveNotice", method = RequestMethod.POST)
public String insertNotice(@ModelAttribute NoticeDTO noticeDto, RedirectAttributes redirectAttributes) throws Exception {
try {
// DTO에 값이 제대로 들어오는지 확인
System.out.println("Title: " + noticeDto.getTitle());
System.out.println("Content: " + noticeDto.getContent());
noticeDto.setCreatorOid("truadmin"); // 생성자 사번 설정
noticeDto.setCreatedTimeStamp(new java.sql.Date(System.currentTimeMillis())); // 생성일자 설정
// 서비스 호출
noticeService.insertNotice(noticeDto);
redirectAttributes.addFlashAttribute("message", "공지사항이 등록되었습니다.");
return "redirect:/managerZone/noticeList"; // 목록 페이지로 리다이렉트
} catch (Exception e) {
e.printStackTrace(); // 예외 출력
redirectAttributes.addFlashAttribute("error", "공지사항 등록 중 오류가 발생했습니다.");
return "redirect:/managerZone/noticeDetail/new"; // 다시 등록 페이지로 이동
}
}
}
게시글을 한글을 포함하여 텍스트를 입력하여 등록해보자.
게시글 등록
게시글 insert
1) 게시글 공지사항 리스트
한글이 깨져서 나오는 것을 확인할 수 있다.
더군다나 이미 작성되어 있는 게시글 리스트를 확인할 때도
CKEditor가 작성은 막아져 있는 상태로 보여져야 하는데 보이지 않는다.
Console 확인
영어와 숫자는 잘 표시되는데 문제는 한글이다.
한글이 깨져서 잘 보이지 않았다.
DB 확인
공지사항 DB에도 데이터가 깨져서 들어간다.
DB, Springboot 프로젝트 내 설정되어 있는 인코딩과
noticeDetail.jsp에서 입력하여 서버로 보내는 텍스트 인코딩이 맞지 않아서 생긴 일이다.
그렇다면 인코딩이 각각 어떻게 설정되어 있는지 살펴 볼 필요가 있다.
- application.properties 설정 파일 내 인코딩 설정 확인
- JSP 파일 내 인코딩 설정
- (Spring Security 사용 시) SecurityConfig.java 소스 내 인코딩 부분 확인
- DB 인코딩 확인
1. 인코딩 확인
application.properties 설정 파일 내 인코딩 설정 확인
# application.properties
spring.http.encoding.enabled=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.force=true
이런 코드가 있는지 확인했는데 해당 설정 자체가 아예 존재하지 않아서 application.properties는 패스하겠다.
JSP 파일 내 인코딩 설정
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
JSP 파일 내 해당 코드가 명시되어 있다.
따라서 JSP 파일에서는 텍스트가 UTF-8로 인코딩된다.
(Spring Security 사용 시) SecurityConfig.java 소스 내 인코딩 부분 확인
private CharacterEncodingFilter filter( ) {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("euc-kr");
filter.setForceEncoding(true);
return filter;
}
이 부분이 문제가 되었다.
CharacterEncodingFilter()에서 'euc-kr'로 명시되어 있어 utf-8 인코딩과 맞지 않아 한글이 깨진 것 같다.
해당 인코딩 설정을 저렇게 한 것으로 보아 DB 인코딩 환경이 'euc-kr'로 되어 있을 가능성이 높다.
DB 인코딩 확인
-- 인코딩 확인
SELECT value FROM v$nls_parameters WHERE parameter = 'NLS_CHARACTERSET';
VALUE |
KO16MSWIN949 |
역시 DB 인코딩은 euc-kr로 되어있다.
여기서 KO16MSWIN949는 Windows의 한국어 문자 집합인 euc-kr의 확장 버전으로,
주로 한국어 문자와 몇 가지 특수 문자를 지원한다.
해당 인코딩을 사용하면 한글 저장은 용이하나, UTF-8과 같은 더 넓은 문자 집합을 지원하지 않기 때문에
비한글 문자에 대한 호환성 문제가 발생할 가능성이 있다.
기본적으로 영어 알파벳과 숫자, 일부 특수문자도 지원하긴 한다.
그러나 모든 유니코드 문자를 지원하진 않기 때문에 다양한 국가의 문자나 특수 문자는 깨질 수 있다.
UTF-8로 인코딩 설정을 맞추는게 제일 베스트이긴 하나,
시간도 오래 걸리고 말단 사원인 내가 이미 구축되어 있는 시스템과 DB 설정을 바꾼다는건 많은 모험이기에
일단 인코딩 설정을 건들지 않는 쪽으로 생각했다.
JSP 화면 내에서 텍스트가 UTF-8로 입력된다.
그런데 해당 UTF-8 텍스트를 서버로 넘길 때는 서버 인코딩이 euc-kr로 되어 있어 한글 텍스트가 깨진다.
그리고 서버로 넘어간 한글이 깨진 텍스트가 인코딩 설정이 euc-kr로 되어 있는 데이터베이스에 insert된다.
그럼 JSP 화면 내에서 UTF-8로 입력 된 테스트를 서버로 넘길 때
인코딩 형식을 euc-kr로 바꿔주면 해결된다.
2. 기존 코드
noticeDetail.jsp
<!-- CKEditor 4.16.2 -->
<script src="https://cdn.ckeditor.com/4.16.2/standard/ckeditor.js"></script>
<script>
// CKEditor 초기화
document.addEventListener("DOMContentLoaded", function() {
CKEDITOR.replace('content', {
language: "ko",
entities: false, // HTML 엔티티를 사용하지 않도록 설정
allowedContent: true // 모든 HTML 내용을 허용
});
// CKEditor에 내용 설정
CKEDITOR.instances.content.setData('${fn:escapeXml(noticeDto.content)}'); // XSS 방지
});
</script>
ManagerController.java
// 공지사항 등록
@RequestMapping(value = "saveNotice", method = RequestMethod.POST)
public String insertNotice(@ModelAttribute NoticeDTO noticeDto, RedirectAttributes redirectAttributes) throws Exception {
try {
// DTO에 값이 제대로 들어오는지 확인
System.out.println("Title: " + noticeDto.getTitle());
System.out.println("Content: " + noticeDto.getContent());
// 서비스 호출
noticeService.insertNotice(noticeDto);
redirectAttributes.addFlashAttribute("message", "공지사항이 등록되었습니다.");
return "redirect:/managerZone/noticeList"; // 목록 페이지로 리다이렉트
} catch (Exception e) {
e.printStackTrace(); // 예외 출력
redirectAttributes.addFlashAttribute("error", "공지사항 등록 중 오류가 발생했습니다.");
return "redirect:/managerZone/noticeDetail/new"; // 다시 등록 페이지로 이동
}
}
기존 코드에서는 noticeDetail.jsp에서 UTF-8로 텍스트를 저장해서 서버로 보내고,
서버에서는 euc-kr인 환경과 충돌하여 깨진 한글로 변환하여 DB에 insert 시키고 있다.
3. 수정 코드
noticeDetail.jsp
<div>
<!-- CSRF 토큰 추가 -->
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
CKEDITOR.replace('content', {
language: "ko",
entities: false,
allowedContent: true,
});
document.getElementById("form").addEventListener("submit", function(event) {
event.preventDefault(); // 기본 폼 제출 방지
const contentEditor = CKEDITOR.instances.content;
const contentData = contentEditor.getData();
// HTML 이스케이프 처리
const escapedContent = escapeHtml(contentData);
// CSRF 토큰을 가져오기
const csrfParameterName = "${_csrf.parameterName}";
const csrfToken = "${_csrf.token}";
// AJAX 요청으로 데이터 전송
$.ajax({
type: "POST",
url: "${pageContext.request.contextPath}/managerZone/${isNew ? 'saveNotice' : 'updateNotice'}",
data: {
title: $('#title').val(),
content: escapedContent,
[csrfParameterName]: csrfToken // CSRF 토큰 추가
},
success: function(response) {
// 성공적으로 처리된 후의 행동
window.location.href = "${pageContext.request.contextPath}/managerZone/noticeList";
},
error: function(xhr, status, error) {
console.error("Error: " + error);
alert("공지사항 등록 중 오류가 발생했습니다.");
}
});
});
});
function escapeHtml(html) {
return html
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
</script>
서버로 전송되기 전 UTF-8로 입력된 데이터를 JavaScript를 이용하여 EUC-KR로 변환한다.
그리고 Ajax를 이용하여 변환 된 데이터를 전송한다.
Spring Security를 사용하는 경우, Ajax 요청 시 CSRF 토큰이 포함되지 않으면 403 에러가 발생할 가능성이 있어서
CSRF 토큰을 Ajax 요청에 추가한다.
ManagerController.java
@RequestMapping(value = "saveNotice", method = RequestMethod.POST)
public String insertNotice(@RequestParam("title") String title,
@RequestParam("content") String content,
RedirectAttributes redirectAttributes) throws Exception {
try {
// 입력 값 확인을 위한 콘솔 출력
System.out.println("Title: " + title);
System.out.println("Content: " + content);
// DTO에 값 설정
NoticeDTO noticeDto = new NoticeDTO();
noticeDto.setTitle(title);
noticeDto.setContent(content); // UTF-8로 수신한 내용을 그대로 사용
// 서비스 호출
noticeService.insertNotice(noticeDto);
redirectAttributes.addFlashAttribute("message", "공지사항이 등록되었습니다.");
return "redirect:/managerZone/noticeList";
} catch (Exception e) {
e.printStackTrace();
redirectAttributes.addFlashAttribute("error", "공지사항 등록 중 오류가 발생했습니다.");
return "redirect:/managerZone/noticeDetail/new";
}
}
JavaScript에서 euc-kr로 변환하여 수신한 데이터를 서버에서는 그대로 content를 사용하여 DB로 보낸다.
DB에 깨지지 않은 한글이 잘 들어갔다.
처음 구축하면 인코딩을 UTF-8로 잘 설정해줬으면 하는 바람이다...
'[Project] > 업무일지' 카테고리의 다른 글
회원탈퇴 시 쿠키 삭제하기(JavaScript) (1) | 2024.12.27 |
---|---|
전역변수 vs 지역변수 (0) | 2024.12.16 |
[Oracle] 부모 - 자식 간 관계일 때 컬럼값 수정 (0) | 2024.10.24 |
[Oracle/WEB] 특정 쿠폰 사용완료 고객, 주문, 쿠폰 정보 조회 쿼리 작성 (0) | 2024.10.23 |
Cause: java.sql.SQLSyntaxErrorException: ORA-00911: invalid character (0) | 2024.10.16 |