블로그의 예제들에서는 여태까지 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