JSP를 만드는데 페이지를 새로고침 하지 않고


페이지의 정보를 바꾸고 싶을때 Ajax를 사용한다.


예를들어 네이버의 실시간 검색순위를 보면 일정 시간마다 리스트가 계속 바뀐다. 새로고침은 하지 않고


또 회원가입 시 아이디 중복체크를 누르면 페이지는 그대로 있고 중복인지 아닌지 메세지가 뜬다.


이러한 기능들은 Ajax를 통해 구현된다.


JSP 문서에서 Jquery 문에 Ajax를 사용하고


Servlet에 데이터를 전달받아서 Servlet에서 데이터를 처리 후


Json을 사용하여 데이터를 포장하여 JSP 문서로 전달하게 된다.


하지만 보안 작업에는 적합하지 않으므로 작업에 따라 적절히 사용하자.



Ajax 장단점


- 장점 -


비동기식 방식으로 웹서버 응답을 기다리지 않고 데이터를 빠르게 처리한다.


예로 실시간 검색순위나 자동 완성이 있다.



- 단점 -


새로고침 하지 않고 계속 사용한다면 리소스가 계속 쌓여 페이지가 느려짐


JQ 오류와 같이 오류를 찾기 힘듦







비 동기식 처리모델 : 페이지가 로딩 될 때 먼저 서버에서 연산작업을 요청하고 계속 로딩한다. 작업에 대한 답은 기다리지 않고 처리된다.


동기식 처리모델 : 페이지가 로딩 될 때 서버의 연산을 모두 기다리고 페이지의 연산 처리가 끝나면 나머지 부분을 로드한다.



사용법으로는 먼저 API 사용에 필요한 라이브러리 파일을 받자


파일은 JSON


GSON 파일이 있다.




- JSON -


www.java2s.com/Code/Jar/j/Downloadjsonsimple11jar.htm


위 링크로 들어가면 다운로드가 바로 보인다.




해당 파일을 lib 폴더에 추가하면 된다.








- GSON -


위 링크로 들어가 Gson을 고르고 다음 캡쳐와 같이 진행






해당 파일을 받아 lib에 추가한다.








이와 같이 라이브러리에 추가가 끝나면 JSON, GSON을 사용하는데 문제가 없을 것이다.


Ajax는 Jsp 파일에서 스크립트 단에서 사용하며


특정 동작이 있을 때 페이지를 변경(새로고침)하지 않고 Servlet에 통신 후 데이터를 받아와 변경






$(function(){ // 페이지 시작 시

$.ajax({

url:"서블릿 맵핑 주소",
data:{보낼 데이터 Key : "보낼 데이터 Value"},
type:"get",
 success: function(data) {
    // success는 서버에서 통신 성공 시 받을 자료를 받는다(data)

    // 통신만 성공해도 success로 넘어간다는 것을 주의하자.
    console.log("서버 전송 성공" + data);
},
error:function(data){ // 데이터 통신에 실패한 것
    console.log("서버 전송 실패");   
},
complete:function(data){
    console.log("무조건 호출되는 함수");
}

});

});




이와같이 Ajax는 Jquery를 이용해 서블릿 통신을 하고 그 결과를 통해서 JQuery를 작성하여 여러가지 기능을 쓸 수 있다.


