JSP

 

클라이언트에서 폼에 대한 유효성 검사 를 하면 보안에 취약할 수 있다.  자바스크립트를 제거해서 서버에 전송 할 수 있기 

때문이다.  따라서,  서버에서 폼에 대한 유효성 체크를 하도록 한다.

 

하이버네이트  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>

 

2. classmate-1.3.1.jar

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>

 

3. javax.el-3.0.1-b08.jar

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 컨넥션 설정은 필수 설정이다.

 

 

jsp

 

about author

PHRASE

Level 60  라이트

자기의 행동이 도의에 어긋나지 않다는 자신이 있으면 비록 왕공귀인(王公貴人) 앞에서도 조금도 굴함이 없이 당당할 수가 있다. -순자

댓글 ( 4)

댓글 남기기

작성