@GetMapping("/login.form")
public String loginForm(HttpServletRequest request, Model model) throws Exception {
// referer : 이전 주소가 저장되는 요청 Header 값
String referer = request.getHeader("referer");
model.addAttribute("referer", referer == null ? request.getContextPath() + "/main.do" : referer);
}
${referer}로 확인 가능)<input type="hidden" name="referer" value="${referer}">
response.sendRedirect(request.getParameter("referer"));
<script src="${contextPath}/resources/js/user_agree.js"></script>

// 1. 전체 선택을 클릭하면 개별 선택에 영향을 미친다.
const fnChkAll = () => {
$('#chk_all').click((ev) => {
$('.chk_each').prop('checked', $(ev.target).prop('checked'));
})
}
// 2. 개별 선택을 클릭하면 전체 선택에 영향을 미친다.
const fnChkEach = () => {
$(document).on('click', '.chk_each', () => {
var total = 0;
$.each($('.chk_each'), (i, elem) => {
total += $(elem).prop('checked');
})
$('#chk_all').prop('checked', total === $('.chk_each').length);
})
}
// 3. 필수 동의를 해야만 가입 페이지로 이동할 수 있다.
const fnJoinForm = () => {
$('#frm_agree').submit((ev) => {
if(!$('#service').is(':checked')){
alert('필수 약관에 동의하세요.');
ev.preventDefault();
return;
}
})
}
1
3
모두 동의(id="chk_all")는 체크 편하게 해주기 위한 프론트 기술이니까 보낼 필요 없어서 제외하고 나머지 두 개만 name 속성 추가해준다<form id="frm_agree" action="${contextPath}/user/join.form">
<h1>약관 동의하기</h1>
<div>
<input type="checkbox" id="chk_all">
<label for="chk_all">모두 동의합니다</label>
</div>
<hr>
<div>
<input type="checkbox" name="service" id="service" class="chk_each">
<label for="service">서비스 이용약관 동의(필수)</label>
</div>
<div>
<textarea>본 약관은 ...</textarea>
</div>
<div>
<input type="checkbox" name="event" id="event" class="chk_each">
<label for="event">이벤트 알림 동의(선택)</label>
</div>
<div>
<textarea>본 약관은 ...</textarea>
</div>
<div>
<button type="submit">다음</button>
</div>
</form>
id="service"와 id="event" 모두 value를 만들지 않았다. 이러면 넘어가는 값이 없을 것 같지만 on이 넘어간다.

service와 event에 둘 다 체크했을 때 모습