다음은 서블릿에서 다시 Ajax 구문에 data를 전송하는 부분


    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        List<User> userList = new ArrayList<User>();
       
        User user1 = new User(1, "호이1", "외계");
        User user2 = new User(2, "호이2", "외계");
        User user3 = new User(3, "호이3", "외계");
        User user4 = new User(4, "호이4", "외계");
        User user5 = new User(5, "호이5", "외계");
        User user6 = new User(6, "호이6", "외계");
        User user7 = new User(7, "호이7", "외계");
        User user8 = new User(8, "호이8", "외계");
        User user9 = new User(9, "호이9", "외계");
        User user10 = new User(10, "호이10", "외계");
       
        userList.add(user1);
        userList.add(user2);
        userList.add(user3);
        userList.add(user4);
        userList.add(user5);
        userList.add(user6);
        userList.add(user7);
        userList.add(user8);
        userList.add(user9);
        userList.add(user10);
       
        int userIndex = Integer.parseInt(request.getParameter("받은 데이터 Key 값"));
       
        User user = userList.get(userIndex - 1);
       
        // 자바에서 자바 스크립트에 알아먹을 수 있게 객체를 바꿔주는 기능(key, value 형식)이 JSON
        // 라이브러리 json simple이 필요
        JSONObject result = new JSONObject();
       
        // 값을 그냥 전달 할 수 없다. 한글이 있으므로 UTF-8 처리를 해야한다.
        result.put("userNo", user.getUserNo());
        result.put("userName", URLEncoder.encode(user.getUserName(), "UTF-8"));
        result.put("userNation", URLEncoder.encode(user.getUserNation(), "UTF-8"));
       
        response.setContentType("application/json");
       
        PrintWriter out = response.getWriter();
       
        out.print(result.toJSONString());
       
        // <textarea>value</textarea> HTML 형태는 textarea가 key가 된다.
        // <key>value</key> xml형태는 key를 html보다 제한적이지 않고 자유롭게 사용 할 수 있다.
        // {key:value} JSON 형태는 xml html보다 쓰기 쉽고 짧아서 바뀌는 추세이다.
       
       
        out.flush();
        out.close();

}





위와 같이 서블릿에서 따로 데이터를 JSON으로 처리를 해야한다.


처리하고 나서 데이터를 전송하는데 과정이 까다롭기 때문에 Gson이라는 오픈소스를 사용하면 편하다.



    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Map<String, User> userMap = new HashMap<String, User>();
       
        User user1 = new User(1, "호이1", "외계");
        User user2 = new User(2, "호이2", "외계");
        User user3 = new User(3, "호이3", "외계");
        User user4 = new User(4, "호이4", "외계");
        User user5 = new User(5, "호이5", "외계");
        User user6 = new User(6, "호이6", "외계");
        User user7 = new User(7, "호이7", "외계");
        User user8 = new User(8, "호이8", "외계");
        User user9 = new User(9, "호이9", "외계");
        User user10 = new User(10, "호이10", "외계");
       
        userMap.put(user1.getUserName(), user1);
        userMap.put(user2.getUserName(), user2);
        userMap.put(user3.getUserName(), user3);
        userMap.put(user4.getUserName(), user4);
        userMap.put(user5.getUserName(), user5);
        userMap.put(user6.getUserName(), user6);
        userMap.put(user7.getUserName(), user7);
        userMap.put(user8.getUserName(), user8);
        userMap.put(user9.getUserName(), user9);
        userMap.put(user10.getUserName(), user10);
       
       
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        new Gson().toJson(userMap, response.getWriter());

       
    }


위와같이 긴 코드가 3줄로 변하는것을 확인 할 수 있다.












직접 사용해보면 Ajax는 실시간이라기 보다는


개발자가 어떤 버튼, 동작에 상호작용하여 어떤 서버와의 통신이 필요할 때 사용할 작업을 설정하는 것이다.


이와 달리 실시간으로 계속 통신을 하고 싶다면 JSP Socket 통신을 이용하면 된다.


일반 게시판에서 사진을 업로드 할 때 방법을 설명


먼저 JSP 파일


------------------------------------------------------------------------------------------------------------------------------------

출처: https://qdgbjsdnb.tistory.com/ [하위^^]
------------------------------------------------------------------------------------------------------------------------------------

출처: https://qdgbjsdnb.tistory.com/ [하위^^]

------------------------------------------------------------------------------------------------------------------------------------

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<style>
    .outer {
        width:1000px;
        height:650px;
        background:black;
        color:white;
        margin-left:auto;
        margin-right:auto;
        margin-top:50px;
    }
    table {
        border:1px solid white;
    }
    .insertArea {
        width:500px;
        height:450px;
        margin-left:auto;
        margin-right:auto;
    }
    .btnArea {
        width:150px;
        margin-left:auto;
        margin-right:auto;
    }
    #titleImgArea {
        width:350px;
        height:200px;
        border:2px dashed darkgray;
        text-align:center;
        display:table-cell;
        vertical-align:middle;
    }
    #contentImgArea1, #contentImgArea2, #contentImgArea3 {
        width:150px;
        height:100px;
        border:2px dashed darkgray;
        text-align:center;
        display:table-cell;
        vertical-align:middle;
    }
