웹 프로그래밍을 하다보면 회원가입과 로그인 기능을 구현하게 된다.

이때 비밀번호는 중요한 개인 정보로서 운영자도 알 수 없게 암호화하여 저장을 하는 것을 요구하고 있다.

여기서는 어떻게 하면 암호화하여 저장할 수 있는지 기술한다.

security.zip

첨부한 소스코드는 spring mvc 프로젝트로 만든 sample 코드이다.

파일 구성은 다음과 같다.

- HomeController.java : home()

  + 프로젝트가 시작되면 호출된다. home.jsp를 보여준다.

- home.jsp : 메인 화면으로 회원가입과 로그인에 대한 메뉴 버튼이 있다.

  + 회원가입 메뉴를 누르면 회원가입 입력창으로 이동

  + 로그인 메뉴를 누르면 로그인 입력창으로 이동

- joinForm.jsp : 회원가입 정보(아이디, 비밀번호)를 입력 받아 서버로 회워가입을 요청한다.

- HomeController.java : join() - GET

  + id와 pw를 입력 받고, pw를 암호화하여 임시로 저장하는 클래스에 저장한다.

  + joinSuccess.jsp를 보여준다.

- joinSuccess.jsp

  + 회원가입 성공을 알리고

  + 홈(메인)으로 이동하는 메뉴를 둔다.

- loginForm.jsp : 로그인에 필요한 정보(id, password)를 입력 받아 서버로 로그인을 요청한다.

- HomeController login() - Post

  + 로그인 정보 (id, pw)로 입력받은 것과

  + 임시로 저장한 암호화된 비밀번호를 가져와

  + 로그인 시 입력한 비밀번호와 암호화된 비밀번호를 이용하여 사용자에 대한 비밀번호 검증을 한다.

  + 성공하면 loginSuccess.jsp로 이동하고

  + 실패하면 loginFail.jsp로 이동한다.

- loginSuccess.jsp : 로그인 성공을 알리고, 홈으로 이동하는 메뉴를 보여준다.

- loginFail.jsp : 로그인 실패를 알리고 홈으로 이동하는 메뉴를 보여준다.

- Utilities.java

  + makeSha256BCrypt() : 비밀번호를 암호화 한다. 

  + checkSha256BCrypt() : 로그인시 입력한 비밀번호와 데이터베이스에 저장된 비밀번호를 사용하여 비밀번호 검증을 수행한다.

- SHA256.java : SHA 알고리즘을 수행한다.

- BCrypt.java : 암호화나 복호화를 수행한다.

Posted by 세상을 살아가는 사람
,

게시판의 본문을 블로그 작성하듯 도와주는 소프트웨어로 네이버에서 제공하는 스마트에디터가 있다. 이것을 스프링 MVC 프로젝트에서 적용하는 방법을 기술한다.

1. 이클립스에서 File>New>Spring Legacy Project>Spring MVC Project로 새로운 스프링 프로젝트 se를 생성한다.

  - 새로 만든 스프링 프로젝트는 기본적으로 "/"가 HomeController에서 동작하여 home.jsp를 처움에 보여준다.

2. home.jsp를 "게시글 쓰기" 형태로 수정한다.

1 <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>

 2 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

 3 <%@ page session="false" %>

 4 <html>

 5 <head>

 6  <title>Home</title>

 7  <script type="text/javascript" src="<c:url value="/resources/js/jquery-1.11.3.min.js"/>"></script>

 8  <script type="text/javascript" src="<c:url value="/resources/smarteditor/js/HuskyEZCreator.js"/>" charset="utf-8"></script>

 9  <script type="text/javascript" charset="utf-8" src="<c:url value="/resources/js/writeForm.js"/>"></script>

10  <link rel="stylesheet" href="<c:url value="/resources/css/common.css"/>"/>

11  <script type="text/javascript" charset="utf-8">

12  sessionStorage.setItem("contextpath", "${pageContext.request.contextPath}");

13  </script>

14 </head>

15 <body>

16 <form action="write" method="post" id="frm">

17  <table>

18  <tr>

19  <td>제목</td>

20 <td><input type="text" name="title" id="title" placeholder="제목"></td>

21 </tr>

22 <tr>

23 <td style="margin:0; padding:0;" colspan="2"><textarea name="smarteditor" id="smarteditor" rows="10" cols="100" style="width:100%; height:412px;"></textarea></td>