event에 체크안했을 때 모습. 체크 안하면 on 안넘어가고 null
이제 백단에서 on이 있는지 확인하고 판단하면 된다. (그런데 아까 필수 약관 동의 안한 경우는 ev.preventDefault로 막았으니까 추가 작업 안해도잘 판별돼서 전송되긴 한다. 그래도 할거다.)
@GetMapping("/join.form")
public String joinForm(@RequestParam(value="service", required=false, defaultValue="off") String service
, @RequestParam(value="event", required=false, defaultValue="off") String event
, Model model) {
String rtn = null;
if(service.equals("off")) {
rtn = "redirect:/main.do";
} else {
model.addAttribute("event", event); // user 폴더 join.jsp로 전달하는 event는 "on" 또는 "off" 값을 가진다.
rtn = "user/join";
}
return rtn;
}
required=false 로 필수 요소가 아니라고 명시해줘야 한다. <form id="frm_join" method="post" action="${contextPath}/user/join.do">
<input type="hidden" name="event" value="${event}">
Apache Commons Lang 3.12Spring Context Support 5.3.3 // 인증코드 반환
public String getRandomString(int count, boolean letters, boolean numbers) {
return RandomStringUtils.random(count, letters, numbers);
}
// 크로스 사이트 스크립팅(Cross Site Scripting) 방지
public String preventXSS(String source) {
return source.replace("<", "<").replace(">", ">");
}
< > 필수로 써야하니까 악성 스크립팅 방지하기 위해 < 는 작다라는 뜻인 <로 바꾸고 >는 크다라는 뜻인 >로 바꾼다.# mail
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.username=
spring.mail.password=
* 1. 구글에 로그인한다.
* 2. [계정] - [보안]
* 3. [2단계 인증] - [앱 비밀번호] - [App name] : myhome 입력
* 4. 생성된 비밀번호를 복사해서 email.properties 파일에 붙여넣기한다. (띄어쓰기 지우고)
@PropertySource(value="classpath:email.properties")
읽어들일 프로퍼티 파일 적어주고
@Autowired
private Environment env;
// Properties 객체 생성 (이메일 보내는 호스트 정보)
Properties properties = new Properties();
properties.put("mail.smtp.host", env.getProperty("spring.mail.host"));
properties.put("mail.smtp.port", env.getProperty("spring.mail.host"));
properties.put("mail.smtp.auth", env.getProperty("spring.mail.properties.mail.smtp.auth"));
properties.put("mail.smtp.starttls.enable", env.getProperty("spring.mail.properties.mail.smtp.starttls.enable"));
// javax.mail.Session 객체 생성 (이메일 보내는 사용자 정보)
Session session = Session.getInstance(properties, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(env.getProperty("spring.mail.username"), env.getProperty("spring.mail.password"));
}
});
유저이름( env.getProperty("spring.mail.username"))과 패스워드(env.getProperty("spring.mail.password"))를 전달한다. // 메일 만들기 (보내는 사람 + 받는 사람 + 제목 + 내용)
MimeMessage mimeMessage = new MimeMessage(session);
mimeMessage.setFrom(new InternetAddress(env.getProperty("spring.mail.username"), "사이트관리자"));
mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
mimeMessage.setSubject(subject);
mimeMessage.setContent(content, "text/html; charset=UTF-8");
받는 사람 + 제목 + 내용은 sendJavaMail 메소드를 만들 때 파라미터로 이미 불러온 상태(String to, String subject, String content) 이 파라미터들 대해선 뒤에 설명할듯
<input type="text" name="email" id="email">가입한 사람이 입력한 이메일이 곧 받는 사람 이메일. $.ajax({
success: () => {
$ajax({
})
}
}) ajax를 연속으로 써야할 때는 이렇게 중복해서 적어준다. 만약 따로 두개를 쓰면 하나 끝나고 그 결과를 바탕으로 다음 ajax가 도는게 아니라 비동기처리로 동시에 돌 수도 있다. (그런데 우리는 이 방법 대신 이걸 씀. 이걸 해결하는 방법이 promise 객체였음.)
const fnCheckEmail = () => {
$('#btn_get_code').click(() => {
let email = $('#email').val();
// 연속된 ajax() 함수 호출의 실행 순서를 보장하는 JavaScript 객체 Promise
new Promise((resolve, reject) => {
// 성공했다면 resolve() 함수 호출 -> then() 메소드에 정의된 화살표 함수 호출
// 실패했다면 reject() 함수 호출 -> catch() 메소드에 정의된 화살표 함수 호출
// 1. 정규식 검사
let regEmail = /^[A-Za-z0-9-_]+@[A-Za-z0-9]{2,}([.][A-Za-z]{2,6}){1,2}$/;
if(!regEmail.test(email)){
reject(1);
return;
}
// 2. 이메일 중복 체크
$.ajax({
// 요청
type: 'get',
url: getContextPath() + '/user/checkEmail.do',
data: 'email=' + email,
// 응답
dataType: 'json',
success: (resData) => { // resData === {"enableEmail": true}
if(resData.enableEmail){
resolve();
} else {
reject(2);
}
}
})
}).then(() => {
// 3. 인증코드 전송
$.ajax({
// 요청
type: 'get',
url: getContextPath() + '/user/sendCode.do',
data: 'email=' + email,
// 응답
dataType: 'json',
success: (resData) => { // resData === {"code": "6자리코드"}
alert(email + "로 인증코드를 전송했습니다.");
$('#code').prop('disabled', false);
$('#btn_verify_code').prop('disabled', false);
$('#btn_verify_code').click(() => {
emailPassed = $('#code').val() === resData.code;
if(emailPassed){
alert('이메일이 인증되었습니다.');
} else {
alert('이메일 인증이 실패했습니다.');
}
})
}
})
}).catch((state) => {
emailPassed = false;
switch(state){
case 1: $('#msg_email').text('이메일 형식이 올바르지 않습니다.'); break;
case 2: $('#msg_email').text('이미 가입한 이메일입니다. 다른 이메일을 입력해 주세요.'); break;
}
})
})
}
if(!regEmail.test(email)){
reject(1);
return; // return으로 꼭 종료해줘야 한다
}
email이 정규식(regEmail) 테스트를 통과하지 못했다면 reject(1) 호출 (catch문의 state 매개변수로 1이 들어가서 case 1과 연결)
🔺userMapper.xml(DB작업)

