비밀번호 처리 기능 보완

This commit is contained in:
KNKIM 2021-07-30 00:01:00 +09:00
parent 7ccba8a51f
commit c34019243d
8 changed files with 205 additions and 61 deletions

View File

@ -6,7 +6,7 @@
<articleType>101</articleType>
<articleTypeName>Q&amp;A</articleTypeName>
<title>공지사항 테스트</title>
<articlePassword>111</articlePassword>
<articlePassword>9uCh4qxBlFqap/+KiqoM68EqO8yYGpKa1c+BCgkOEa4=</articlePassword><!-- 111 -->
<readCount>15</readCount>
<regDate>2021-07-19</regDate>
<regUserName>김아무</regUserName>

View File

@ -23,6 +23,7 @@ import nlib.bbs.service.BoardService;
import nlib.cmm.NlibCommonController;
import nlib.cmm.crypto.AriaCrypto;
import nlib.cmm.crypto.NlibPasswordEncoder;
import nlib.cmm.exception.ErrorMessage;
import nlib.cmm.service.CodeService;
import nlib.cmm.service.NlibProperty;
import nlib.restful.service.DataApiReqVO;
@ -180,7 +181,6 @@ public class BoardController extends NlibCommonController
String pageSize = paramMap.get("pageSize");
String message = paramMap.get("message");
// 게시물 번호
String articleNo = paramMap.get("articleNo");
@ -197,24 +197,19 @@ public class BoardController extends NlibCommonController
reqVO.setAuthKey(createAuthKey(req, authentication));
reqVO.setPageIndex(pageIndex);
reqVO.setPageSize(pageSize);
reqVO.addInfoItem("boardNo", "QNA"); // 게시판ID
reqVO.addInfoItem("articleNo", articleNo); // 게시물번호
// 추가 정보 설정 : 게시판ID, 게시물번호
HashMap<String, Object> info = new HashMap<String, Object>();
info.put("boardNo", "QNA");
info.put("articleNo", articleNo);
reqVO.setInfo(info);
// 요청
DataApiResVO resVO = boardService.selectQnA(reqVO);
// 반환 정보
model.addAttribute("articleNo" , articleNo);
model.addAttribute("article" , resVO.getInfo());
model.addAttribute("preArticle" , resVO.getList().get(0));
model.addAttribute("nextArticle" , resVO.getList().get(1));
model.addAttribute("article" , resVO.getInfo()); // 게시물 상세
model.addAttribute("preArticle" , resVO.getList().get(0)); // 이전글 정보
model.addAttribute("nextArticle" , resVO.getList().get(1)); // 다음글 정보
model.addAttribute("message" , message);
model.addAttribute("pageIndex" , pageIndex);
model.addAttribute("pageIndex" , pageIndex);
model.addAttribute("pageSize" , pageSize);
model.addAttribute("searchKeyword", searchKeyword);
@ -305,20 +300,19 @@ public class BoardController extends NlibCommonController
reqVO.setAuthKey(createAuthKey(req, authentication));
reqVO.setPageIndex(pageIndex);
reqVO.setPageSize(pageSize);
reqVO.addInfoItem("boardNo", "QNA"); // 게시판ID
reqVO.addInfoItem("articleNo", articleNo); // 게시물번호
// 추가 정보 설정 : 게시판ID, 게시물번호
HashMap<String, Object> info = new HashMap<String, Object>();
info.put("boardNo", "QNA");
info.put("articleNo", articleNo);
reqVO.setInfo(info);
// 요청
DataApiResVO resVO = boardService.selectQnA(reqVO);
// 비밀번호 확인
HashMap<String, Object> article = resVO.getInfo();
if(!articlePassword.equals(article.get("articlePassword"))) {
model.addAttribute("message", "비밀빈호가 올바르지 않습니다.(1)");
String shaArticlePassword = nlibPasswordEncoder.encode(articlePassword);
log.debug("updateQnAForm > 입력받은 비밀번호를 SHA256 암호화한 값 : " + shaArticlePassword);
if(!shaArticlePassword.equals(article.get("articlePassword"))) {
model.addAttribute("message", "비밀번호가 올바르지 않습니다.(1)");
return "redirect:/board/selectQnA.do";
}
@ -326,13 +320,16 @@ public class BoardController extends NlibCommonController
model.addAttribute("articleNo" , articleNo);
model.addAttribute("article" , resVO.getInfo());
//-------------------------------
// 게시글 비밀번호 처리
String shaArticlePassword = nlibPasswordEncoder.encode(articlePassword);
String encArticlePassword = ariaCrypto.encode(shaArticlePassword, createAuthKey(req, authentication));
//-------------------------------
// 비밀번호 : SHA-256 암호화 처리되어 DB에 저장되며,
// 화면에 표출될 때는 Aria로 articleNo를 Salt값으로 하여 재암호화처리하고 BASE64로 다시 한번 인코딩하여 표출함
String encArticlePassword = StringUtil.encodeBase64(ariaCrypto.encode(shaArticlePassword, articleNo));
model.addAttribute("encArticlePassword", encArticlePassword);
// 검색 정보
model.addAttribute("pageIndex" , pageIndex);
model.addAttribute("pageIndex" , pageIndex);
model.addAttribute("pageSize" , pageSize);
model.addAttribute("searchKeyword", searchKeyword);
@ -354,8 +351,15 @@ public class BoardController extends NlibCommonController
// 게시물 번호
String articleNo = paramMap.get("articleNo");
//-------------------------------
// 게시글 비밀번호 처리
//-------------------------------
// 비밀번호 : SHA-256 암호화 처리되어 DB에 저장되며,
// 화면에 표출될 때는 Aria로 articleNo를 Salt값으로 하여 재암호화처리하고 BASE64로 다시 한번 인코딩하여 표출함
String encArticlePassword = paramMap.get("encArticlePassword");
String checkArticlePassword = ariaCrypto.decode(encArticlePassword, authKey);
String checkArticlePassword = ariaCrypto.decode(StringUtil.decodeBase64(encArticlePassword), articleNo);
String articlePassword = paramMap.get("articlePassword");
//-------------------------------
// 비밀번호 유효 체크
@ -368,11 +372,20 @@ public class BoardController extends NlibCommonController
DataApiResVO resVO = boardService.selectQnA(reqVO);
String oldArticlePassword = resVO.getInfoItem("articlePassword");
if(checkArticlePassword == null || !checkArticlePassword.equals(oldArticlePassword)) {
message = "잘못된 접근입니다.";
// TODO 수정화면으로 다시 redirect 시킬것
return "redirect:/board/updateQnAForm.do";
model.addAttribute("errorMessage", new ErrorMessage("ERR_BRD_UPDTQNA", "잘못된 접근입니다."));
return "redirect:/alert/showError.do";
}
// 글비밀번호가 존재하지 않는 경우, 기존 비밀번호 설정 처리
if(StringUtil.isEmpty(articlePassword)) {
articlePassword = checkArticlePassword;
}
// 글비밀번호가 입력된 경우, SHA256 암호화 처리하여 설정
else {
articlePassword = nlibPasswordEncoder.encode(articlePassword);
}
//-------------------------------
// REQ VO 구성
//-------------------------------
@ -384,6 +397,7 @@ public class BoardController extends NlibCommonController
updateReqVO.addInfoItem("regUserName" , paramMap.get("regUserName"));
updateReqVO.addInfoItem("email" , paramMap.get("email"));
updateReqVO.addInfoItem("contentQeust", paramMap.get("contentQeust"));
updateReqVO.addInfoItem("articlePassword", articlePassword); // SHA256 글비밀번호
// 요청
DataApiResVO updateResVO = boardService.updateQnA(updateReqVO);
@ -391,18 +405,19 @@ public class BoardController extends NlibCommonController
// 처리결과 확인
if(!updateResVO.isSuccess()) {
message = updateResVO.getResultMessage();
addParamsToModel(paramMap, model);
return "redirect:/board/updateQnAForm.do";
}
// 반환 정보
model.addAttribute("articleNo" , updateResVO.getInfoItem("articleNo")); // 신규등록된 게시물 ID
model.addAttribute("article" , resVO.getInfo());
model.addAttribute("message" , "성공적으로 수정처리되었습니다.");
// 검색 정보
model.addAttribute("pageIndex" , pageIndex);
model.addAttribute("pageSize" , pageSize);
model.addAttribute("searchKeyword", searchKeyword);
model.addAttribute("message" , "");
return "redirect:nlib/board/selectQnA";
}
@ -412,5 +427,4 @@ public class BoardController extends NlibCommonController
return "nlib/board/deleteQnA";
}
}