24 </tr>

26 </table>

27 <input id="addBtn" type="submit" value="전송">

28 </form>

29 </body>

30 </html>


1줄 : pageEncoding을 UTF-8로 설정

 7줄 : Ajax Operation을 위해 jquery js (jquery-1.11.3.min.js)를 사용

 8줄 : 스마트에디터를 사용하기 위해 필요

 9줄 : javascript를 정의하기 위해 writeForm.js를 사용

10줄 : 디자인을 깨끗하게 하기 위해 common.css를 사용

11줄~13줄 : contextPath를 javascript에서 사용하기 위해 sessionStorage에 저장

16줄 : form을 사용. "전송" 버튼을 누르면 javascript에서 textarea에 입력된 데이터를 가져와 request에 추가하도록 처리를 한다. 이후 서버로 입력 파라미터들이 전달되고 action=write가 서버에서 수행됨.

23줄 : 스마트에디터를 사용하여 본문을 입력하도록 정의해 준다.

27줄 : 전송 버튼을 누르면 javascript가 동작

3. smarteditor 파일들 : http://naver.github.io/smarteditor2/user_guide/ 참조

  - SmartEditor Basic 2.0은 JavaScript로 구현된 웹 기반의 WYSIWYG 편집기이다. 글꼴, 글자 크기, 줄 간격 등을 자유롭게 설정할 수 있으며, 단어 찾기/바꾸기와 같은 편리한 기능을 제공한다.

  - https://github.com/naver/smarteditor2에서 최신 버전을 다운로드

    -> 이 버전은 사진(이미지) 업로드 기능이 삭제된 것이다. 무슨 이유인지 모르겠지만 네이버가 이 기능을 배포판에서 빼 버렸다.

se.zip

  - 디렉토리 구조는 다음과 같다.

    + src/main/webapp/resources/smarteditor 디렉토리 밑에 다음과 같은 파일이 존재

    + css

    + img

    + js

    + photo_uploader

      * plugin

      * popup

        # attach_photo.js      // 사진 추가에 대한 javascript 처리 (서버와 연동하여 파일 저장)

        # callback.html         // 사진 추가에 대한 결과 처리

        # file_uploader.php    // 다중 사진(이미지) 본문 추가에 대한 서버 처리 루틴 (PHP 방식)

        # file_uploader_html5.php    // 단일 사진(이미지) 본문 추가에 대한 서버 처리 루틴 (PHP 방식)

        # jindo.fileuploader.js

        # jindo.min.js

        # photo_uploader.html        // popup 창에 대한 HTML 파일

    + smartEditor2Skin.html

    + 일부 파일 생략 (...)


4. writeForm.js

 1 $(function() {

 2   //전역변수선언

 3   var editor_object = [];

 4   var ctx = getContextPath();

 5

 6   nhn.husky.EZCreator.createInIFrame({    // 입력 창을 구성하는 부분을 구동

 7     oAppRef: editor_object,

 8     elPlaceHolder: "smarteditor",

 9     sSkinURI: ctx + "/resources/smarteditor/SmartEditor2Skin.html",

10     htParams : {

11       // 툴바 사용 여부 (true:사용/ false:사용하지 않음)

12       bUseToolbar : true,             

13       // 입력창 크기 조절바 사용 여부 (true:사용/ false:사용하지 않음)

14       bUseVerticalResizer : true,     

15       // 모드 탭(Editor | HTML | TEXT) 사용 여부 (true:사용/ false:사용하지 않음)

16       bUseModeChanger : true,

17       fOnBeforeUnload : function(){

18                

19       }

20     }

21   });

22    

23   //전송버튼 클릭이벤트

24   $("#addBtn").click(function(){

25     //id가 smarteditor인 textarea에 에디터에서 대입

26     editor_object.getById["smarteditor"].exec("UPDATE_CONTENTS_FIELD", []);

27         

28     // 이부분에 에디터 validation 검증 : 때에 따라 가비지 correction 등에 대한 처리 필요

29     var el = document.createElement('html');

30     el.innerHTML = editor_object.getById["smarteditor"].elPlaceHolder.value;    // 본문 정보 넣기

31     //폼 submit

32     $("#frm").submit();    // 서버로 전송 (게시글 추가 처리 요청)

33   });

34    

35   function getContextPath() {

36     return sessionStorage.getItem("contextpath");

37   }

38 });