<select id="getUser" parameterType="Map" resultType="UserDto">
SELECT USER_NO, EMAIL, PW, NAME, GENDER, MOBILE, POSTCODE, ROAD_ADDRESS, JIBUN_ADDRESS, DETAIL_ADDRESS, AGREE, STATE, PW_MODIFIED_AT, JOINED_AT
FROM USER_T
<where>
<if test="email != null">EMAIL = #{email}</if>
<if test="pw != null">AND PW = #{pw}</if>
<if test="userNo != null">AND USER_NO = #{userNo}</if>
</where>
</select>
다음과 같이 수정했다. email이 있으면 EMAIL = #{email} 조건을 체크
<select id="getLeaveUser" parameterType="Map" resultType="LeaveUserDto">
SELECT EMAIL, JOINED_AT, LEAVED_AT
FROM LEAVE_USER_T
WHERE EMAIL = #{email}
</select>
<select id="getInactiveUser" parameterType="Map" resultType="InactiveUserDto">
SELECT USER_NO, EMAIL, PW, NAME, GENDER, MOBILE, POSTCODE, ROAD_ADDRESS, JIBUN_ADDRESS, DETAIL_ADDRESS, AGREE, STATE, PW_MODIFIED_AT, JOINED_AT, INACTIVED_AT
FROM INACTIVE_USER_T
<where>
<if test="email != null">EMAIL = #{email}</if>
<if test="pw != null">AND PW = #{pw}</if>
</where>
</select>
🔺UserService.java (서비스 작업)
public ResponseEntity<Map<String, Object>> checkEmail(String email);
🔺UserServiceImpl.java (서비스 작업)
@Transactional(readOnly=true)
@Override
public ResponseEntity<Map<String, Object>> checkEmail(String email) {
Map<String, Object> map = Map.of("email", email);
boolean enableEmail = userMapper.getUser(map) == null
&& userMapper.getLeaveUser(map) == null
&& userMapper.getInactiveUser(map) == null;
return new ResponseEntity<>(Map.of("enableEmail", enableEmail), HttpStatus.OK);
}

return new ResponseEntity<>(Map.of("enableEmail", enableEmail), HttpStatus.OK);
🔺user_join.js 요청
const getContextPath = () => {
let begin = location.href.indexOf(location.host) + location.host.length;
let end = location.href.indexOf('/', begin + 1);
return location.href.substring(begin, end);
}http://localhost:8080/myhome/~~에서 myhomehttp://localhost:8080/까지 통과할 수 있다. -> myhome 시작위치(begin)🔺UserController.java
produces=MediaType.APPLICATION_JSON_VALUE는 produces="application/json"과 같다. (후자로 쓰면 오타날 수 있으니 전자로 쓰는 것도 괜춘)🔺user_join.js 응답
<div>
<label for="email">이메일</label>
<input type="text" name="email" id="email">
<button type="button" id="btn_get_code">인증코드받기</button>
<span id="msg_email"></span>
</div>
<div>
<input type="text" id="code" placeholder="인증코드입력" disabled>
<button type="button" id="btn_verify_code" disabled>인증하기</button>
</div>
$.ajax({
// 요청
type: 'get',
url: getContextPath() + '/user/sendCode.do',
data: 'email=' + email,
// 응답
dataType: 'json',
success: (resData) => { // resData === {"code": "6자리코드"}
alert(email + "로 인증코드를 전송했습니다.");
$('#code').prop('disabled', false);
$('#btn_verify_code').prop('disabled', false);
$('#btn_verify_code').click(() => {
emailPassed = $('#code').val() === resData.code;
if(emailPassed){
alert('이메일이 인증되었습니다.');
} else {
alert('이메일 인증이 실패했습니다.');
}
})
const fnJoin = () => {
$('#frm_join').submit((ev) => {
if(!emailPassed){
alert('이메일을 인증 받아야 합니다.');
ev.preventDefault();
return;
} else if(!pwPassed || !pw2Passed){
alert('비밀번호를 확인하세요.');
ev.preventDefault();
return;
} else if(!namePassed){
alert('이름을 확인하세요.');
ev.preventDefault();
return;
} else if(!mobilePassed){
alert('휴대전화번호를 확인하세요.');
ev.preventDefault();
return;
}
})
}