클라이언트에서 폼에 대한 유효성 검사 를 하면 보안에 취약할 수 있다. 자바스크립트를 제거해서 서버에 전송 할 수 있기
때문이다. 따라서, 서버에서 폼에 대한 유효성 체크를 하도록 한다.
하이버네이트 validator 사용해서 유효성 체크
http://hibernate.org/validator/
유효성 체크를 위해 다음과 라이브러리를 준비했다. 필수라이브러리는 hibernate 이다.
깃허브에 존재하는 소스는 메이븐으로 개발하는 것이 아니라서 mvnrepository 에서 직접 다운로드 후 WEB-INF 의 lib 에 등록해야 한다.
1. hibernate-validator-5.4.1.Final.jar
http://mvnrepository.com/artifact/org.hibernate/hibernate-validator/5.4.1.Final
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.4.1.Final</version> </dependency>
http://mvnrepository.com/artifact/com.fasterxml/classmate/1.3.1
<!-- https://mvnrepository.com/artifact/com.fasterxml/classmate --> <dependency> <groupId>com.fasterxml</groupId> <artifactId>classmate</artifactId> <version>1.3.1</version> </dependency>
http://mvnrepository.com/artifact/javax.el/javax.el-api/3.0-b08
<!-- https://mvnrepository.com/artifact/javax.el/javax.el-api --> <dependency> <groupId>javax.el</groupId> <artifactId>javax.el-api</artifactId> <version>3.0-b08</version> </dependency>
4. jboss-logging-3.3.0.Final.jar
http://mvnrepository.com/artifact/org.jboss.logging/jboss-logging/3.3.0.Final
<!-- https://mvnrepository.com/artifact/org.jboss.logging/jboss-logging --> <dependency> <groupId>org.jboss.logging</groupId> <artifactId>jboss-logging</artifactId> <version>3.3.0.Final</version> </dependency>
5. validation-api-1.1.0.Final.jar
http://mvnrepository.com/artifact/javax.validation/validation-api/1.1.0.Final
<!-- https://mvnrepository.com/artifact/javax.validation/validation-api --> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.1.0.Final</version> </dependency>
전송 폼
<csrf:form class="form-horizontal" action="/member/joinproc.jsp" method="post"> <div class="form-group"> <div class="col-sm-2 control-label"> <label for="id">아이디</label> </div> <div class="col-sm-6 text-left"> <input type="text" class="form-control" name="id" id="id" value="${member.id }"> <p style="color:red;">${idError}</p> </div> </div> <div class="form-group"> <div class="col-sm-2 control-label"> <label id="pwd">패스워드</label> </div> <div class="col-sm-6"> <input type="password" class="form-control" name="pwd" id="pwd"> <p style="color:red;">${pwdError}</p> </div> </div> <div class="form-group"> <div class="col-sm-2 control-label"> <label id="pwdCheck">패스워드 체크</label> </div> <div class="col-sm-6"> <input type="password" class="form-control" name="pwdCheck" id="pwdCheck"> <p style="color:red;">${pwdCheckError}</p> </div> </div> <div class="form-group"> <div class="col-sm-2 control-label"> <label id="name">이름</label> </div> <div class="col-sm-6"> <input type="text" class="form-control" name="name" id="name" value="${member.name }"> <p style="color:red;">${nameError}</p> </div> </div> <div class="form-group"> <div class="col-sm-2 control-label"> <label id="email">이메일</label> </div> <div class="col-sm-6"> <input type="email" class="form-control" name="email" id="email" value="${member.email }"> <p style="color:red;">${emailError}</p> </div> </div> <div class="form-group"> <div class="col-sm-2 control-label"> <label id="zip_num">우편번호</label> </div> <div class="col-sm-3"> <input type="text" id="sample6_postcode" placeholder="우편번호" name="zip_num" class="form-control" value="${member.zip_num }"> <p style="color:red;">${zip_numError}</p> </div> <div class="col-sm-3"> <input type="button" onclick="sample6_execDaumPostcode()" value="우편번호 찾기" class="btn btn-primary"> </div> </div> <div class="form-group" > <div class="col-sm-2 control-label"> <label id="address1">주소</label> </div> <div class="col-sm-6"> <input type="text" id="sample6_address" placeholder="주소" name="address1" class="form-control" value="${member.address1 }"> <p style="color:red;">${addressError}</p> </div> </div> <div class="form-group"> <div class="col-sm-2 control-label"> <label id="address1">상세주소</label> </div> <div class="col-sm-6"> <input type="text" id="sample6_address2" placeholder="상세주소" name="address2" class="form-control" value="${member.address2 }"> </div> </div> <div class="form-group"> <div class="col-sm-2 control-label"> <label id="phone">전화번호</label> </div> <div class="col-sm-6"> <input type="text" class="form-control" name="phone" id="phone" value="${member.phone }"> </div> </div> <div class="form-group" > <div class="col-sm-12 text-center"> <input type="hidden" name="ip" value="${GetIpAddress.getIp()}"> <input type="submit" value="회원가입" class="btn btn-success"> <input type="reset" value="취소" class="btn btn-warning"> </div> </div> </csrf:form>
위 라이브러리를 등록했으면 DTO 에 어노테이션을 통해 설정을 하자.
더 세부적인 내용은 하이버네이트 validator 를 통해 찾아보자.
http://hibernate.org/validator/documentation/
package net.macaronics.web.dto; import java.sql.Timestamp; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.Size; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.NotEmpty; import com.sun.istack.internal.NotNull; public class MemberVO { @NotEmpty @Size(min=4, max=12) private String id ; @NotEmpty @Size(min=6, max=20) private String pwd ; @NotEmpty private String pwdCheck; @NotEmpty @Size(min=2, max=12) private String name ; @NotEmpty @Email private String email ; @NotNull @NotEmpty private String zip_num; private String address ; @NotNull private String address1; private String address2; private String phone ; private String useyn ; @NotNull private String ip ; private Timestamp indate ; public boolean isPassCheck(){ if(this.pwd.equals(this.pwdCheck)){ return false; } //패스워드가 일치하지 않으면 true return true; } setter, getter ~~
스프링으로 하면 더 간단할 수 있겠으나 jsp MVC 프로젝트이다.
따라서, 필자는 다음과 같은 방법으로 설정 하였다.
MyValidatorFactory 클래스를 만들자.
package config; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; public class MyValidatorFactory { //유효성체크 public static Validator createValidator(){ ValidatorFactory factory=Validation.buildDefaultValidatorFactory(); return factory.getValidator(); } }
buildDefaultValidatorFactory 의해 Validator 를 객체를 생성 한다.
DTO 설정한 어노테이션 대한 유효성을 체크한다.
validator.validate(member); 로 DTO 값을 넘겨 준다.
member 값은 jsp:usebean 을 통해 폼에 전송된 값들이 저장된 상태이다.
<jsp:useBean id="member" class="net.macaronics.web.dto.MemberVO"> <jsp:setProperty name="member" property="*"/> </jsp:useBean>
//유효성 체크 Validator validator =MyValidatorFactory.createValidator(); Set<ConstraintViolation<MemberVO>> constraintViolations=validator.validate(member); Iterator<ConstraintViolation<MemberVO>> iterator =constraintViolations.iterator(); while(iterator.hasNext()){ ConstraintViolation<MemberVO> each=iterator.next(); }
Validator 가 유효성을 확인 후 이것에 대한 결과값을 Iterator<ConstraintViolation<MemberVO>> 통해 가져올 수 있다.
유효성에 맞지 않으면 즉 에러가 하나라도 존재한다면 iterator.hasNext() 통해 while 문을 거치게 된다.
키값은 다음과 같은 방법으로 가져올 수 있다. String key =each.getPropertyPath().toString();
에러 메시지는 each.getMessage() 로 가져 올 수 있다. 에러메시지는 서비스 환경에 따라 자동으로 변환되어서 자동으로
한글 출력이 된다.
int error=0; 으로 주어서 유효성에 맞지 않을 경우 반드시 while 문을 거치게 되므로 while 문안에서 증가값을 주었다.
그리고 request.setAttribute 로 통해 에러 메시지를 저장 하였다.
스프링보다 상당히 복잡한 방법이고 일일이 request.setAttribute 통해 에러메시지를 저장을 해야 한다.
<% //유효성 체크 Validator validator =MyValidatorFactory.createValidator(); Set<ConstraintViolation<MemberVO>> constraintViolations=validator.validate(member); Iterator<ConstraintViolation<MemberVO>> iterator =constraintViolations.iterator(); int error=0; while(iterator.hasNext()){ ConstraintViolation<MemberVO> each=iterator.next(); if(each.getPropertyPath()!=null){ String key =each.getPropertyPath().toString(); if(key.equals("id"))request.setAttribute("idError","아이디는 " +each.getMessage()); if(key.equals("pwd"))request.setAttribute("pwdError","패스워드는 " +each.getMessage()); if(key.equals("zip_num"))request.setAttribute("zip_numError","우편번호는 " +each.getMessage()); if(key.equals("name"))request.setAttribute("nameError","이름 은 " +each.getMessage()); if(key.equals("email"))request.setAttribute("emailError","이메일은 " +each.getMessage()); if(key.equals("address1"))request.setAttribute("addressError","주소는 " +each.getMessage()); } error++; } if(member.getPwd()!=null && member.getPwdCheck()!=null){ if(member.isPassCheck()){ error++; request.setAttribute("pwdCheckError", "패스워드와 패스워드확인이 일치하지 않습니다."); } } //아이디 중복 확인 MemberDAO memberDao=MemberDAO.getInstance(); if(member.getId()!=null){ if(memberDao.confirm(member.getId())){ error++; request.setAttribute("idError", "중복된 아이디 입니다."); } } if(error>0){ request.setAttribute("member", member); RequestDispatcher rd=request.getRequestDispatcher("/member/join.jsp"); rd.forward(request, response); }else{ //에러사항이 없을 경우 등록 memberDao.insertMember(member); response.sendRedirect("/index.html?msg=joinSuccess"); } %>
다시 폼에 있는 코드 내용을 보면 ${emailError} 나 ${member.email } 를 볼수 있는데 el 태그가 좋은 것이 값이 없으면 null 값 에러가 출력 되는
것이 아니라 blank 가 된다. 따라서 request.setAttribute 로 저장 된 데이터가 forward 방식으로 페지 전화 될 때 에러 메시지가 출력 된게 된다.
<div class="col-sm-6"> <input type="email" class="form-control" name="email" id="email" value="${member.email }"> <p style="color:red;">${emailError}</p> </div>
에러 메시지가 없을 경우 response.sendRedirect("/index.html?msg=joinSuccess"); 코드를 통해 메인 페이지로 이동하게 개발하였다.
그리고 index.html 페이지에서는 파라미터 값 msg 값이 joinSuccess 라면 '회원 가입축하 메시지를 띄우게 개발하였다.
<script> getParameter = function(name){ search=location.search; if(!search){ //파라미터가 하나도 없을때 document.write("에러 출력 텍스트"); return false; } search=search.split("?"); data=search[1].split("="); if(search[1].indexOf(name)==(-1) || data[0]!=name){ //해당하는 파라미터가 없을때. return ""; return; } if(search[1].indexOf("&")==(-1)){ //한개의 파라미터일때. data=search[1].split("="); return data[1]; }else{ //여러개의 파라미터 일때. data=search[1].split("&"); //엠퍼센트로 자름. for(i=0;i<=data.length-1;i++){ l_data=data[i].split("="); if(l_data[0]==name){ return l_data[1]; break; }else continue; } } } if(getParameter('msg')=="joinSuccess"){ alert("회원가입을 축하합니다."); } location.href="MacaronicsServlet?command=index"; </script>
joinproc.jsp 전체
<%@page import="net.macaronics.web.dao.MemberDAO"%> <%@page import="org.apache.logging.log4j.LogManager"%> <%@page import="org.apache.logging.log4j.Logger"%> <%@page import="java.util.Iterator"%> <%@page import="javax.validation.Validator"%> <%@page import="javax.validation.ConstraintViolation"%> <%@page import="net.macaronics.web.dto.MemberVO"%> <%@page import="java.util.Set"%> <%@page import="config.MyValidatorFactory"%> <%@page import="java.net.URLDecoder"%> <%@page import="java.net.URLEncoder"%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>JoinProc</title> </head> <body> <jsp:useBean id="member" class="net.macaronics.web.dto.MemberVO"> <jsp:setProperty name="member" property="*"/> </jsp:useBean> CSRF attacks 공격에 대한 보안유지 되어할 페이지.<br> 이 페이지에 넘어 오면 CSRF 정상 작동. <br> <% String name=request.getParameter("name"); String address1=request.getParameter("address1"); String address2=request.getParameter("address2"); String address=new String(address1.getBytes("ISO_8859_1"), "utf-8"); address += " " +new String(address2.getBytes("ISO_8859_1"), "utf-8"); /* String charset[] = {"euc-kr", "ksc5601", "iso-8859-1", "8859_1", "ascii", "UTF-8"}; System.out.println("넘어온 값 :" +name); for(int i=0; i<charset.length ; i++){ System.out.println(charset[i] + " URLEncoder : " + URLEncoder.encode(name, charset[i])); System.out.println(charset[i] + " URLDecoder : " + URLDecoder.decode(name, charset[i])); } */ %> <!-- 한글필터 적용이 안 되어서 한글 깨짐 처리--> <jsp:setProperty property="name" name="member" value='<%= new String(name.getBytes("ISO_8859_1"), "UTF-8") %>' /> <jsp:setProperty property="address1" name="member" value='<%= new String(address1.getBytes("ISO_8859_1"), "UTF-8") %>' /> <jsp:setProperty property="address2" name="member" value='<%= new String(address2.getBytes("ISO_8859_1"), "UTF-8") %>' /> <jsp:setProperty property="address" name="member" value='<%= address %>' /> <%-- ${member.toString() } --%> <% //유효성 체크 Validator validator =MyValidatorFactory.createValidator(); Set<ConstraintViolation<MemberVO>> constraintViolations=validator.validate(member); Iterator<ConstraintViolation<MemberVO>> iterator =constraintViolations.iterator(); int error=0; while(iterator.hasNext()){ ConstraintViolation<MemberVO> each=iterator.next(); if(each.getPropertyPath()!=null){ String key =each.getPropertyPath().toString(); if(key.equals("id"))request.setAttribute("idError","아이디는 " +each.getMessage()); if(key.equals("pwd"))request.setAttribute("pwdError","패스워드는 " +each.getMessage()); if(key.equals("zip_num"))request.setAttribute("zip_numError","우편번호는 " +each.getMessage()); if(key.equals("name"))request.setAttribute("nameError","이름 은 " +each.getMessage()); if(key.equals("email"))request.setAttribute("emailError","이메일은 " +each.getMessage()); if(key.equals("address1"))request.setAttribute("addressError","주소는 " +each.getMessage()); } error++; } if(member.getPwd()!=null && member.getPwdCheck()!=null){ if(member.isPassCheck()){ error++; request.setAttribute("pwdCheckError", "패스워드와 패스워드확인이 일치하지 않습니다."); } } //아이디 중복 확인 MemberDAO memberDao=MemberDAO.getInstance(); if(member.getId()!=null){ if(memberDao.confirm(member.getId())){ error++; request.setAttribute("idError", "중복된 아이디 입니다."); } } if(error>0){ request.setAttribute("member", member); RequestDispatcher rd=request.getRequestDispatcher("/member/join.jsp"); rd.forward(request, response); }else{ //에러사항이 없을 경우 등록 memberDao.insertMember(member); response.sendRedirect("/index.html?msg=joinSuccess"); } %> </body> </html>
DAO 와 mybatis 의 DB 쪽을 살펴보자.
DAO 는 mybatis 를 사용하는 것이기 때문에 스프링과 별 차이가 없어 보인다.
하지만, 스프링에서는 스프링 프레임워크가 자동으로 자원을 닫아 주지만, jsp MVC 모델2 프로젝트는 finally 를
통해 자원을 닫아야 한다. 또한, select 문을 제외한 insert , 와 update 등의 쿼리에서는 항상 sqlSession.commit();
과 같은 commit() 해줘야 완료가 된다는 것이다.
반드시 , 자원을 닫고 commit 을 하자.
DAO
package net.macaronics.web.dao; import org.apache.ibatis.session.SqlSession; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import config.MybatisService; import net.macaronics.web.dto.MemberVO; public class MemberDAO { private SqlSession sqlSession; private static Logger logger= LogManager.getLogger(MemberDAO.class); private MemberDAO(){ } private static MemberDAO instance; public static MemberDAO getInstance(){ if(instance==null){ instance=new MemberDAO(); } return instance; } //유저아이디 체크 public boolean confirm(String id){ int result=0; //result 값이 0 보다 크면 아이디 중복 try{ sqlSession=MybatisService.getFactory().openSession(); result=sqlSession.selectOne("member.confirm", id); logger.info(" confirm result - {} ", result); }catch(Exception e){ e.printStackTrace(); }finally{ MybatisService.sessionClose(sqlSession); } return result ==0 ? false :true; } //회원 정보 불러오기 public MemberVO getMember(String id){ MemberVO memberVO=new MemberVO(); try{ sqlSession=MybatisService.getFactory().openSession(); memberVO=sqlSession.selectOne("member.getMember", id); }catch(Exception e){ e.printStackTrace(); }finally { MybatisService.sessionClose(sqlSession); } return memberVO; } //회원 등록 public void insertMember(MemberVO memberVO){ try{ sqlSession=MybatisService.getFactory().openSession(); sqlSession.insert("member.insertMember", memberVO); }catch(Exception e){ e.printStackTrace(); }finally{ //select 이외의 문장은 commit()을 해야 함 sqlSession.commit(); MybatisService.sessionClose(sqlSession); } } }
member.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="member"> <!-- id="태그의 식별자" resultType="sql 명령어의 리턴타입(레코드의 자료형)" 샵{변수} => 입력매개변수 --> <!-- 유저아이디 체크 --> <select id="confirm" resultType="int"> <![CDATA[ select count(*) from TBL_MEMBER where id=#{id} ]]> </select> <!-- 회원 정보 불러오기 --> <select id="getMember" resultType="net.macaronics.web.dto.MemberVO"> <![CDATA[ select * from TBL_MEMBER where id=#{id} ]]> </select> <insert id="insertMember"> <![CDATA[ INSERT INTO TBL_MEMBER (ID, PWD, NAME, EMAIL, ZIP_NUM, ADDRESS, PHONE, IP) VALUES(#{id}, #{pwd}, #{name}, #{email}, #{zip_num}, #{address}, #{phone}, #{ip}) ]]> </insert> </mapper>
sqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8"?> <!-- xml 지시어 --> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 알리아스 설정 --> <!-- typeAlias type="전체경로" alias="별칭" --> <!-- <typeAliases> <typeAlias type="emp.dto.EmpDTO" alias="e" /> </typeAliases> --> <!-- db연결 참조코드 --> <environments default=""> <environment id=""> <transactionManager type="JDBC" /> <dataSource type="JNDI"> <property name="data_source" value="java:comp/env/jdbc/pool" /> </dataSource> </environment> </environments> <!-- 실제 sql query --> <mappers> <!-- 샘플 설정 --> <!-- <mapper resource="emp/mapper/emp.xml" /> --> <mapper resource="mapper/board.xml"/> <mapper resource="mapper/product.xml"/> <mapper resource="mapper/member.xml"/> </mappers> </configuration>
제작 : macaronics.net - Developer Jun Ho Choi
소스 : https://github.com/braverokmc79/jsp_sin
루트 설정( http://macaronics.net/index.php/m01/jsp/view/1352) 및 server.xml 에서 DB 컨넥션 설정은 필수 설정이다.
댓글 ( 4)
댓글 남기기