View File

@ -3,6 +3,7 @@ package nlib.cmm;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
@ -12,6 +13,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.ui.ModelMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -21,6 +23,7 @@ import nlib.cmm.exception.ErrorMessage;
import nlib.restful.service.DataApiResVO;
import nlib.security.SecUserVO;
import nlib.user.service.NlibLoginVO;
import nlib.util.StringUtil;
/**
* <pre>
@ -174,5 +177,22 @@ public class NlibCommonController {
return ResponseEntity.ok().headers(headers).body(jsonStr);
}
public ModelMap addParamsToModel(Map<String, String> paramMap, ModelMap model) {
return addParamsToModel(paramMap, model, null, null);
}
public ModelMap addParamsToModel(Map<String, String> paramMap, ModelMap model, String upperMapName, String itemNames) {
if(paramMap == null || paramMap.size() < 1) return model;
if(model == null) return model;
boolean existUpperMap = StringUtil.isNotEmpty(upperMapName);
Map<String, String> uppperMap = new HashMap<String, String>();
for(String key : paramMap.keySet()) {
model.addAttribute(key, paramMap.get(key));
}
return model;
}
}

View File

@ -13,5 +13,21 @@ public class ErrorMessage {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@ -0,0 +1,55 @@
package nlib.cmm.web;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import nlib.cmm.exception.ErrorMessage;
/**
* <pre>
* @Class Name : AlertController.java
*
* @Description : 각종 처리 결과/오류 안내관련 공통 컨트롤러 클래스
*
*
* @프로젝트명: 지방문화원 통합자료관리시스템 구축사업 (2021)
*
* </pre>
*
* @ ------------ -------- ---------------------------
* @ 수정일 수정자 수정내용
* @ ------------ -------- ---------------------------
* @ 2021. 7. 29. KNKIM 최초 생성
*
*
* @author 이씨플라자 * DIGITALSHIP KNKIM
* @since 2021. 7. 29.
* @version 1.0
*
*/
@Controller
public class AlertController {
private static final Logger log = LoggerFactory.getLogger(SystemController.class);
/**
* NLIB Property 재로드 작업을 요청한다.
*
* @param authentication
* @param model
* @return
*/
@RequestMapping( {"/alert/showError.do"} )
public String showError(@RequestParam ErrorMessage errorMessage, ModelMap model) {
model.addAttribute("code", errorMessage.getCode());
model.addAttribute("message", errorMessage.getMessage());
return "nlib/cmm/common";
}
}

