🔥 회원가입 기능 🔥
💻 memberEnrollForm.jsp 생성
👉🏻 header, footer include 후 form 태그 action 속성에 insert.me url 제시
👉🏻 Controller에 커맨드 객체 방식으로 전달할 것이기 때문에 name 속성값도 맞춰 줌!
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- 메뉴바 -->
<jsp:include page="../common/header.jsp" />
<div class="content">
<br><br>
<div class="innerOuter">
<h2>회원가입</h2>
<br>
<form action="insert.me" method="post">
<div class="form-group">
<label for="userId">* ID : </label>
<input type="text" class="form-control" id="userId" placeholder="Please Enter ID" name="userId" required> <br>
<label for="userPwd">* Password : </label>
<input type="password" class="form-control" id="userPwd" placeholder="Please Enter Password" name="userPwd" required> <br>
<label for="checkPwd">* Password Check : </label>
<input type="password" class="form-control" id="checkPwd" placeholder="Please Enter Password" required> <br>
<label for="userName">* Name : </label>
<input type="text" class="form-control" id="userName" placeholder="Please Enter Name" name="userName" required> <br>
<label for="email"> Email : </label>
<input type="text" class="form-control" id="email" placeholder="Please Enter Email" name="email"> <br>
<label for="age"> Age : </label>
<input type="number" class="form-control" id="age" placeholder="Please Enter Age" name="age"> <br>
<label for="phone"> Phone : </label>
<input type="tel" class="form-control" id="phone" placeholder="Please Enter Phone (-없이)" name="phone"> <br>
<label for="address"> Address : </label>
<input type="text" class="form-control" id="address" placeholder="Please Enter Address" name="address"> <br>
<label for=""> Gender : </label>
<input type="radio" id="Male" value="M" name="gender" checked>
<label for="Male">남자</label>
<input type="radio" id="Female" value="F" name="gender">
<label for="Female">여자</label>
</div>
<br>
<div class="btns" align="center">
<button type="submit" class="btn btn-primary">회원가입</button>
<button type="reset" class="btn btn-danger">초기화</button>
</div>
</form>
</div>
<br><br>
</div>
<!-- 푸터바 -->
<jsp:include page="../common/footer.jsp" />
</body>
</html>
🙋🏻♀️ phone의 tel 속성은 뭔가요?
💻 header.jsp에서 화면을 띄워 줄 요청 url 제시
👉🏻 회원가입 a 태그에 url 제시
👉🏻 아이디, 비밀번호 required 속성 추가
// 회원가입 url 요청
<c:when test="${ empty loginUser }">
<!-- 로그인 전 -->
<a href="enrollForm.me">회원가입</a>
<a data-toggle="modal" data-target="#loginModal">로그인</a>
<!-- 모달의 원리 : 이 버튼 클릭시 data-targer에 제시되어있는 해당 아이디의 div요소를 띄워줌 -->
</c:when>
// 아이디, 비밀번호 required 속성 추가
<form action="login.me" method="post">
<!-- Modal body -->
<div class="modal-body">
<label for="userId" class="mr-sm-2">ID : </label>
<input type="text" class="form-control mb-2 mr-sm-2" placeholder="Enter ID" id="userId" name="userId" required> <br>
<label for="userPwd" class="mr-sm-2">Password : </label>
<input type="password" class="form-control mb-2 mr-sm-2" placeholder="Enter Password" id="userPwd" name="userPwd" required>
</div>
<!-- Modal footer -->
<div class="modal-footer">
<button type="submit" class="btn btn-primary">로그인</button>
<button type="button" class="btn btn-danger" data-dismiss="modal">취소</button>
</div>
</form>
💻 MemberController
👉🏻 insertMember 메소드의 경우 커맨드 객체 방식으로 전달받을 것
@RequestMapping("enrollForm.me")
public String enrollForm() {
// 회원가입 페이지를 포워딩
// 포워딩 하고자 하는 경로: /WEB-INF/views/member/memberEnrollForm.jsp
// 접두어와 접미어를 제외한 경로: member/memberEnrollForm
return "member/memberEnrollForm";
}
@RequestMapping("insert.me")
public void insertMember(Member m) {
System.out.println(m);
}
1. 필수 입력 사항(required)인 4가지만 입력했을 때 400 오류
2. 모든 사항 입력했을 때 404 오류
👉🏻 현재 응답 페이지를 제대로 지정하지 않았으므로 이 오류가 뜨는 것은 맞음!
방법 2의 경우 콘솔이 뜨긴 하지만 한글이 깨지고 있음
3. DB에 비밀번호가 그대로 노출됨
👉🏻 이거 완전 철컹철컹이에요...
현재의 문제점
1️⃣ 한글 깨짐 문제 발생 👉🏻 요청 시 전달값을 뽑기 전에 먼저 UTF-8 형식으로 인코딩 설정하기
👉🏻 이 시점에서 인코딩 설정을 하면 안 되나?
❌: 이미 읽어 온 후인데 이 시점에서 해 봐야 뭐 하나요 헛고생임!
⭕️ 해결책: 요청이 DispatcherServlet에 도달하기 전에 "인코딩 필터"를 거쳐가게끔 해 줄 것
스프링에서 제공하는 인코딩 필터를 web.xml에 등록(제일 먼저 읽히니까)
필터(Filter)
👉🏻 모든 요청 처리 전 공통적으로 필요로 하는 기능들을 클라이언트의 요청을 Servlet이 받기 전에 먼저 가로채서 해당 작성된 내용이 먼저 수행되도록 하는 개념
스프링에서 제공해 주는 인코딩 필터를 등록
👉🏻경로: spring-web-x.x.xx.jar 파일의 org.springframework.web.filter.CharacterEncodingFilter.class 파일
👉🏻 필터 등록 시 filter 태그와 filter-mapping 태그를 같이 기술
💻 web.xml
<!-- 필터를 이용하겠다 -->
<filter>
<!-- 반드시 filter-name으로 filter명을 지정해야 함 -->
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- 옵션 1. 인코딩 방식을 지정(UTF-8) -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!-- 옵션 2. 혹시나 충돌 시 강제로 인코딩을 UTF-8로 할 수 있게끔 지정 -->
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<!-- 이 필터를 언제 적용하겠다 -->
<filter-mapping>
<!-- 위에서 지정한 encodingFilter를 모든 요청에 적용하겠음 -->
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
👉🏻 web.xml에서 인코딩 설정을 한 뒤 해당 Controller에 진입하였기 때문에 한글이 더 이상 깨지지 않음!
2️⃣ 필수 입력사항만 입력하고 넘겼을 경우 400 에러?
👉🏻 넘기는 데이터의 형식이 맞지 않을 때 일어나는 오류
WARN : org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver - Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors<EOL>Field error in object 'member' on field 'age': rejected value []; codes [typeMismatch.member.age,typeMismatch.age,typeMismatch.int,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [member.age,age]; arguments []; default message [age]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'int' for property 'age'; nested exception is java.lang.NumberFormatException: For input string: ""]]
👉🏻 콘솔의 내용을 잘 뜯어보니 모두 입력하지 않았을 때가 아니라 '나이'를 제대로 받아오지 못한 것으로 보임
즉, 문제 상황은 2️⃣ 필수 입력사항만 입력하고 넘겼을 경우 400 에러? 가 아닌
2️⃣ 요청 시 나이를 입력하지 않았을 경우
✔️ 400 에러(Bad Request Error) : 형식에 맞지 않는 데이터가 넘어온 경우 발생함
⭕️ 해결책: Member 클래스의 age 필드를 int형에서 String 형으로 변경
해결법 1-1) Member 클래스의 age 필드와 관련 setter, getter, toString을 모두 String으로 바꾸기
해결법 1-2) 롬복 사용하기
👉🏻 결국 age 필드를 int형에서 String 형으로 바꾼다는 것은 동일함! 협업이라면 팀원 중 1명이라도 롬복 사용 불가능하다면 그냥 수기로 바꿔 줄 것!!
💻 Member
package com.kh.spring.member.model.vo;
import java.sql.Date;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@NoArgsConstructor // 기본생성자를 만들어 주는 어노테이션
@AllArgsConstructor // 모든 필드를 매개변수로 갖는 생성자를 만들어 주는 어노테이션
@Setter // setter 메소드들
@Getter // getter 메소드들
@ToString // toString 메소드
public class Member {
private String userId; // USER_ID VARCHAR2(30 BYTE)
private String userPwd; // USER_PWD VARCHAR2(100 BYTE)
private String userName; // USER_NAME VARCHAR2(15 BYTE)
private String email; // EMAIL VARCHAR2(100 BYTE)
private String gender; // GENDER VARCHAR2(1 BYTE)
// private int age; // AGE NUMBER
private String age; // 400 에러 해결방법
private String phone; // PHONE VARCHAR2(13 BYTE)
private String address; // ADDRESS VARCHAR2(100 BYTE)
private Date enrollDate; // ENROLL_DATE DATE
private Date modifyDate; // MODIFY_DATE DATE
private String status; // STATUS VARCHAR2(1 BYTE)
}
👉🏻 이제 나이를 입력하지 않아도 400 오류가 뜨지 않음!
3️⃣ DB에 비밀번호가 그대로 노출됨
⭕️ 해결책: 📖 비밀번호 암호화 - Bcrypt를 위한 스프링 스큐리티 모듈
// 비밀번호 암호화를 위한 변수
@Autowired
private BCryptPasswordEncoder bcryptPasswordEncoder;
@RequestMapping("insert.me")
public void insertMember(Member m) {
System.out.println("평문: " + m.getUserPwd());
// 암호화 작업 (암호문을 만들어 내는 과정)
String encPwd = bcryptPasswordEncoder.encode(m.getUserPwd());
System.out.println("암호문: " + encPwd);
// => 같은 평문이어도 매번 다른 암호문 결과가 나옴
// => 평문 + salt(랜덤값) => 암호화 작업이 이루어지기 때문
}
👉🏻 권장하지는 않으나 테스트용이라면 나온 값을 직접 수정해 줘도 됨!
👉🏻 이후 커밋은 반드시 해 줄 것!
💻 문제점을 모두 수정한 MemberController
// 비밀번호 암호화를 위한 변수
@Autowired
private BCryptPasswordEncoder bcryptPasswordEncoder;
@RequestMapping("insert.me")
public void insertMember(Member m) {
// System.out.println("평문: " + m.getUserPwd());
// 암호화 작업 (암호문을 만들어 내는 과정)
String encPwd = bcryptPasswordEncoder.encode(m.getUserPwd());
// System.out.println("암호문: " + encPwd);
// => 같은 평문이어도 매번 다른 암호문 결과가 나옴
// => 평문 + salt(랜덤값) => 암호화 작업이 이루어지기 때문
m.setUserPwd(encPwd);
int result = memberService.insertMember(m);
}
💻 MemberServiceImpl
@Override
public int insertMember(Member m) {
return memberDao.insertMember(sqlSession, m);
}
💻 MemberDao
public int insertMember(SqlSessionTemplate sqlSession, Member m) {
return sqlSession.insert("memberMapper.insertMember", m);
}
💻 member-mapper.xml
<!-- 회원가입용 쿼리문 -->
<insert id="insertMember" parameterType="member">
INSERT INTO MEMBER(USER_ID
, USER_PWD
, USER_NAME
, EMAIL
, GENDER
, AGE
, PHONE
, ADDRESS)
VALUES(#{userId}
, #{userPwd}
, #{userName}
, #{email}
, #{gender}
, #{age}
, #{phone}
, #{address})
</insert>
💻 DB까지 다녀온 후의 Controller
👉🏻 매개변수로 Model 추가하기
👉🏻 반환형 String으로 수정하기
@RequestMapping("insert.me")
public String insertMember(Member m, Model model, HttpSession session) {
// System.out.println(m);
// System.out.println("평문: " + m.getUserPwd());
// 암호화 작업 (암호문을 만들어 내는 과정)
String encPwd = bcryptPasswordEncoder.encode(m.getUserPwd());
// Member 객체의 userPwd 필드의 값을 암호문으로 바꿔치기 => setter 메소드
m.setUserPwd(encPwd);
int result = memberService.insertMember(m);
if(result > 0) { // 성공, 메인페이지 url 재요청
// 일회성 알람 문구
session.setAttribute("alertMsg", "성공적으로 회원가입이 완료되었습니다.");
return "redirect:/";
} else { // 실패, 에러 문구를 담아서 에러 페이지 포워딩
model.addAttribute("errorMsg", "회원가입 실패");
// /WEB-INF/views/common/errorPage.jsp
return "common/errorPage";
}
}
💻 header.jsp
👉🏻 alert창을 띄우기 위한 설정 추가
<body>
<!-- 액션 태그의 특징: script 태그 영역 내부에서 사용할 수 없음 (인식 안 됨) -->
<c:if test="${ not empty alertMsg }">
<script>
alert("${ alertMsg }");
</script>
<c:remove var="alertMsg" scope="session" />
</c:if>
<div id="header">
👉🏻 alert창 잘 뜸
👉🏻 DB 등록 확인