</style>
</head>
<body>
    <%@ include file="../common/menubar.jsp" %>
    <% if(loginUser != null) { %>
    <div class="outer">
        <br>
        <h2 align="center">사진 게시판 작성</h2>
        <form action="<%=request.getContextPath() %>/insert.tn" method="post" encType="multipart/form-data"> <!-- 파일 전송 시 encType을 지정해야 한다. -->
            <div class="insertArea">
                <table align="center">
                    <tr>
                        <td>제목</td>
                        <td colspan="3"><input type="text" size="45" name="title"></td>
                    </tr>
                    <tr>
                        <td>대표 이미지</td>
                        <td colspan="3">
                            <div id="titleImgArea">
                                <img id="titleImg" width="350" height="200">
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>내용사진</td>
                        <td>
                            <div id="contentImgArea1">
                                <img id="contentImg1" width="120" height="100">
                            </div>
                        </td>
                        <td>
                            <div id="contentImgArea2">
                                <img id="contentImg2" width="120" height="100">
                            </div>
                        </td>
                        <td>
                            <div id="contentImgArea3">
                                <img id="contentImg3" width="120" height="100">
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td width="100px">사진 메모</td>
                        <td colspan="3">
                            <textarea name="content" rows="5" cols="50" style="resize:none;"></textarea>
                        </td>
                    </tr>
                </table>
                <div id="fileArea">
                    <input type="file" id="thumbnailImg1" name="thumbnailImg1" onchange="loadImg(this, 1)">
                    <input type="file" id="thumbnailImg2" name="thumbnailImg2" onchange="loadImg(this, 2)">
                    <input type="file" id="thumbnailImg3" name="thumbnailImg3" onchange="loadImg(this, 3)">
                    <input type="file" id="thumbnailImg4" name="thumbnailImg4" onchange="loadImg(this, 4)">
                </div>
            </div>
            <div class="btnArea">
                <button>취소하기</button>
                <button type="submit">작성완료</button>
            </div>
        </form>
    </div>
   
    <script type="text/javascript">
        $(function() {
            $("#fileArea").hide(); // 버튼 숨기기
           
            $("#titleImgArea").click(function() {
                $("#thumbnailImg1").click();
            });
            $("#contentImgArea1").click(function() {
                $("#thumbnailImg2").click();
            });
            $("#contentImgArea2").click(function() {
                $("#thumbnailImg3").click();
            });
            $("#contentImgArea3").click(function() {
                $("#thumbnailImg4").click();
            });
        });
        /* function loadImg(value) { // 박스에 파일 업로드 하는 방법
            if (value.files && value.files[0]) {
                var reader = new FileReader();
               
                reader.onload = function(e) {
                    $("#titleImg").attr("src", e.target.result);
                };
               
                reader.readAsDataURL(value.files[0]);
            }
        } */
        function loadImg(value, num) { // value는 업로드한 파일
            if (value.files && value.files[0]) { // 파일을 업로드하면 배열 형태로 들어온다.
                var reader = new FileReader();
               
                reader.onload = function(e) { // reader가 실행되면 적용될 속성
                    switch (num) {
                    case 1 :
                        $("#titleImg").attr("src", e.target.result); // target.result는 업로드한 파일의 임시경로
                        break;
                    case 2 :
                        $("#contentImg1").attr("src", e.target.result);
                        break;
                    case 3 :
                        $("#contentImg2").attr("src", e.target.result);
                        break;
                    case 4 :
                        $("#contentImg3").attr("src", e.target.result);
                        break;
                    }
                };
               
                reader.readAsDataURL(value.files[0]); // reader호출, 파일의 url을 읽어 들임
            }
        }
    </script>
   
    <% } else { %>
    <%    
        request.setAttribute("msg", "잘못된 경로로 접근하셨습니다.");
        request.getRequestDispatcher("../common/errorPage.jsp").forward(request, response);
    %>
    <% } %>
