Java EE 정리 08
07 Mar 2019
Reading time ~4 minutes
Java EE 정리 08 - 암호화, DBCP를 이용한 회원가입
암호화
- 입력한 값들을 담은 form을 갖고 요청을 보낼 때 중요 정보를 탈취하는 “중간자 공격”을 막고자 수행
- java.security 패키지에서 제공
- SHA
- 일방향 Hash 알고리즘
- 처리된 데이터는 유일성을 가지며 원래의 문자열로 돌아갈 수 없다.
- MD2, MD5, SHA-1, SHA-224, SHA-512, PGP ->갈수록 키가 길어진다.
- MD - MessageDigest
- SHA - Secure Hash Algorithm
- 일방향 Hash 알고리즘
- 암호화(PKI, Public Key Infrastructure)
- 암호화된 문자열 키를 가지고 복호화 할 수 있는 것
- DES, RSA
// 1. 알고리즘을 설정하여 객체를 얻는다.
MessageDigest md = MessageDigest.getInstance("알고리즘");
// MD2, MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512
// 2. 평문(Plain Text)을 알고리즘에 해당하는 문자열로 변경
String text = "이히히";
md.update(text.getBytes());
// 3. 변경된 문자열을 얻는다.
new String(md.digest()); // digest()는 byte[]을 반환
public class ShaUtil {
public static String shaEncoding(String plainText) {
String cipherText = "";
if (plainText != null || !"".equals(plainText)) {
try {
// 1. 알고리즘을 사용할 수 있는 개체를 얻는다.
MessageDigest md = MessageDigest.getInstance("MD5");
// 2. 평문(Plain Text)를 암호문(Cipher Text)로 변환
md.update(plainText.getBytes());
// 3. 변환된 암호문 (Cipher Text) 얻기
cipherText = new String(md.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
return cipherText;
}
public static void main(String[] args) {
System.out.println(shaEncoding("ABCD"));
}
}
...
private static Base64 base64 = new Base64(); // tomcat-util에 있는 Base 64 클래스
public static String shaEncoding(String plainText) {
String cipherText = "";
if (plainText != null || !"".equals(plainText)) {
try {
// 1. 알고리즘을 사용할 수 있는 개체를 얻는다.
MessageDigest md = MessageDigest.getInstance("MD5");
// 2. 평문(Plain Text)를 암호문(Cipher Text)로 변환
md.update(plainText.getBytes());
// 3. 변환된 암호문 (Cipher Text) 얻기 , 알아볼 수 있도록 Base64 객체로 인코딩
cipherText = new String(base64.encode(md.digest()));
...
DBCP를 이용한 회원가입
- 0218 교육내용(회원가입, 팝업 페이지)을 재사용
- 코드가 긴 관계로 링크만 추가
- servlet_prj/src
- 전체 흐름
- 클라이언트가 member_join.html 요청
- 서버가 페이지 응답
- 클라이언트가 아이디 중복확인, 팝업페이지 요청, 응답받음
- 팝업페이지에 중복확인 버튼 클릭 시 IdDuplication Servlet 요청
- GET 방식으로 요청
- DB서버와 통신 후 존재하는여부 판단, 응답하여 “이미 사용중” 또는 “사용가능” 표시
- “사용가능” 시 사용버튼 클릭하면 부모창 아이디 input으로 값 전달 후 창 종료
- 팝업페이지에 중복확인 버튼 클릭 시 IdDuplication Servlet 요청
- 나머지 필수 회원정보 입력 후 가입 버튼 클릭 시 WebMemberJoin Servlet 요청
- POST 방식으로 요청
- Servlet에서 쿼리스트링에 있는 Parameter를 사용하여 검증 후 DB에 추가한 후 완료 페이지 응답
- 비연결성 문제 발생
- 테이블 설계서 작성
- 테이블 생성
CREATE TABLE web_member(
id VARCHAR2(20) CONSTRAINT pk_web_member PRIMARY KEY,
pass VARCHAR2(200) NOT NULL,
name VARCHAR2(60) NOT NULL,
ssn VARCHAR2(200) NOT NULL,
gender CHAR(6) NOT NULL,
birth VARCHAR2(10) NOT NULL,
age NUMBER(3) NOT NULL,
marriage CHAR(6) NOT NULL,
marriage_date VARCHAR2(10) NULL,
zipcode CHAR(5) NOT NULL,
addr1 VARCHAR2(300) NOT NULL,
addr2 VARCHAR2(300) NOT NULL,
greeting VARCHAR2(4000) NOT NULL,
input_date DATE DEFAULT SYSDATE,
ip_addr VARCHAR2(15) NOT NULL,
);
CREATE TABLE web_lang(
id VARCHAR2(20) CONSTRAINT fk_web_lang_id REFERENCES web_member(id),
lang VARCHAR2(7) not null
);
COMMIT;
- 클래스 다이어그램
- 중복 체크 실행모습
웹에서 발생가능한 문제들
- Web Browser에서 커서를 가질 수 있는 HTML Form Control이 하나인 경우 엔터키를 치면 JavaScript의 유효성 검증과 상관없이 submit이 된다.
- ”” 처리를 BackEnd(Servlet)에서 수행한다.(방어코드)
if (id != null && !"".equals(id)) { // 파라미터가 있는 경우, 아이디가 ""가 아닌 경우 처리
- 비연결성 문제
- 웹은 비연결성이기 때문에 중복확인한 동일한 아이디로 모든 여러 유저가 가입시도를 할 수 있음.
- 가장 먼저 가입버튼을 누른 사람만 처리해야 된다.
- 회원정보를 insert 하기 전 아이디를 먼저 사용중인지 다시 체크한 후 insert 수행
public class WebMemberJoin extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8"); // MIME Type 설정
PrintWriter out = response.getWriter();
//post방식 한글처리
request.setCharacterEncoding("UTF-8");
//사용자가 입력한 값(HTML Form Control)을 받기(getParameter)
String id=request.getParameter("id");
String pass1=request.getParameter("pass1");
String name=request.getParameter("name");
String ssn1=request.getParameter("ssn1");
String ssn2=request.getParameter("ssn2");
String gender=request.getParameter("gender");
String b_year=request.getParameter("b_year");
String b_month=request.getParameter("b_month");
String b_day=request.getParameter("b_day");
String age=request.getParameter("age");
String[] interestlang=request.getParameterValues("interestlang");
String marriageFlag=request.getParameter("marriageFlag");
String m_year=request.getParameter("m_year");
String m_month=request.getParameter("m_month");
String m_day=request.getParameter("m_day");
String zipcode=request.getParameter("zipcode");
String addr1=request.getParameter("addr1");
String addr2=request.getParameter("addr2");
String greetings=request.getParameter("greetings");
// DB에 데이터를 저장하기 위해 파라메터값을 VO에 저장, 비밀번호와 ssn은 일방향 해쉬알고리즘으로 암호화해서 저장
// 암호화로 중간자 공격을 막을 수 있다. DB정보를 빼가더라도 암호화된 상태로 저장되기 때문에 안전
// DAO에서는 DB작업 처리만 집중하도록 해야 한다.
// DAO보다 암호화는 DAO를 쓰는쪽(Servlet, Controller, Business Logic을 처리하는 곳)에서 처리
WebMemberVO wm_vo = new WebMemberVO(id, ShaUtil.shaEncoding(pass1), name, ShaUtil.shaEncoding(ssn1+"-"+ssn2),
gender, b_year+"-"+b_month+"-"+b_day, age, marriageFlag, m_year+"-"+m_month+"-"+m_day,
zipcode, addr1, addr2, greetings, request.getRemoteAddr(), interestlang); // 접속자의 IP를 얻는 getRemoteAddr()
...
try {
// 웹은 비연결성
// 비연결성의 문제 - 중복확인한 동일한 아이디로 모든 여러 유저가 가입시도를 할 수 있다!
// 비연결성에 대한 해결코드를 준비를 해야 한다! (비연결성 문제가 발생할 가능성이 있다면 재조회 후 처리)
// 회원 정보를 insert 전에 아이디를 사용중인지 다시 체크한 후 insert!
addWebMember(wm_vo);
out.write("\t<div id=\"resultTitle\">\r\n");
out.write("\t\t<span>");
out.print(name);
out.print("</span>님께서 입력하신 정보는 아래와 같습니다.\r\n");
out.write("\t</div>\r\n");
out.write("\t<div id=\"resultInput\">\r\n");
out.write("\t\t<ul>\r\n");
out.write("\t\t\t<li>아이디 : <strong>");
out.print(id);
out.print("</strong></li>\r\n");
out.write("\t\t\t<li>성별 : <strong>");
out.print(gender);
out.print("</strong></li>\r\n");
out.write("\t\t\t<li>생년월일 : <strong>");
out.print(b_year+" "+b_month+" "+b_day);
out.print("</strong></li>\r\n");
out.write("\t\t\t<li>결혼여부 : <strong>");
out.print(marriageFlag);
out.print("</strong></li>\r\n");
out.write("\t\t\t<li>관심언어 : \r\n");
out.write("\t\t\t\t\t<strong>");
if(interestlang!=null) {
for(int i=0; i<interestlang.length;i++) {
out.write("\t\t\t\t\t<strong>");
out.print(interestlang[i]);
out.print(" <strong>\r\n");
}
}
out.print("</strong>\r\n");
out.write("\t\t\t</li>\r\n");
out.write("\t\t\t<li><a href=\"#void\">로그인</a></li>\r\n");
out.write("\t\t</ul>\r\n");
out.write("\t</div>\r\n");
} catch(RuntimeException re) { // 중복 아이디가 존재해서 던진 RuntimeException 예외처리
out.println("아이디가 이미 사용중입니다. <br/>");
out.println("<a href='#void' onclick='history.back();'>다시 입력<a/>. <br/>");
} catch(SQLException se) {
se.printStackTrace();
out.println("회원가입이 정상적으로 이루어지지 않았습니다.<br/>");
out.println("서비스가 원활하지 못한점 죄송합니다.<br/>");
out.println("정상적인 서비스를 위해서 저희 임직원들은 최선을 다하고 있습니다.<br/>");
out.println("신속하게 복구하겠습니다. 사용중 불편한 점은 010-디잘잘에게 연락해주세요.<br/>");
}//end catch
...
}//doPost
private void addWebMember(WebMemberVO wm_vo) throws SQLException,RuntimeException {
WebMemberDAO wm_dao = WebMemberDAO.getInstance();
//추가전에 동일 아이디가 존재하는지 다시 검색하여 처리(비연결성의 처리)
if(wm_dao.selectId(wm_vo.getId())){
throw new RuntimeException(); // 존재하면 런타임 예외 던짐
}//end if
wm_dao.insertMember(wm_vo);
}//addWebMember
}