6~21줄 : home.jsp에서 사용한 textarea 부분을 구성해 주는 부분

23~33줄 : textarea에 입력한 정보를 "smarteditor"란 parameter에 넣어주고 form 형태로 서버로 전송


5. 사진(이미지) 추가에 대한 동작 처리 : smarteditor/photo_uploader/popup/attach_photo.js

  - 사진(이미지)를 추가하는 방법은 하나의 이미지만 추가 하는 방법과 여러 개의 사진을 추가하는 방법으로 두 가지를 제공한다. 필요에 따라 자신에게 맞는 방법을 선택한다.

    + 아래 처럼 bSupportDragAndDropAPI 값을 true/false로 설정함에 따라 다르게 설정된다.

        //File API 지원 여부로 결정

function checkDragAndDropAPI(){

try{

if( !oNavigator.ie ){

if(!!oNavigator.safari && oNavigator.version <= 5){

bSupportDragAndDropAPI = false;

}else{

bSupportDragAndDropAPI = true; // multiple 파일 업로드

// bSupportDragAndDropAPI = false; // single 파일 업로드

}

} else {

bSupportDragAndDropAPI = false;

}

}catch(e){

bSupportDragAndDropAPI = false;

}

}

  - function html5Upload()    // 하나의 사진 이미지 추가에 대한 처리

    + popup 창이 뜨고, 다음을 선택한 후 확인 버튼을 누르면, 선택한 사진을 서버로 전송하는 부분

    + sUploadURL = '/se/file_uploader_html5';    // 사진 업로드 URL

  - function callFileUploader()    // 다중 사진 이미지 추가에 대한 처리

    + sUrl : '/se/file_uploader',    // 서버에서 처리하는 URL

    + sCallback : '/resources/smarteditor/photo_uploader/popup/callback.html',    // 처리 결과 응답