</body>
</html>

------------------------------------------------------------------------------------------------------------------------------------


다른 폼은 여태껏 봐왔던 html 코드들인데


스크립트 부분을 설명하면


        $(function() {
            $("#fileArea").hide(); // 버튼 숨기기
           
            $("#titleImgArea").click(function() {
                $("#thumbnailImg1").click();
            });
            $("#contentImgArea1").click(function() {
                $("#thumbnailImg2").click();
            });
            $("#contentImgArea2").click(function() {
                $("#thumbnailImg3").click();
            });
            $("#contentImgArea3").click(function() {
                $("#thumbnailImg4").click();
            });
        });


이 부분은 이미지 박스를 눌렀을 때 버튼 기능을 실행하게 하고


버튼은 숨겨 안보이게 표시하는 스크립트




        /* function loadImg(value) { // 박스에 파일 업로드 하는 방법
            if (value.files && value.files[0]) {
                var reader = new FileReader();
               
                reader.onload = function(e) {
                    $("#titleImg").attr("src", e.target.result);
                };
               
                reader.readAsDataURL(value.files[0]);
            }
        } */


type="file"에 함수를 만들어 value를 인자로 받았을 때


value는 받은 파일을 의미하게 된다.


value는 여러개가 들어올 수 있으므로 배열 형태로 들어오게 되고


files 라는 메소드는 파일이 있는지 없는지를 반환한다.




FileReader 객체를 생성하고 불러올 때 작동할 함수(onload)는 클릭한 박스의 속성의 이미지를 등록하는 것이다.


function(e) 의 e는 등록한 파일을 의미하고 e.target.result는 서버에 업로드 하기 전에 임시로 지정된 파일의 경로가 된다.


reader.readAsDataURL(value.files[0])은 파일의 경로를 한번 불러오는 것이고 호출했기 때문에 onload가 작동된다.






        function loadImg(value, num) { // value는 업로드한 파일
            if (value.files && value.files[0]) { // 파일을 업로드하면 배열 형태로 들어온다.
                var reader = new FileReader();
               
                reader.onload = function(e) { // reader가 실행되면 적용될 속성
                    switch (num) {
                    case 1 :
                        $("#titleImg").attr("src", e.target.result); // target.result는 업로드한 파일의 임시경로
                        break;
                    case 2 :
                        $("#contentImg1").attr("src", e.target.result);
                        break;
                    case 3 :
                        $("#contentImg2").attr("src", e.target.result);
                        break;
                    case 4 :
                        $("#contentImg3").attr("src", e.target.result);
                        break;
                    }
                };
               
                reader.readAsDataURL(value.files[0]); // reader호출, 파일의 url을 읽어 들임
            }
        }



2번째 스크립트의 확장으로 이미지 선택 박스의 종류 별 기능을 활성화 한다.








submit으로 설정한 버튼을 통해서


<form action="<%=request.getContextPath() %>/insert.tn" method="post" encType="multipart/form-data"> 부분의


<%=request.getContextPath() %>/insert.tn 주소로 맵핑된 부분으로 이동된다.


이동된 부분은 Servlet 파일로 서버에 업로드 된 파일을 처리한다.


단, 파일을 보낼 때는 form 속성에 무조건 encType="multipart/form-data" 를 명시해야 한다.








다음으로는 Servlet 파일

------------------------------------------------------------------------------------------------------------------------------------

package com.kh.jsp.board.controller;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.util.ArrayList;
import java.util.Enumeration;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;

import com.kh.jsp.board.model.service.BoardService;
import com.kh.jsp.board.model.vo.Attachment;
import com.kh.jsp.board.model.vo.Board;
import com.kh.jsp.common.MyFileRenamePolicy;
import com.kh.jsp.member.model.vo.Member;
import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;