View File

@ -2,6 +2,9 @@ package nlib.util;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;
import java.util.Locale;
import egovframework.com.utl.fcc.service.EgovStringUtil;
@ -107,4 +110,31 @@ public class StringUtil extends EgovStringUtil {
return rtnStr;
}
/**
* Base64로 인코딩한다.
*
* @return
*/
public static String encodeBase64(String str) {
if(str == null) return null;
Encoder encoder = Base64.getEncoder();
return new String(encoder.encode(str.getBytes()));
}
/**
* Base64로 디코딩한다.
*
* @return
*/
public static String decodeBase64(String str) {
if(str == null) return null;
Decoder decoder = Base64.getDecoder();
return new String(decoder.decode(str.getBytes()));
}
}

View File

@ -38,12 +38,7 @@
<head>
<title>${pageTitle}</title>
<!-- GRID -->
<link type="text/css" rel="stylesheet" href="/nlib/js/jsgrid/nlib-jsgrid.css" />
<link type="text/css" rel="stylesheet" href="/nlib/js/jsgrid/nlib-jsgrid-theme.css" />
<script src="/nlib/js/jsgrid/nlib-jsgrid.js"></script>
<script type="text/javaScript" language="javascript">
<script type="text/javaScript">
//선택한 게시글 상세 보기로 이동
function fn_viewDetail(articleNo) {
@ -58,24 +53,18 @@ function fn_list() {
$("#articleForm").submit();
}
var fn_updateForm = function (passwd) {
if(passwd == null || passwd == "") {
alert("수정을 위한 게시물의 비밀번호를 입력하여 주시기 바랍니다.");
return;
}
$("#articlePassword").val(passwd);
$("#articleForm").attr("action", "${pageContext.request.contextPath}/board/updateQnAForm.do");
$("#articleForm").submit();
}
// 수정 처리
function fn_update() {
if(!confirm("저장하시겠습니까?")) return;
if(!validEmail($("#email").val())) {
alert("이메일 형식에 맞지 않습니다. 다시 확인하여 주시기 바랍니다.");
$("#email").focus();
return false;
}
$("#articleForm").attr("action", "${pageContext.request.contextPath}/board/updateQnA.do");
$("#articleForm").submit();
if(!confirm("저장하시겠습니까?")) return false;
return true;
}
</script>
@ -89,8 +78,9 @@ function fn_update() {
<br />
<form name="articleForm" id="articleForm"
action="${pageContext.request.contextPath}/board/upateQnA.do"
method="post">
action="${pageContext.request.contextPath}/board/updateQnA.do"
method="post"
onsubmit="return fn_update();">
<!-- HIDDEN 영역 : 시작 -->
<input type="text" name="pageIndex" id="pageIndex" title="페이지번호" value="${pageIndex}" readonly />
@ -109,19 +99,21 @@ function fn_update() {
</tr>
<tr>
<th>작성자</th>
<td><input type="text" name="regUserName" id="regUserName" title="제목" value="${article.regUserName}" size="100" maxlength="200" /></td>
<td><input type="text" name="regUserName" id="regUserName" title="제목" value="${article.regUserName}" size="100" maxlength="200" required /></td>
</tr>
<tr>
<th>이메일</th>
<td><input type="text" name="email" id="email" title="이메일" value="${article.email}" size="100" maxlength="200" /></td>
<td><input type="email" name="email" id="email" title="이메일" value="${article.email}" size="100" maxlength="200" required /></td>
</tr>
<tr>
<th>기존비밀번호</th>
<td><input type="text" name="encArticlePassword" id="encArticlePassword" title="제목" value="${encArticlePassword}" size="100" maxlength="200" readonly /></td>
</tr>
<tr>
<th>비밀번호</th>
<td><input type="text" name="articlePassword" id="articlePassword" title="비밀번호" value="${article.articlePassword}" size="100" maxlength="200" /></td>
<th>비밀번호<br/>(변경시 입력)</th>
<td><input type="text" name="articlePassword" id="articlePassword" title="비밀번호" value="${article.articlePassword}" size="100" maxlength="200" required /><br/>
* 비밀번호 : SHA-256 암호화 처리 후, DB에 저장되며, 웹 화면에 표출될 때는 Aria로 AuthKey를 Salt값으로 하여 재암호화처리하고 BASE64로 다시한번 인코딩하여 표출함
</td>
</tr>
<tr>
<th>질문내용</th>
@ -131,10 +123,9 @@ function fn_update() {
<br/>
<button name="btnNew" id="btnNew" title="취소버튼" class="float"
onclick="fn_viewDetail('${articleNo}')">취소</button>
<button type="submit" name="btnNew" id="btnNew" title="저장버튼" class="float"
onsubmit="fn_update()">저장</button>
<input type="button" name="btnNew" id="btnNew" title="취소버튼" class="float" value="취소"
onclick="fn_viewDetail('${articleNo}')" />
<input type="submit" name="btnNew" id="btnNew" title="저장버튼" class="float" value="저장" />
</form>

View File

@ -45,7 +45,25 @@ function fn_getIntValue(name, defaultValue) {
}
const POPUP_DEFAULT_WIDTH = 600; // 팝업 기본 창 가로 크기
const POPUP_DEFAULT_HEIGHT = 600; // 팝업 기본 창 세로 크기
/**
* 팝업을 생성하고 해당 URL로 접속한다.
*
* @param url
* @param popName
* @param width 생략 POPUP_DEFAULT_WIDTH
* @param height 생략 POPUP_DEFAULT_HEIGHT
* @returns
*/
function openPopup(url, popName, width, height) {
var options = "top=10, left=10, width=" + (width?width:600) + ", height=" + (height?height:600) + ", status=no, menubar=no, toolbar=yes, resizable=no";
var options = "top=10, left=10, width=" + (width?width:POPUP_DEFAULT_WIDTH) + ", height=" + (height?height:POPUP_DEFAULT_HEIGHT) + ", status=no, menubar=no, toolbar=yes, resizable=no";
window.open(url, popName, options);
}
function validEmail(email) {
if(!email) return false;
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
}