클라이언트에서 폼에 대한 유효성 검사 를 하면 보안에 취약할 수 있다. 자바스크립트를 제거해서 서버에 전송 할 수 있기
때문이다. 따라서, 서버에서 폼에 대한 유효성 체크를 하도록 한다.
하이버네이트 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)
댓글 남기기