@WebServlet("/insert.tn")
public class InsertThumbnailServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
      
    public InsertThumbnailServlet() {
        super();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String title = request.getParameter("title");
        System.out.println(title);
        // encType="multipart/form-data"로 데이터를 넘기면 null이 출력된다.
        // 폼전송을 multipart/form-date로 전송하는 경우에는
        // 기존처럼 request.getParameter로 값을 받을 수 없다.
       
        // cos.jar가 파일도 받고 다른 값들도 받아주는 역할을 한다.
        // com.oreilly.servlet의 약자이다.
        // http://servlets.com/cos/ 맨 아래에서 다운로드해서 lib에 추가한다.
       
        if(ServletFileUpload.isMultipartContent(request)) {
            // 전송 파일 용량 제한 : 10MB로 제한되어 있다. 그 이상은 유료
            int maxSize = 1024 * 1024; // (1mb)로 설정
           
            String root = request.getSession().getServletContext().getRealPath("/");
            System.out.println(root); // 톰캣 설정 첫번째 항목 체크를 안하면 경로가 톰캣으로 잡혀버린다.
            // thumbnale_uploadFiles 라는 폴더가 해당 경로 아래에 생기게 되므로 다음과 같이 파일 경로를 설정하자
           
            String filePath = root + "thumbnale_uploadFiles/";
           
            // MultipartRequest multiRequest = new MultipartRequest(request, filePath, maxSize, "UTF-8", new DefaultFileRenamePolicy());            // 사용자가 올린 파일명을 그대로 저장하지 않는 것이 일반적이다.
            // 1. 같은 파일명이 있는 경우 이전 파일을 덮어 쓸 수 있다.
            // 2. 한글로된 파일명, 특수기호, 띄어쓰기는 서버에 따라 문제가 생길 여지가 있다.
            // DefaultFileRenamePolicy는 cos.jar에서 제공하는 클래스
            // 같은 파일명이 존재하는지를 체크하고 있을 경우에는 뒤에 숫자를 붙여준다.
            // ex) aaa.zip, aaa1.zip, aaa2.zip
            MultipartRequest multiRequest = new MultipartRequest(request, filePath, maxSize, "UTF-8", new MyFileRenamePolicy());
           
            // 다중 파일을 묶어서 업로드 하기 위해 컬렉션 사용
            // 저장한 파일의 이름을 저장할 arrayList 생성
            ArrayList<String> saveFiles = new ArrayList<String>();
           
            // 원본 파일의 이름을 저장 할 ArrayList 생성
            ArrayList<String> originFiles = new ArrayList<String>();
           
            // 각 파일의 정보를 구해온 뒤 DB에 저장할 목적의 데이터를 꺼내온다.
            Enumeration<String> files = multiRequest.getFileNames();
           
            while (files.hasMoreElements()) {
                String name = files.nextElement();
               
                System.out.println(name);
               
                saveFiles.add(multiRequest.getFilesystemName(name));
                originFiles.add(multiRequest.getOriginalFileName(name));
               
                System.out.println("FileSystem name : " + multiRequest.getFilesystemName(name));
                System.out.println("originFile name : " + multiRequest.getOriginalFileName(name));
            }
           
            String multiTitle = multiRequest.getParameter("title");
            String multiContent = multiRequest.getParameter("content");
            System.out.println(multiTitle);
            System.out.println(multiContent);
           
            //Board객체 생성
            Board b = new Board();
            b.setbTitle(multiTitle);
            b.setbContent(multiContent);
            b.setbWriter(String.valueOf(((Member)(request.getSession().getAttribute("loginUser"))).getUno()));
           
            // Attachment 객체 생성하여 arrayList 객체 생성
            ArrayList<Attachment> fileList = new ArrayList<Attachment>();
           
            for(int i = originFiles.size() - 1; i >= 0; i--) {
                Attachment at = new Attachment();
                at.setFilePath(filePath);
                at.setOriginName(originFiles.get(i));
                at.setChangeName(saveFiles.get(i));
               
                fileList.add(at);
               
            }
           
            int result = new BoardService().insertThumbnail(b, fileList);
           
            if (result > 0) {
                response.sendRedirect(request.getContextPath() + "/selectList.tn");
            } else {
                // 실패 시 저장된 사진 삭제
                for (int i = 0; i < saveFiles.size(); i++) {
                    // 파일 시스템에 저장된 이름으로 파일 객체 생성
                    File failedFile = new File(filePath + saveFiles.get(i));
                   
                    // true, false를 리턴함
                    failedFile.delete();
                }
               
                request.setAttribute("msg", "사진게시판 등록 실패!");
                request.getRequestDispatcher("views/common/errorPage.jsp").forward(request, response);
            }
           
           
        }
       
       
    }
   
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

}