6. HomeController.java    // 사진 이미지 추가에 대한 처리

  - resources/upload 디렉토리에 사진 이미지 저장 (실제 저장되는 폴더는 workspace/.metadata/.plugins/\org.eclipse.wst.server.core\tmp1\wtpwebapps\se\resources\upload 밑에 저장됨

@RequestMapping(value="/file_uploader", method=RequestMethod.POST)

public void file_uploader(HttpServletRequest request, HttpServletResponse response) throws IOException, FileUploadException {

  request.setCharacterEncoding("utf-8");

  String return1="";

  String return2="";

  String return3="";

  String name = "";

  if (ServletFileUpload.isMultipartContent(request)){

    ServletFileUpload uploadHandler = new ServletFileUpload(new DiskFileItemFactory());

    //UTF-8 인코딩 설정

    uploadHandler.setHeaderEncoding("UTF-8");

    List<FileItem> items = uploadHandler.parseRequest(request);

    //각 필드태그들을 FOR문을 이용하여 비교를 합니다.

    for (FileItem item : items) {

      if(item.getFieldName().equals("callback")) {

return1 = item.getString("UTF-8");

      } else if(item.getFieldName().equals("callback_func")) {

return2 = "?callback_func="+item.getString("UTF-8");

      } else if(item.getFieldName().equals("Filedata")) {

//FILE 태그가 1개이상일 경우

if(item.getSize() > 0) {

  name = item.getName();

  String ext = item.getName().substring(item.getName().lastIndexOf(".")+1);

  //파일 기본경로

  String defaultPath = context.getRealPath("/");

  //파일 기본경로 _ 상세경로

  String path = defaultPath + "upload" + File.separator;

                

  File file = new File(path);

          //디렉토리 존재하지 않을경우 디렉토리 생성

          if(!file.exists()) {

            file.mkdirs();

          }

          //서버에 업로드 할 파일명(한글문제로 인해 원본파일은 올리지 않는것이 좋음)

          String realname = UUID.randomUUID().toString() + "." + ext;

          ///////////////// 서버에 파일쓰기 ///////////////// 

          InputStream is = item.getInputStream();

          OutputStream os=new FileOutputStream(path + realname);

          int numRead;

          byte b[] = new byte[(int)item.getSize()];

          while((numRead = is.read(b,0,b.length)) != -1){

            os.write(b,0,numRead);

          }

  if(is != null)  is.close();

  os.flush();

  os.close();

  ///////////////// 서버에 파일쓰기 /////////////////

  String root = request.getContextPath();

          return3 += "&bNewLine=true&sFileName="+name+"&sFileURL=" + root + "/upload/"+realname; // by ksseo

}else {

  return3 += "&errstr=error";

}

      }

    }

  }

  response.sendRedirect(return1+return2+return3);

}


  - 추후 가비지 correction이 진행되어야 함. (사진 이미지를 추가한 후 삭제할 경우, 또는 사진을 추가하면서 글을 작성하다가 다른 창으로 이동할 경우 등) -> writeForm.js에서 처리해 주어야 함


  - form에 대한 처리 : 데이터베이스에 게시글 정보 저장 (여기서는 생략...)

@RequestMapping(value = "/write", method = RequestMethod.POST)

public ModelAndView write(HttpServletRequest request) throws IOException, FileUploadException {

  String title = request.getParameter("title");

  String smarteditor = request.getParameter("smarteditor");


  ModelAndView model = new ModelAndView("write");

  model.addObject("title", title);

  model.addObject("smarteditor", smarteditor);

  return model;

}


7. pom.xml 고려사항 : file upload dependency 추가

<!-- file upload -->

<dependency>

  <groupId>commons-fileupload</groupId>

  <artifactId>commons-fileupload</artifactId>

  <version>1.3.1</version>

</dependency>


8. servlet-context.xml 고려사항 : multipartResolver 추가

<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

  <beans:property name="maxUploadSize" value="52428800"></beans:property>

</beans:bean>


9. write.jsp : 게시글 추가 결과를 보여주는 뷰

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE html>

<html>

<head>

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>홈 페이지</title>

</head>

<body>

게시글 작성 완료

제목 : ${title}<br>

본문 : <br>

${smarteditor}

</body>

</html>


10. common.css : 게시글 쓰기 CSS 파일

@CHARSET "UTF-8";

* {

margin: 0px;

padding: 0px;

border: 0;

}

html[lang^="ko"] {

font-family: "Nanum Gothic", "나눔 고딕", "NanumGothic", sans-serif;

}

/* content */

body {

width: 980px;

margin: 0 auto;

margin-top: 5px;

min-height: 770px;

}

form {

margin-top: 20px;

line-height: 2;

}

#addBtn {

width: 200px;

font-size: 14px;

padding: 10px;

background-color: blue;

color: white;

border-radius: 5px 5px 5px 5px;

margin-top:30px;

}

table {

border: 1px solid black;

border-collapse: collapse;

border-spacing: 0px;

margin-top: 20px;

width: 100%;

}

td {

border: 1px solid black;

border-collapse: collapse;

border-spacing: 0px;

padding: 5px;

line-height: 1.5;

}


가비지 correction 과정은 추후 기술한다.

수고 하셨습니다.


참고 : http://hellogk.tistory.com/63

Posted by 세상을 살아가는 사람
,

구글 플레이 스토어에서 운영 중인 "클래식 음악 -600곡"의 2017년 1월 1달 동안 사용자들이 이용한 Top 5 (Play 및 다운로드)

Play Top 5

[1] 아베 마리아, 구노

[2] 전주곡 1권 중에서 No.8 La Fille aux Cheveux de Lin (arr. Hartmann), 드뷔시

[3] "카벨레리아 르스티카나" Intermezzo sinfonico, 마스카니

[4] 바흐 관현악 모음곡 3번 D장조 BWV 1068 II. Air, 바흐

[5] 클라리넷 5중주 A장조 KV 581 II. Larghetto, 모차르트

Download Top 5

[1] 피아노 소나타 14번 C sharp단조 op. 27-2 "월광" I. Adagio sostenuto, 베토벤

[2] 트로이메라이 - 어린이 정경 op. 15-7 (실내악 편곡), 슈만

[3] 자장가 D flat장조 op. 57, 쇼팽

[4] 동물의 사육제 Le Cygne, 생상스

[5] 타이스의 명상곡 (2010~2011년 시즌, 갈라쇼), 마스네


클래식음악 - 600곡 https://play.google.com/store/apps/details?id=com.talanton.music.player

Posted by 세상을 살아가는 사람
,