------------------------------------------------------------------------------------------------------------------------------------


        // cos.jar가 파일도 받고 다른 값들도 받아주는 역할을 한다.
        // com.oreilly.servlet의 약자이다.
        // http://servlets.com/cos/ 맨 아래에서 다운로드해서 lib에 추가한다.


부분은 사진으로 추가 설명


http://servlets.com/cos/ 링크로 이동




다운받은 압축파일을 풀어 lib폴더에 복사하면 된다.


대부분의 설명은 주석에 되어 있으므로 안 되있는 부분만 설명




Enumeration<String> files = multiRequest.getFileNames(); 경우는 파일을 리스트 형식으로 알아서 불러오는 객체이다.







서블릿 부분에서 데이터를 정리해서 DB 쪽으로 연결하는 코드는 아래 한 줄 부분이다.

int result = new BoardService().insertThumbnail(b, fileList);




다음은 Service 부분

------------------------------------------------------------------------------------------------------------------------------------

public int insertThumbnail(Board b, ArrayList<Attachment> fileList) {
        Connection con = getConnection();
       
        int result = 0;
       
        int result1 = new BoardDao().insertThumbnailContent(con, b);
       
        if (result1 > 0) {
            int bid = new BoardDao().selectCurrval(con);
           
            for(int i = 0; i < fileList.size(); i++) {
                fileList.get(i).setBid(bid);
            }
        }
       
        int result2 = new BoardDao().insertAttachment(con, fileList);
       
        if (result1 > 0 && result2 > 0) {
            commit(con);
            result = 1;
        } else {
            rollback(con);
        }
       
        close(con);
       
       
        return result;
    }

------------------------------------------------------------------------------------------------------------------------------------

이 부분에서는 DB에 3번의 접근이 일어나게 된다.


첫번째는 게시글 테이블에 등록


두번째는 게시글 번호 불러오기(시퀀스를 통해서 등록한 게시글 번호를 불러온다.)


세번째는 첨부파일 테이블에 게시글 번호(외래키)와 함께 파일을 등록한다.







Dao 부분은 전과 같이 DB에 Prestatement나 statement를 사용하여 쿼리문을 사용한다.

여태껏 해온 부분과 비슷하므로 생략


블로그의 예제들에서는 여태까지 DataBase에 비밀번호를 입력한 그대로 저장하여 그대로 불러왔다.


하지만 법적으로?? 비밀번호는 그대로 저장하면 안되고 입력받고 값을


전달 받으면 암호화 하여서 데이터베이스에 저장해야 한다고 한다.





왜냐하면 해커에게 공격을 당한다면 보통 DB가 타겟이 되는데


DB에 암호가 그대로 쓰여져 있다면 개인정보 뿐만 아니라 암호까지 그대로 전달되어 버린다.


그러므로 비밀번호는 절대 그대로 저장하면 안된다.





JSP에서 비밀번호를 암호화 하는 구조는 다음과 같다.


기존의 MVC를 적용한 구조가 [View - 비밀번호 입력 ->> Controller(Servlet) - 비밀번호를 받음 ->> Service ->> Dao ] 순으로 전달이 되는 구조인데


암호화 (Wrapper : 포장, 싸다)를 적용하면 다음과 같은 구조가 된다.


[View - 비밀번호 입력 ->> Wrapper(Filter), 비밀번호 암호화 ->> Controller(Servlet) - 암호화된 비밀번호를 받음 ->> Service ->> Dao ]


비밀번호를 입력 받자마자 포장하여 암호화 하고 DB에 전달해서 저장하는 것이다.



같은 값을 암호화 했을때 나오는 문구들은 결과가 똑같기 때문에

변경된 값을 비교하여 같으면 로그인에 성공하는 방식이다.






이 구조의 전제로는 암호화 알고리즘이 단방향 알고리즘이여야 한다.


단방향 : 암호화만 할 수 있고 복호화(원상 복구)는 불가능하다. 암호화된 결과를 digest라고 부른다.


양방향 : 암호화 할 수 있고 복호화 또한 가능하다.







이번의 예는 sha - 512 알고리즘을 사용하는데 정부 기관에서는 sha 256 이상 버전의 알고리즘을 권장하고 있다고 한다.


256, 512는 눈치가 빠르다면 2진수 비트수를 의미한다는 것을 눈치 챘을 것이다.







예의 프로젝트 구조는 다음과 같다.





구조를 보면 매우 복잡하다.


그러므로 암호화, Filter 관련 파일만 올린다.


나머지는 여태까지 했던 기능들과 별반 다를게 없다.


암호화에 사용하게될 클래스들은 filter 폴더와 wrapper 폴더가 된다.


다른 클래스에서는 따로 명시할 코드가 전혀 없고 여태까지 쓰던대로와 같기 때문에 설명은 생략









먼저 Filter 파일에 대한 이해가 필요하다.


생성방법은 다음과 같다.




프로젝트 네비게이터에서 만들 폴더에


우클릭 - New - Filter File 이나


우클릭 - New - Other을 눌러 다음과 같이 검색하면 된다.




선택 한 후









이와같이 설정하면 된다.


서블릿과 매우 비슷하다.


해당 2번째 사진에는 Filter mappings에 /CommonFilter을 더블클릭해서 변경하는 팝업창인데


Pattern에 '/'가 아닌 '/*'를 입력해야 한다.






생성된 필터의 내용 또한 서블릿과 비슷하다.


필터 클래스는 지정한 페이지 링크가 열리면 서블릿보다 먼저 request, response 데이터를 받아서 데이터를 수정하고


실제 이동할 서블릿에 수정된 데이터를 넘기는 것이다.


암호화를 이 부분에서 적용한다.





위 문단에서 설명한 지정한 페이지란 Filter 클래스 생성 시 정한 Pattern /* 가 된다.


클래스 상단에 보면 서블릿과 똑같이 어노테이션으로 @WebFilter("/*")라고 쓰여있다.


여기서 지정한 /*란 "/로 시작하는 모든 링크(*의 의미)"라고 생각하면 된다.


다음 필터 파일은 모든 페이지에서 request, response가 발생한다면 그 데이터를 UTF-8 CharSet 설정을 하고 전달하는 것이다.

이로 인해 데이터를 받을 Servlet에서 request.setCharacterEncoding("UTF-8");를 입력 할 필요가 없다.



CommonFilter.java

------------------------------------------------------------------------------------------------------------------------------------

package com.kh.jsp.filter;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;

@WebFilter("/*") // '/*'이 url패턴은 슬러쉬 뒤에 어떤 링크가 와도 모든 페이지에 이 필터가 적용이 된다라는 뜻(모든 페이지는 열 때 /가 붙는다.)
public class CommonFilter implements Filter {

    public CommonFilter() {
        System.out.println("필터 생성!");
    }

    public void destroy() {
       
    }
   
    // doget과 비슷
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        request.setCharacterEncoding("UTF-8"); // 여기서 UTF-8을 지정하였기 때문에 서블릿에서 UTF-8을 지정 할 필요가 없어진다.
        response.setContentType("text/html; charset=UTF-8");
       
       
        chain.doFilter(request, response); // 다음 필터로 정보를 전달함
    }
   
    //시작 시 한번 실행
    public void init(FilterConfig fConfig) throws ServletException {
       
    }

}

------------------------------------------------------------------------------------------------------------------------------------



코드를 쭉 보면 서블릿과 비슷한 메소드들이 등장한다.


이것들의 라이프 사이클 또한 서블릿과 똑같다.


doFilter 메소드는 doGet과 같은 메소드라고 생각하면 된다. 필터가 호출되면 실행되는 문구이다.







Filter는 서버가 실행되면 같이 생성되기 때문에 생성자 CommonFilter에 입력된 코드는


서버를 동작한다면 같이 실행되게 된다.









다음으로 이 Filter를 이용한 암호화 클래스 작성이다.



IncryptFilter.java

------------------------------------------------------------------------------------------------------------------------------------

package com.kh.jsp.filter;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

import com.kh.jsp.wrapper.LoginWrapper;

@WebFilter("*.me") // 뒤에가 .me가 되는 페이지에 전달되는 정보가 필터를 적용
// 어노테이션 말고 xml에 하면 필터의 우선순위 설정 가능
public class IncryptFilter implements Filter {

    public IncryptFilter() {
        System.out.println("나도 객체 생성!!");
    }

    public void destroy() {
       
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // ServletRequest - 부모 / HttpServletRequest - 자식 관계
        HttpServletRequest hRequest = (HttpServletRequest)request;
       
        LoginWrapper lw = new LoginWrapper(hRequest);
       
       
       
        chain.doFilter(lw, response);
    }

    public void init(FilterConfig fConfig) throws ServletException {
       
    }

}

------------------------------------------------------------------------------------------------------------------------------------

이 Filter의 패턴은 '/*.me'인데 해석하자면 /로 시작하고 .me로 끝나는 페이지는 전부 이 필터를 거치게 된다.

LoginWrapper이라는 객체를 생성하여 request로 들어온 정보를 보낸다.(보통 사용자가 입력한 정보가 들어오게 될 것이다.)

이 후 LoginWrapper 클래스를 생성해서 HttpServletRequestWrapper라는 객체를 상속 받게하여 암호화 알고리즘을 적용한다.





LoginWrapper.java

------------------------------------------------------------------------------------------------------------------------------------

package com.kh.jsp.wrapper;

import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class LoginWrapper extends HttpServletRequestWrapper{

    public LoginWrapper(HttpServletRequest request) {
        super(request);
    }
   
    @Override
    public String getParameter(String key) {
        String value = "";
       
        if (key != null && key.equals("userPwd")) {
            value = getSha512(super.getParameter("userPwd"));
        } else {
            value = super.getParameter(key);
        }
       
       
        return value;
    }
    private static String getSha512(String pwd) {
        String encPwd = "";
       
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-512");
            byte[] bytes = pwd.getBytes(Charset.forName("UTF-8"));
            md.update(bytes);
           
            encPwd = Base64.getEncoder().encodeToString(md.digest());
           
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
       
       
        return encPwd;
    }
   
}

------------------------------------------------------------------------------------------------------------------------------------


LoginWrapper 객체는 HttpServletRequestWrapper를 상속받았기 때문에


request.getParameter("~") 처럼 getParameter 메소드를 사용 할 수 있다.


오버라이딩으로 getParameter 메소드를 변경하여 Key값이 비밀번호 일 경우(userPwd)


getSha512 메소드를 통해서 암호화 알고리즘을 적용한 값으로 userPwd의 value 값을 바꿔버린다.







MessageDigest md = MessageDigest.getInstance("SHA-512");
byte[] bytes = [바꿀 문자열 값].getBytes(Charset.forName("UTF-8"));
md.update(bytes);
           
[반환할 String 변수방] = Base64.getEncoder().encodeToString(md.digest());


암호화 코드는 위와 같은데 그냥 복사해서 [ ]안에 값만 조정해서 쓰면 된다.





왜냐하면 위의 코드를 이해하려면


암호화 할 알고리즘이 어떤 방식으로 돌아가는지 알아야 한다.


어떤 방식으로 돌아가는지 알게 된다면 암호화 한 값을 복호화(복구)하기 쉬워지기 때문에


공개되지 않는다.


그러므로 그냥 그대로 갓다 쓰자.








+ Recent posts