데이터융합 JAVA응용 SW개발자 기업 채용연계 연수과정 65일차 강의 정리

misung·2022년 7월 5일
0

Spring

SpringWebMvc 프로젝트 (이어서)

이날 강의는 오디오 오류로 녹음 자체가 아예 안 되었어서.. 분석에 상당히 시간이 걸리거나 애를 좀 먹을 것 같다.

[UserMapper.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="com.spring.mvc.user.repository.IUserMapper">

	<resultMap type="com.spring.mvc.user.model.UserVO" id="UserMap">
		<result property="regDate" column="reg_date" />
	</resultMap>

	<insert id="register">
		INSERT INTO mvc_user
		(account, password, name)
		VALUES(#{account},#{password},#{name})
	</insert>
	
	<select id="checkId" resultType="int">
		SELECT COUNT(*)
		FROM mvc_user
		WHERE account = #{account}
	</select>
	
	<select id="selectOne" resultMap="UserMap">
		SELECT * FROM mvc_user
		WHERE account=#{account}
	</select>
	
	<delete id="delete">
		DELETE FROM mvc_user
		WHERE account=#{account}
	</delete>

</mapper>

src/main/resources 하위에 user 디렉토리를 만들고 거기에 생성.

UserVO 와 관련한 SQL 동작을 수행하고 있다.

[UserMapperTest] 클래스를 통해 회원가입, 조회, 탈퇴 등 테스트하기

위에서 Mapper를 만들고 UserVO 와 DB간에 통신을 할 수 있게 만들어 준 것도 전날의 테스트를 위해서였던 것 같다.
(애초에 DB에 뭔가 만들고 조회하고 삭제하는 기능 자체를 안 만들어두면 회원 가입이나 삭제 등을 할 수 없었을 테니까)

package com.spring.mvc.user;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.spring.mvc.user.model.UserVO;
import com.spring.mvc.user.repository.IUserMapper;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/mvc-config.xml")
public class UserMapperTest {
	
	//IUserMapper 타입의 객체를 자동 주입하세요.
	@Autowired
	private IUserMapper mapper;
	
	
	//회원 가입을 아무 아이디로 진행해 보세요.
	@Test
	public void registTest() {
		UserVO vo = new UserVO();
		vo.setAccount("abc1234");
		vo.setName("홍길동");
		vo.setPassword("aaa1111!");
		
		mapper.register(vo);
	}
	
	//위에서 회원 가입한 아이디로 중복 확인을 해서
	//COUNT(*)를 이용해서 1이 리턴이 되는지 확인하세요.
	@Test
	public void checkIdTest() {
		int result = mapper.checkId("kim1234");
		if(result == 1) {
			System.out.println("아이디가 이미 존재함!");
		} else {
			System.out.println("아이디 사용 가능!");
		}
	}
	
	//가입한 회원의 모든 정보를 얻어내서 출력해 보세요.
	@Test
	public void selectTest() {
		System.out.println(mapper.selectOne("abc1234"));
	}
	
	//위에서 가입한 계정의 탈퇴를 진행해 보세요.
	//탈퇴가 성공했는지의 여부를 정보를 얻어오는 메서드를 통해서
	//확인해 보세요. (null 체크)
	@Test
	public void deleteTest() {
		mapper.delete("abc1234");
		if(mapper.selectOne("abc1234") == null) {
			System.out.println("삭제 완료");
		} else {
			System.out.println("삭제 실패!");
		}
	}
	

}

완성된 파일 구성을 보지 않고 직접 시도해 본 결과 대략 비슷한 테스트 코드를 작성해서 테스트할 수 있었다.

[login_modal.jsp] 유저 회원가입/로그인과 관련된 스크립트 작성

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<!-- 로그인 Modal-->
<div class="modal fade" id="log-in">
	<div class="modal-dialog">
		<div class="modal-content">

			<!-- Modal Header -->
			<div class="modal-header">
				<h4 class="modal-title">
					<span style="color: #643691;">Spring</span> 로그인
				</h4>
				<button type="button" class="close" data-dismiss="modal">&times;</button>
			</div>

			<!-- Modal body -->
			<div class="modal-body">

				<form action="#" method="post" id="signInForm"
					style="margin-bottom: 0;">
					<table style="cellpadding: 0; cellspacing: 0; margin: 0 auto; width: 100%">
						<tr>
							<td style="text-align: left">
								<p><strong>아이디를 입력해주세요.</strong>&nbsp;&nbsp;&nbsp;<span id="idCheck"></span></p>
							</td>
						</tr>
						<tr>
							<td><input type="text" name="userId" id="signInId"
								class="form-control tooltipstered" maxlength="14"
								required="required" aria-required="true"
								style="margin-bottom: 25px; width: 100%; height: 40px; border: 1px solid #d9d9de"
								placeholder="최대 14자"></td>
						</tr>
						<tr>
							<td style="text-align: left">
								<p><strong>비밀번호를 입력해주세요.</strong>&nbsp;&nbsp;&nbsp;<span id="pwCheck"></span></p>
							</td>
						</tr>
						<tr>
							<td><input type="password" size="17" maxlength="20" id="signInPw"
								name="userPw" class="form-control tooltipstered" 
								maxlength="20" required="required" aria-required="true"
								style="ime-mode: inactive; margin-bottom: 25px; height: 40px; border: 1px solid #d9d9de"
								placeholder="최소 8자"></td>
						</tr>
						<tr>
							<td style="padding-top: 10px; text-align: center">
								<p><strong>로그인하셔서 더 많은 서비스를 이용하세요~</strong></p>
							</td>
						</tr>
						<tr>
							<td style="width: 100%; text-align: center; colspan: 2;"><input
								type="button" value="로그인" class="btn form-control tooltipstered" id="signIn-btn"
								style="background-color: #643691; margin-top: 0; height: 40px; color: white; border: 0px solid #f78f24; opacity: 0.8">
							</td>
						</tr>
						<tr>
							<td
								style="width: 100%; text-align: center; colspan: 2; margin-top: 24px; padding-top: 12px; border-top: 1px solid #ececec">

								<a class="btn form-control tooltipstered" data-toggle="modal"
								href="#sign-up"
								style="cursor: pointer; margin-top: 0; height: 40px; color: white; background-color: orange; border: 0px solid #388E3C; opacity: 0.8">
									회원가입</a>
							</td>
						</tr>

					</table>
				</form>
			</div>
		</div>
	</div>
</div>

<!-- 회원가입 Modal -->
<div class="modal fade" id="sign-up" role="dialog">
	<div class="modal-dialog">

		<!-- Modal content-->
		<div class="modal-content">
			<div class="modal-header">
				<h4 class="modal-title">
					<span style="color: #643691;">Spring</span> 회원 가입
				</h4>
				<button type="button" class="close" data-dismiss="modal">×</button>

			</div>

			<div class="modal-body">

				<form action="#" name="signup" id="signUpForm" method="post"
					style="margin-bottom: 0;">
					<table
						style="cellpadding: 0; cellspacing: 0; margin: 0 auto; width: 100%">
						<tr>
							<td style="text-align: left">
								<p><strong>아이디를 입력해주세요.</strong>&nbsp;&nbsp;&nbsp;<span id="idChk"></span></p>
							</td>
								
							
						</tr>
						<tr>
							<td><input type="text" name="userId" id="user_id"
								class="form-control tooltipstered" maxlength="14"
								required="required" aria-required="true"
								style="margin-bottom: 25px; width: 100%; height: 40px; border: 1px solid #d9d9de"
								placeholder="숫자와 영어로 4-14자">
								</td>
							
						</tr>

						<tr>
							<td style="text-align: left">
								<p><strong>비밀번호를 입력해주세요.</strong>&nbsp;&nbsp;&nbsp;<span id="pwChk"></span></p>
							</td>
						</tr>
						<tr>
							<td><input type="password" size="17" maxlength="20" id="password"
								name="userPw" class="form-control tooltipstered" 
								maxlength="20" required="required" aria-required="true"
								style="ime-mode: inactive; margin-bottom: 25px; height: 40px; border: 1px solid #d9d9de"
								placeholder="영문과 특수문자를 포함한 최소 8자"></td>
						</tr>
						<tr>
							<td style="text-align: left">
								<p><strong>비밀번호를 재확인해주세요.</strong>&nbsp;&nbsp;&nbsp;<span id="pwChk2"></span></p>
							</td>
						</tr>
						<tr>
							<td><input type="password" size="17" maxlength="20" id="password_check"
								name="pw_check" class="form-control tooltipstered" 
								maxlength="20" required="required" aria-required="true"
								style="ime-mode: inactive; margin-bottom: 25px; height: 40px; border: 1px solid #d9d9de"
								placeholder="비밀번호가 일치해야합니다."></td>
						</tr>

						<tr>
							<td style="text-align: left">
								<p><strong>이름을 입력해주세요.</strong>&nbsp;&nbsp;&nbsp;<span id="nameChk"></span></p>
							</td>
						</tr>
						<tr>
							<td><input type="text" name="userName" id="user_name"
								class="form-control tooltipstered" maxlength="6"
								required="required" aria-required="true"
								style="margin-bottom: 25px; width: 100%; height: 40px; border: 1px solid #d9d9de"
								placeholder="한글로 최대 6자"></td>
						</tr>

						<tr>
							<td style="padding-top: 10px; text-align: center">
								<p><strong>회원가입을 환영합니다~~!</strong></p>
							</td>
						</tr>
						<tr>
							<td style="width: 100%; text-align: center; colspan: 2;"><input
								type="button" value="회원가입" 
								class="btn form-control tooltipstered" id="signup-btn"
								style="background-color: #643691; margin-top: 0; height: 40px; color: white; border: 0px solid #388E3C; opacity: 0.8">
							</td>
						</tr>

					</table>
				</form>
			</div>
		</div>
	</div>
</div>

<script>

	//start jQuery
	$(function() {
		
		//각 입력값들의 유효성 검증을 위한 정규표현식을 변수로 선언.
		const getIdCheck = RegExp(/^[a-zA-Z0-9]{4,14}$/);
		const getPwCheck = RegExp(/([a-zA-Z0-9].*[!,@,#,$,%,^,&,*,?,_,~])|([!,@,#,$,%,^,&,*,?,_,~].*[a-zA-Z0-9])/);
		const getNameCheck = RegExp(/^[가-힣]+$/);
		
		//입력값 중 하나라도 만족하지 못한다면 회원가입 처리를 막기 위한 논리형 변수 선언.
		let chk1 = false, chk2 = false, chk3 = false, chk4 = false;
		
		//회원 가입 시 사용자의 입력값들을 검증!
		
		//1. ID 입력값 검증
		$('#user_id').keyup(function() {
			
			if($(this).val() === '') {
				$(this).css('background', 'pink');
				$('#idChk').html('<b style="font-size: 14px; color: red">[아이디는 필수 정보입니다!]</b>');
				chk1 = false;
			}
			//아이디 입력값 유효성 검증 (영문, 숫자로만 4~14글자 허용)
			//정규표현식변수.test('검증하고 싶은 값') -> return boolean type
			//정규표현식에 어긋난 값이면 false, 올바른 값이면 true.
			else if(!getIdCheck.test($(this).val())) {
				$(this).css('background', 'pink');
				$('#idChk').html('<b style="font-size: 14px; color: red">[영문자, 숫자조합 4-14자!]</b>');
				chk1 = false;
			}
			//ID 중복 확인 (비동기 처리)
			//특정 로직의 실행이 끝날 때까지 기다리지 않고 먼저 코드를 실행.(페이지 전환 없이 통신)
			else {
				
				//ID 중복 확인 통신을 위해 사용자의 입력값을 가져오기
				const id = $(this).val();
				
				//ajax 호출.
				//클라이언트에서 서버와 비동기 통신을 진행하게 도와주는 ajax 함수.
				$.ajax({
					type: 'POST', //서버에 전송하는 HTTP 방식
					url: '/user/checkId', //서버 요청 url
					headers: {
						'Content-Type' : 'application/json'
					},
					dataType: 'text', //서버로부터 응답받을 데이터의 형태
					data: id, //서버로 전송할 데이터
					success : function(result) { //매개변수에 통신 성공 시 데이터가 저장됨.
						//서버와 통신 성공 시 실행할 내용.
						console.log('통신 성공!: ' + result);
						if(result === 'available') {
							$('#user_id').css('background', 'aqua');
							$('#idChk').html('<b style="font-size: 14px; color: green">[아이디 사용이 가능합니다!]</b>');
							chk1 = true;
						} else {
							$('#user_id').css('background', 'pink');
							$('#idChk').html('<b style="font-size: 14px; color: red">[아이디가 중복되었습니다!]</b>');
							chk1 = false;
						}
					},
					error: function(status, error) {
						console.log('통신 실패!');
						console.log(status, error);
					}
					
				}); //end ajax (아이디 중복 확인)
			}	
		}); //아이디 검증 끝.
		
		//2. 패스워드 입력값 검증.
		$('#password').keyup(function() {
			
			//비밀번호란 공백 확인.
			if($(this).val() === '') {
				$(this).css('background', 'pink');
				$('#pwChk').html('<b style="font-size: 14px; color: red">[비밀번호는 필수 정보입니다!]</b>');
				chk2 = false;
			}
			//비밀번호 유효성 검사
			else if(!getPwCheck.test($(this).val()) || $(this).val().length < 8) {
				$(this).css('background', 'pink');
				$('#pwChk').html('<b style="font-size: 14px; color: red">[특수문자 포함 8자 이상!]</b>');
				chk2 = false;
			}
			//통과
			else {
				$(this).css('background', 'aqua');
				$('#pwChk').html('<b style="font-size: 14px; color: green">[비밀번호 입력 완료!]</b>');
				chk2 = true;
			}
		}); //비밀번호 검증 끝.
		
		//3. 비밀번호 확인란 검증
		$('#password_check').keyup(function() {
			//비밀번호 확인란 공백 검증
			if($(this).val() === '') {
				$(this).css('background', 'pink');
				$('#pwChk2').html('<b style="font-size: 14px; color: red">[비밀번호 확인은 필수 정보입니다!]</b>');
				chk3 = false;
			}
			//비밀번호 확인란 유효성 검사
			else if($(this).val() !== $('#password').val()) {
				$(this).css('background', 'pink');
				$('#pwChk2').html('<b style="font-size: 14px; color: red">[비밀번호와 일치하지 않습니다!]</b>');
				chk3 = false;
			} else {
				$(this).css('background', 'aqua');
				$('#pwChk2').html('<b style="font-size: 14px; color: green">[비밀번호 확인 완료!]</b>');
				chk3 = true;
			}
		}); //비밀번호 확인 검증 끝.
		
		//4. 이름 입력값 검증
		$('#user_name').keyup(function() {
			//이름값 공백 확인
			if($(this).val() === '') {
				$(this).css('background', 'pink');
				$('#nameChk').html('<b style="font-size: 14px; color: red">[이름은 필수 정보입니다!]</b>');
				chk4 = false;
			}
			//이름값 유효성 검사
			else if(!getNameCheck.test($(this).val())) {
				$(this).css('background', 'pink');
				$('#nameChk').html('<b style="font-size: 14px; color: red">[이름은 한글로만 작성하세요!]</b>');
				chk4 = false;
			} else {
				$(this).css('background', 'aqua');
				$('#nameChk').html('<b style="font-size: 14px; color: green">[이름 입력 완료!]</b>');
				chk4 = true;
			}
			
		}); //이름 입력 검증 끝.
		
		
		
		//사용자가 회원 가입 버튼을 눌렀을 때의 이벤트 처리
		//사용자가 입력하는 4가지 데이터 중 단 하나라도 문제가 있다면
		//회원 가입 처리를 해 주지 말아야 겠죠?
		$('#signup-btn').click(function() {
			
			if(chk1 && chk2 && chk3 && chk4) {
				
				//아이디 정보
				const id = $('#user_id').val();
				//비밀번호 정보
				const pw = $('#password').val();
				//이름 정보
				const name = $('#user_name').val();
				
				//여러 개의 값을 보낼 때는 객체로 포장해서 전송
				//property 이름은 VO 객체의 변수명과 동일하게 (커맨드 객체 사용을 위해)
				const user = {
					'account' : id,
					'password' : pw,
					'name' : name
				}; //아직 자바스크립트 객체 (JSON x)
				
				//비동기 통신 시작!
				$.ajax({
					type: 'POST',
					url: '/user/',
					contentType: 'application/json',
					dataType: 'text',
					//JavaScript 객체를 JSON 문자열로 변환해 주는 메서드
					data: JSON.stringify(user),
					success: function(result) {
						console.log('통신 성공!: ' + result);
						alert('회원가입을 환영합니다.');
						location.href='/';
					},
					error: function() {
						alert('회원 가입 실패~!');
					}
					
				}); // end ajax (회원 가입 처리)
				
			} else { //입력값 4가지 중 하나라도 false라면
				alert('입력 정보를 다시 확인하세요!');
			}
			
		}); // 회원 가입 처리 끝.
		
		
		//////////////////////////////////////////////////////////
		
		//로그인 검증
		//ID 입력값 검증(공백, 정규표현식)
		$('#signInId').keyup(function() {
			if($(this).val() === '') {
				$(this).css('background-color', 'pink');
				$('#idCheck').html('<b style="font-size: 14px; color: red">[아이디는 필수 정보입니다!]</b>');
				chk1 = false;
			} else if(!getIdCheck.test($(this).val())) {
				$(this).css('background-color', 'pink');
				$('#idCheck').html('<b style="font-size: 14px; color: red">[영문, 숫자로 4-14자로 작성!]</b>');
				chk1 = false;
			} else {
				$(this).css('background-color', 'aqua');
				$('#idCheck').html('<b style="font-size: 14px; color: green">[아이디 입력 완료!]</b>');
				chk1 = true;
			}
		}); //아이디 입력값 검증 끝!
		
		//비밀번호 입력값 검증(공백, 정규표현식)
		$('#signInPw').keyup(function() {
			if($(this).val() === '') {
				$(this).css('background-color', 'pink');
				$('#pwCheck').html('<b style="font-size: 14px; color: red">[비밀번호 쓰세요!]</b>');
				chk2 = false;
			} else if(!getPwCheck.test($(this).val())) {
				$(this).css('background-color', 'pink');
				$('#pwCheck').html('<b style="font-size: 14px; color: red">[특수문자 포함 8자 이상!]</b>');
				chk2 = false;
			} else {
				$(this).css('background-color', 'aqua');
				$('#pwCheck').html('<b style="font-size: 14px; color: green">[비밀번호 입력 완료!]</b>');
				chk2 = true;
			}
		}); //비밀번호 입력값 검증 끝!
		
		//로그인 버튼 클릭 이벤트 (ID, 비밀번호 둘 다 올바른 값이어야 이벤트 진행)
		//chk1, chk2를 재활용 해서 사용했습니다.
		//ajax를 이용한 비동기 방식으로 로그인을 처리할 예정입니다.
		//비동기 처리를 안해도 되지만, 연습을 위해서 비동기 처리를 합니다.
		//굳이 비동기 처리가 필요 없는 곳은 남용하지 않아도 됩니다.
		
		$('#signIn-btn').click(function() {
			
			if(chk1 && chk2) {
				
				/*
				아이디, 비밀번호를 가져오셔서 객체로 포장하세요.
				비동기 통신을 진행하여 서버로 객체를 json형태로 전송하세요.
				그리고, console.log()로 서버가 보내온 데이터를 확인하여
				아이디가 없습니다, 비밀번호가 틀렸습니다, 로그인 성공이라는
				메세지를 브라우저의 console창에서 확인하세요.
				서버에서 클라이언트로 데이터 전송은 text로 이루어 질 것이며
				idFail, pwFail, loginSuccess라는 문자열을 리턴할 것입니다.
				전송방식: POST, url: /user/loginCheck
				*/
				
				const id = $('#signInId').val();
				const pw = $('#signInPw').val();
				
				const userInfo = {
					"account" : id,
					"password" : pw
				};
				
				$.ajax({
					type: 'POST',
					url: '/user/loginCheck',
					contentType: 'application/json',
					dataType: 'text',
					data: JSON.stringify(userInfo),
					success: function(data) {
						if(data === 'idFail') {
							//console.log('아이디가 없습니다.');
							$('#signInId').css('background-color', 'pink');
							$('#idCheck').html('<b style="font-size: 14px; color: red">[존재하지 않는 아이디입니다!]</b>');
							$('#signInId').val('');
							$('#signInId').focus(); //커서를 이동시키고, 스크롤도 해당 위치로 이동시키는 함수.
							chk1 = false, chk2 = false;
							
						} else if(data === 'pwFail') {
							//console.log('비밀번호가 틀렸습니다.');
							$('#signInPw').css('background-color', 'pink');
							$('#pwCheck').html('<b style="font-size: 14px; color: red">[비밀번호가 틀렸습니다!]</b>');
							$('#signInPw').val('');
							$('#signInPw').focus();
							chk2 = false;

						} else {
							//console.log('로그인 성공!');
							location.href='/';
						}
					},
					error: function() {
						console.log('통신 실패!');
					}
					
				}); //end ajax
				
			} else {
				alert('입력값을 다시 확인하세요!');
			}
			
		}); //로그인 버튼 클릭 이벤트 처리 끝!
		
		
		
		
	}); //end jQuery

</script>

전체 소스는 위와 같다. 이제 차근차근 쪼개서 분석해가고자 한다.

사전 변수 선언

//각 입력값들의 유효성 검증을 위한 정규표현식을 변수로 선언.
		const getIdCheck = RegExp(/^[a-zA-Z0-9]{4,14}$/);
		const getPwCheck = RegExp(/([a-zA-Z0-9].*[!,@,#,$,%,^,&,*,?,_,~])|([!,@,#,$,%,^,&,*,?,_,~].*[a-zA-Z0-9])/);
		const getNameCheck = RegExp(/^[가-힣]+$/);
		
		//입력값 중 하나라도 만족하지 못한다면 회원가입 처리를 막기 위한 논리형 변수 선언.
		let chk1 = false, chk2 = false, chk3 = false, chk4 = false;

정규표현식의 패턴에 대해 짧게 언급하고 넘어가자면,

  • a-zA-Z : 영어 알파벳. 범위는 - 로 지정한다.
  • 0-9 : 숫자. 범위는 - 로 지정한다.
  • [] : 검색 패턴. 괄호 안의 문자들 중 하나라고 생각하면 된다.
  • [^문자] : 괄호 안의 문자를 제외한 것이라고 생각하면 된다.
  • ^문자열 : 특정 문자열로 시작. (괄호 없음에 주의)
  • ? : 없거나 or 최대 한개
  • * : 없거나 or 여러개 있거나
  • + : 최소 한개 or 여러개
  • {min, MAX} : min개 이상, MAX개 이하

아이디 입력값 검증 파트

1. 아이디 입력값 검증

//1. ID 입력값 검증
		$('#user_id').keyup(function() {
			
			if($(this).val() === '') {
				$(this).css('background', 'pink');
				$('#idChk').html('<b style="font-size: 14px; color: red">[아이디는 필수 정보입니다!]</b>');
				chk1 = false;
			}
			//아이디 입력값 유효성 검증 (영문, 숫자로만 4~14글자 허용)
			//정규표현식변수.test('검증하고 싶은 값') -> return boolean type
			//정규표현식에 어긋난 값이면 false, 올바른 값이면 true.
			else if(!getIdCheck.test($(this).val())) {
				$(this).css('background', 'pink');
				$('#idChk').html('<b style="font-size: 14px; color: red">[영문자, 숫자조합 4-14자!]</b>');
				chk1 = false;
			}
			//ID 중복 확인 (비동기 처리)
			//특정 로직의 실행이 끝날 때까지 기다리지 않고 먼저 코드를 실행.(페이지 전환 없이 통신)
			else {
				
				//ID 중복 확인 통신을 위해 사용자의 입력값을 가져오기
				const id = $(this).val();
				
				//ajax 호출.
				//클라이언트에서 서버와 비동기 통신을 진행하게 도와주는 ajax 함수.
				$.ajax({
					type: 'POST', //서버에 전송하는 HTTP 방식
					url: '/user/checkId', //서버 요청 url
					headers: {
						'Content-Type' : 'application/json'
					},
					dataType: 'text', //서버로부터 응답받을 데이터의 형태
					data: id, //서버로 전송할 데이터
					success : function(result) { //매개변수에 통신 성공 시 데이터가 저장됨.
						//서버와 통신 성공 시 실행할 내용.
						console.log('통신 성공!: ' + result);
						if(result === 'available') {
							$('#user_id').css('background', 'aqua');
							$('#idChk').html('<b style="font-size: 14px; color: green">[아이디 사용이 가능합니다!]</b>');
							chk1 = true;
						} else {
							$('#user_id').css('background', 'pink');
							$('#idChk').html('<b style="font-size: 14px; color: red">[아이디가 중복되었습니다!]</b>');
							chk1 = false;
						}
					},
					error: function(status, error) {
						console.log('통신 실패!');
						console.log(status, error);
					}
					
				}); //end ajax (아이디 중복 확인)
			}	
		}); //아이디 검증 끝.

부분 1. 아이디가 입력되지 않은 경우

$('#user_id').keyup(function() {
			
			if($(this).val() === '') {
				$(this).css('background', 'pink');
				$('#idChk').html('<b style="font-size: 14px; color: red">[아이디는 필수 정보입니다!]</b>');
				chk1 = false;
			}

user_id 라는 id# 기호로 끌어오고 있다.
그리고 그것을 $(선택자(id 혹은 클래스명)) 의 식으로 가져오고 있는데, 이건 jQuery 문법이다.

만약 바닐라 js였다면, document.getElementById('user_id') 와 같은 식으로 끌어와야 했을 것이다.

그리고 해당 id에 대해 keyup이 일어난 경우, jQuery에서 css 스타일을 주는 함수를 사용해서 배경색을 핑크색으로 만들고 '아이디는 필수 정보입니다' 라는 문장을 표시하게 하였다.

부분 2. 아이디 입력값 유효성 검증

//아이디 입력값 유효성 검증 (영문, 숫자로만 4~14글자 허용)
			//정규표현식변수.test('검증하고 싶은 값') -> return boolean type
			//정규표현식에 어긋난 값이면 false, 올바른 값이면 true.
			else if(!getIdCheck.test($(this).val())) {
				$(this).css('background', 'pink');
				$('#idChk').html('<b style="font-size: 14px; color: red">[영문자, 숫자조합 4-14자!]</b>');
				chk1 = false;
			}

아까 위에서 미리 선언해둔 사전 변수를 사용해 검사한다.

const getIdCheck = RegExp(/^[a-zA-Z0-9]{4,14}$/); 이거에 의해서 검사되므로 최소 4자 ~ 최대14자 내에서 검사되고 영문 소/대문자 및 숫자로 이루어져 있어야 한다.

만약 그렇지 않은 경우 jQuery의 css() 함수로 또 스타일을 준다.

부분 3. ID 중복 확인 (비동기 처리)

//ID 중복 확인 (비동기 처리)
			//특정 로직의 실행이 끝날 때까지 기다리지 않고 먼저 코드를 실행.(페이지 전환 없이 통신)
			else {
				
				//ID 중복 확인 통신을 위해 사용자의 입력값을 가져오기
				const id = $(this).val();
				
				//ajax 호출.
				//클라이언트에서 서버와 비동기 통신을 진행하게 도와주는 ajax 함수.
				$.ajax({
					type: 'POST', //서버에 전송하는 HTTP 방식
					url: '/user/checkId', //서버 요청 url
					headers: {
						'Content-Type' : 'application/json'
					},
					dataType: 'text', //서버로부터 응답받을 데이터의 형태
					data: id, //서버로 전송할 데이터
					success : function(result) { //매개변수에 통신 성공 시 데이터가 저장됨.
						//서버와 통신 성공 시 실행할 내용.
						console.log('통신 성공!: ' + result);
						if(result === 'available') {
							$('#user_id').css('background', 'aqua');
							$('#idChk').html('<b style="font-size: 14px; color: green">[아이디 사용이 가능합니다!]</b>');
							chk1 = true;
						} else {
							$('#user_id').css('background', 'pink');
							$('#idChk').html('<b style="font-size: 14px; color: red">[아이디가 중복되었습니다!]</b>');
							chk1 = false;
						}
					},
					error: function(status, error) {
						console.log('통신 실패!');
						console.log(status, error);
					}
					
				}); //end ajax (아이디 중복 확인)
			}	
		}); //아이디 검증 끝.

아이디가 비어있지 않으며, 정규표현식을 잘 지켰다면 이제는 중복 검사를 할 차례이다.

먼저 아이디 중복 확인을 위한 통신을 하기 위해 const id 변수를 만들고 값을 끌어온다.

그리고 $.ajax({ }); 이렇게 ajax 파트가 시작되는데, 전에 라이브러리를 로드한 기억이 없어서.. 어떻게 바로 사용이 가능한지 잘 모르겠다.
+아, 찾아보니까 jQuery에 ajax가 포함되어 있는 것 같다.

여튼 짧게 분석하고 넘어가면 ajax는 JS와 XML을 이용한 비동기적 정보교환 기법이라고 한다.

  • type : 서버에 전송하는 HTTP 방식 GET, POST, DELETE, PUT 등.
  • url : 서버에 요청할 url (예. url : 'board/checkContent')
  • headers : ajax를 이용해 특정 url을 호출할 경우, http header에 특정 내용을 담아서 전송해야 하는 경우에 사용한다. json 배열과 같은 방식으로 이름과 값을 넣어주면 된다.
  • dataType : 서버로부터 응답받을 데이터의 형태. xml, html, script, json, jsonp, text 등이 있다고 한다.
  • data : 서버로 전송할 데이터를 넣는다. 여기서는 우리가 아까 가져온 유저의 id 를 보내고 있다.
  • success, error : 통신 성공, 실패 시 각각 실행할 내용을 적는다.

대충 각 키워드에 대한 분석은 이렇고 흐름을 살펴보자면,

  1. POST 방식으로 UserController/user/checkId 요청을 보낸다.
    반환받는 데이터 타입은 text 형태로 받고, 서버에는 id 데이터를 전송한다.

  2. UserController 클래스에서 요청을 받을 메서드를 잠시 확인해보자.

//아이디 중복 여부 체크
	@PostMapping("/checkId")
	public String checkId(@RequestBody String account) {
		System.out.println("/user/checkId: POST");
		System.out.println("param: " + account);
		
		int checkNum = service.checkId(account);
		
		if(checkNum == 1) {
			System.out.println("아이디가 중복됨!");
			return "duplicated";
		} else {
			System.out.println("아이디 사용 가능!");
			return "available";
		}
	}

@RequestParam 이 아니라 @RequestBody 로 받아오고 있는데, 전자는 객체 생성이 불가능하고 후자는 가능하다고 한다.

근데 지금의 경우 Person 같은 객체가 아니라 그냥 String 인데 굳이 @RequestBody 를 썼어야 했나? 싶기도 하다. (이 날 강의가 녹음이 안돼서 왜 이렇게 쓰는지 이유를 잘 알 수가 없다..)

여튼, 받아온 id에 대해 service 클래스로 id를 넘겨 테스트하게 하는데, 아마 mapper 로 가서 id가 있는지 COUNT(*) 등으로 검사를 하고 반환해 주고 있을 것 같다.

<select id="checkId" resultType="int">
		SELECT COUNT(*)
		FROM mvc_user
		WHERE account = #{account}
</select>

빙고! 찾은 id의 개수를 int로 반환하고 있고, 어차피 중복 id는 생성되지 않으므로 최대 1이 반환되는 수준에서 그칠 것이다.

그러면 이제 반환된 int값에 의해 중복인지 아닌지 결과를 String 문자열 형태로 반환해 줄 것이다.

이제 jQuery의 css 함수를 통해서 사용이 가능/불가능한지 여부에 따라 각각 스타일을 달리 주게 된다.

패스워드 입력값 검증 파트

//2. 패스워드 입력값 검증.
		$('#password').keyup(function() {
			
			//비밀번호란 공백 확인.
			if($(this).val() === '') {
				$(this).css('background', 'pink');
				$('#pwChk').html('<b style="font-size: 14px; color: red">[비밀번호는 필수 정보입니다!]</b>');
				chk2 = false;
			}
			//비밀번호 유효성 검사
			else if(!getPwCheck.test($(this).val()) || $(this).val().length < 8) {
				$(this).css('background', 'pink');
				$('#pwChk').html('<b style="font-size: 14px; color: red">[특수문자 포함 8자 이상!]</b>');
				chk2 = false;
			}
			//통과
			else {
				$(this).css('background', 'aqua');
				$('#pwChk').html('<b style="font-size: 14px; color: green">[비밀번호 입력 완료!]</b>');
				chk2 = true;
			}
		}); //비밀번호 검증 끝.

부분 1. 시작 부분 및 공백 확인

keyup() 에 대해서 아까 분석을 안 했는데, 찾아보니 키가 떼 질때 불려지는 함수가 맞긴 했다. 중요한건 떼 질 때마다 함수가 호출되는 것인 듯.

여튼, 비밀번호란이 공백인 경우를 먼저 검사하고 있다.

부분 2. 유효성 검사

getPwCheck 정규표현식을 토대로 test() 함수를 불러 $(this) 값을 검증하고 있다. 거기서 혹은 8자 이하의 비밀번호가 입력된 경우에도 해당 분기를 타도록 하고 있다.

부분 3. 통과

상기한 공백 및 유효성 검사와 관련된 부분에서 이상이 없는 경우, 사용 가능한 비밀번호라고 확인시켜 주도록 한다.

비밀번호 확인란 검증

//3. 비밀번호 확인란 검증
		$('#password_check').keyup(function() {
			//비밀번호 확인란 공백 검증
			if($(this).val() === '') {
				$(this).css('background', 'pink');
				$('#pwChk2').html('<b style="font-size: 14px; color: red">[비밀번호 확인은 필수 정보입니다!]</b>');
				chk3 = false;
			}
			//비밀번호 확인란 유효성 검사
			else if($(this).val() !== $('#password').val()) {
				$(this).css('background', 'pink');
				$('#pwChk2').html('<b style="font-size: 14px; color: red">[비밀번호와 일치하지 않습니다!]</b>');
				chk3 = false;
			} else {
				$(this).css('background', 'aqua');
				$('#pwChk2').html('<b style="font-size: 14px; color: green">[비밀번호 확인 완료!]</b>');
				chk3 = true;
			}
		}); //비밀번호 확인 검증 끝.

비밀번호를 적었으니, 확인란에 대한 검증도 진행해야 한다.
위의 비밀번호란 검증과 거의 동일한데, 공백을 검사하는 부분은 동일하고 유효성은 정규표현식으로 체크할 필요가 없고 (이미 했으니) 위에서 입력한 비밀번호와 완전히 같게 입력이 되었는지만 검사하도록 한다.

둘 다 통과했으면, 유효하다고 체크해준다. (chk3 = true)

이름 입력값 검증

//4. 이름 입력값 검증
		$('#user_name').keyup(function() {
			//이름값 공백 확인
			if($(this).val() === '') {
				$(this).css('background', 'pink');
				$('#nameChk').html('<b style="font-size: 14px; color: red">[이름은 필수 정보입니다!]</b>');
				chk4 = false;
			}
			//이름값 유효성 검사
			else if(!getNameCheck.test($(this).val())) {
				$(this).css('background', 'pink');
				$('#nameChk').html('<b style="font-size: 14px; color: red">[이름은 한글로만 작성하세요!]</b>');
				chk4 = false;
			} else {
				$(this).css('background', 'aqua');
				$('#nameChk').html('<b style="font-size: 14px; color: green">[이름 입력 완료!]</b>');
				chk4 = true;
			}
			
		}); //이름 입력 검증 끝.

굳이 너무 자잘하게 자를 필요가 없다 싶긴 하다.

  1. 공백인지 먼저 검사하고
  2. 미리 정해둔 정규표현식 기준에 부합하는지 검사하고
  3. 둘 다 통과했으면 chk4 = true

회원 가입 처리

//사용자가 회원 가입 버튼을 눌렀을 때의 이벤트 처리
		//사용자가 입력하는 4가지 데이터 중 단 하나라도 문제가 있다면
		//회원 가입 처리를 해 주지 말아야 겠죠?
		$('#signup-btn').click(function() {
			
			if(chk1 && chk2 && chk3 && chk4) {
				
				//아이디 정보
				const id = $('#user_id').val();
				//비밀번호 정보
				const pw = $('#password').val();
				//이름 정보
				const name = $('#user_name').val();
				
				//여러 개의 값을 보낼 때는 객체로 포장해서 전송
				//property 이름은 VO 객체의 변수명과 동일하게 (커맨드 객체 사용을 위해)
				const user = {
					'account' : id,
					'password' : pw,
					'name' : name
				}; //아직 자바스크립트 객체 (JSON x)
				
				//비동기 통신 시작!
				$.ajax({
					type: 'POST',
					url: '/user/',
					contentType: 'application/json',
					dataType: 'text',
					//JavaScript 객체를 JSON 문자열로 변환해 주는 메서드
					data: JSON.stringify(user),
					success: function(result) {
						console.log('통신 성공!: ' + result);
						alert('회원가입을 환영합니다.');
						location.href='/';
					},
					error: function() {
						alert('회원 가입 실패~!');
					}
					
				}); // end ajax (회원 가입 처리)
				
			} else { //입력값 4가지 중 하나라도 false라면
				alert('입력 정보를 다시 확인하세요!');
			}
			
		}); // 회원 가입 처리 끝.

앞서 모든 검사를 통과했다면 chk1 ~ chk4 가 전부 true 가 되었을 것이다.

부분 1. 앞서 입력한 데이터가 전부 유효했던 경우

차례로 id, pw, name 을 태그의 value 로 부터 얻어와서 const 변수에 저장한다.

보낼 객체의 값이 여러개이므로, 값을 포장해서 보내는데 property 이름은 VO 객체의 이름과 동일하게 해서 보내야 한다. (커맨드 객체의 자동 매핑은 이름이 같아야 하니까)
그리고 JSON처럼 보이는데 아직은 JSON이 아니라고 한다. 그런갑다..

그리고 이제 ajax 비동기 통신을 시작한다.
contentType 은, 보내는 데이터의 타입을 의미한다. dataType 은 아까도 설명했지만 서버측에서 받는 타입의 형태를 의미한다.

그리고 서버로 보낼 data 는 아까 포장한 객체를 보내는데, 아직 JSON 타입이 아니라고 했었으므로 JSON으로 만들어서 보내야하니 JSON.stringify() 메서드를 사용한다.

그러면 이제 회원 가입 요청을 받는 컨트롤러의 해당 메서드로 가보자.

UserController.java

...
//회원 가입 요청 처리
	@PostMapping("/")
	public String register(@RequestBody UserVO vo) {
		System.out.println("/user/: POST");
		service.register(vo);
		return "joinSuccess";
	}
...

보면, UserVO vo 이렇게 커맨드 객체로 받고 있는 것을 볼 수 있고 (그래서 아까 property의 이름을 VO의 멤버변수 이름과 동일하게 했다) service 클래스의 register 메서드를 호출하고 있다.

UserService.java

...
@Override
	public void register(UserVO user) {
		
		//회원 비밀번호를 암호화 인코딩
		BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
		System.out.println("암호화 하기 전 비번: " + user.getPassword());
		
		//비밀번호를 암호화 해서 user객체에 다시 저장하기.
		String securePw = encoder.encode(user.getPassword());
		System.out.println("암호화 후 비번: " + securePw);
		user.setPassword(securePw);
		
		
		mapper.register(user);
	}
...

지금 Spring Security가 적용되어 있긴 한데, 이건 있다가 설명할테니 무시하고 구조만 보자면 mapperregister() 메서드를 부르고 있다.

UserMapper.xml

...
<insert id="register">
		INSERT INTO mvc_user
		(account, password, name)
		VALUES(#{account},#{password},#{name})
</insert>
...

account, password, name 컬럼에 각각 값을 끌어와서 넣어준다.
근데 보통 VALUES(?, ?, ?) 하고 이렇게 먼저 온 다음에, account, password, name 이렇게 오지 않나? 흠.. 뭐 여튼 이렇게 값이 들어갈 거다.

값이 잘 들어가면 joinSuccess 문자열을 반환할 것이고, 아마 모종의 이유로 메서드가 터지면 아무것도 반환을 안 하거나 할 테니 그에 따른 분기 처리를 해 준다.

잘 처리된 경우에는 location.href='/' 로 리다이렉트한다. 근데 이렇게 해서 얻는게 뭐가 있지..? 컨트롤러에 같은 그걸로 들어가봤자 의미가 없는 거 같기는 한데.. 여기는 잘 모르겠다.

로그인 검증 파트

//로그인 검증
		//ID 입력값 검증(공백, 정규표현식)
		$('#signInId').keyup(function() {
			if($(this).val() === '') {
				$(this).css('background-color', 'pink');
				$('#idCheck').html('<b style="font-size: 14px; color: red">[아이디는 필수 정보입니다!]</b>');
				chk1 = false;
			} else if(!getIdCheck.test($(this).val())) {
				$(this).css('background-color', 'pink');
				$('#idCheck').html('<b style="font-size: 14px; color: red">[영문, 숫자로 4-14자로 작성!]</b>');
				chk1 = false;
			} else {
				$(this).css('background-color', 'aqua');
				$('#idCheck').html('<b style="font-size: 14px; color: green">[아이디 입력 완료!]</b>');
				chk1 = true;
			}
		}); //아이디 입력값 검증 끝!
		
		//비밀번호 입력값 검증(공백, 정규표현식)
		$('#signInPw').keyup(function() {
			if($(this).val() === '') {
				$(this).css('background-color', 'pink');
				$('#pwCheck').html('<b style="font-size: 14px; color: red">[비밀번호 쓰세요!]</b>');
				chk2 = false;
			} else if(!getPwCheck.test($(this).val())) {
				$(this).css('background-color', 'pink');
				$('#pwCheck').html('<b style="font-size: 14px; color: red">[특수문자 포함 8자 이상!]</b>');
				chk2 = false;
			} else {
				$(this).css('background-color', 'aqua');
				$('#pwCheck').html('<b style="font-size: 14px; color: green">[비밀번호 입력 완료!]</b>');
				chk2 = true;
			}
		}); //비밀번호 입력값 검증 끝!

ID, PW 입력 검증 파트인데, 공백 검증과 앞서 정의한 정규표현식에 부합하는 값이 입력되었는지 체크한다.

그래서 다 통과하면, chk1, chk2 두 변수를 재활용해서 ID, PW 각각이 잘 입력되었는지 체크해둔다.

로그인 버튼 클릭 이벤트 파트

//로그인 버튼 클릭 이벤트 (ID, 비밀번호 둘 다 올바른 값이어야 이벤트 진행)
		//chk1, chk2를 재활용 해서 사용했습니다.
		//ajax를 이용한 비동기 방식으로 로그인을 처리할 예정입니다.
		//비동기 처리를 안해도 되지만, 연습을 위해서 비동기 처리를 합니다.
		//굳이 비동기 처리가 필요 없는 곳은 남용하지 않아도 됩니다.
		
		$('#signIn-btn').click(function() {
			
			if(chk1 && chk2) {
				
				/*
				아이디, 비밀번호를 가져오셔서 객체로 포장하세요.
				비동기 통신을 진행하여 서버로 객체를 json형태로 전송하세요.
				그리고, console.log()로 서버가 보내온 데이터를 확인하여
				아이디가 없습니다, 비밀번호가 틀렸습니다, 로그인 성공이라는
				메세지를 브라우저의 console창에서 확인하세요.
				서버에서 클라이언트로 데이터 전송은 text로 이루어 질 것이며
				idFail, pwFail, loginSuccess라는 문자열을 리턴할 것입니다.
				전송방식: POST, url: /user/loginCheck
				*/
				
				const id = $('#signInId').val();
				const pw = $('#signInPw').val();
				
				const userInfo = {
					"account" : id,
					"password" : pw
				};
				
				$.ajax({
					type: 'POST',
					url: '/user/loginCheck',
					contentType: 'application/json',
					dataType: 'text',
					data: JSON.stringify(userInfo),
					success: function(data) {
						if(data === 'idFail') {
							//console.log('아이디가 없습니다.');
							$('#signInId').css('background-color', 'pink');
							$('#idCheck').html('<b style="font-size: 14px; color: red">[존재하지 않는 아이디입니다!]</b>');
							$('#signInId').val('');
							$('#signInId').focus(); //커서를 이동시키고, 스크롤도 해당 위치로 이동시키는 함수.
							chk1 = false, chk2 = false;
							
						} else if(data === 'pwFail') {
							//console.log('비밀번호가 틀렸습니다.');
							$('#signInPw').css('background-color', 'pink');
							$('#pwCheck').html('<b style="font-size: 14px; color: red">[비밀번호가 틀렸습니다!]</b>');
							$('#signInPw').val('');
							$('#signInPw').focus();
							chk2 = false;

						} else {
							//console.log('로그인 성공!');
							location.href='/';
						}
					},
					error: function() {
						console.log('통신 실패!');
					}
					
				}); //end ajax
				
			} else {
				alert('입력값을 다시 확인하세요!');
			}
			
		}); //로그인 버튼 클릭 이벤트 처리 끝!

앞서 입력한 아이디, 패스워드가 전부 조건에 부합할 경우에 진입한다.

부분 1. 객체 포장

$() 로 얻어와 const 타입의 변수에 저장한다.
그리고 아직은 JSON 은 아니지만 js 타입의 객체에 VO 멤버변수와 이름이 같게 property 의 이름을 정해준다.

부분 2. ajax 비동기 통신 처리

이젠 대략적으로 어떤 느낌인지 분석이 되고 있을 테니, 컨트롤러단에서는 어떻게 하고 있는지 잠시 보러 갔다 오자.

UserController.java

...
//로그인 요청 처리
	@PostMapping("/loginCheck")
	public String loginCheck(@RequestBody UserVO vo, /*HttpServletRequest request*/
								HttpSession session) {
		System.out.println("/user/loginCheck: POST");
		System.out.println("param: " + vo);
		
		//서버에서 세션 객체를 얻는 방법
		//1. HttpServletRequest 객체 사용
		//HttpSession session = request.getSession();
		
		//2. 매개값으로 HttpSession 객체 받아서 사용.
		
		
		UserVO dbData = service.selectOne(vo.getAccount());
		BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
		
		if(dbData != null) {
			if(encoder.matches(vo.getPassword(), dbData.getPassword())) {
				//로그인 성공 회원을 대상으로 세션 정보를 생성
				session.setAttribute("login", dbData);
				
				return "loginSuccess";
			} else {
				return "pwFail";
			}
		} else {
			return "idFail";
		}
	}
...

일단 또 @RequestBody 어노테이션으로 아까 뷰에서 포장해서 보낸 객체를 커맨드 객체 형식으로 받고 있다.

그리고 중요한건 HttpSession 으로 세션 객체를 얻어온 부분인데..

찾아보니 HttpServletRequest 는 리퀘스트 중에만 존재한다고 하니까 아마 세션 정보가 금방 없어지는 문제가 생길 듯?

반면 HttpSession 은 클라이언트가 접속 중인 때에만 존재한다고 한다. 어쨌든 로그인중에는 세션이 유지되어야 하니 이 쪽을 쓰는게 맞는 것 같다.

그래서 이제
UserVO dbData = service.selectOne(vo.getAccount()); 를 통해 프론트단에서 넘어왔던 포장된 객체(UserVO)의 account 값을 가져와 service 클래스의 selectOne() 메서드를 호출한다.

서비스 클래스의 selectOne() 메서드는 account 컬럼이 일치하는 레코드를 찾아와 반환해줄 것이다.

그러면 이제 일치하지 않았거나 존재하지 않는 경우가 있을 수 있으므로 if(dbData != null) 로 확실하게 값이 전달되어 온 경우만 분기에 진입하도록 하고,

이 경우엔 어쨌든 id 자체는 일치했단 뜻이므로 로그인 처리를 해 주기 이전에 패스워드가 일치하는지 검사 후 일치하면 session.setAttribute() 메서드를 통해 dbData 를 담은 "login" 이라는 이름의 세션을 생성해준다.

만약 비밀번호가 일치하지 않은 경우나, 아이디 자체가 없는 경우에는 다른 분기를 타서 반환을 해 준다.

하여튼 이것으로 login_modal 의 로그인과 관련된 ajax 비동기 처리는 끝이 났다.

이제는 스프링 시큐리티를 알아볼 차례다.

스프링 시큐리티

왜 갑자기 스프링 시큐리티라는걸 설치하나 싶을텐데, 다음과 같은 이유 때문이다.

  1. 회원 가입을 한다.
  2. DB에 회원 테이블을 조회해보자
  3. 회원 비밀번호들이 평문 으로 저장되어있는 모습을 볼 수 있다.

이러면 만에 하나 DB가 탈취되었을 경우 전 회원의 비밀번호가 그냥 공개되는 최아그이 사태가 벌어지므로, 우리는 평문이 아닌 암호문으로 암호화를 해 줄 필요가 있다.

스프링 시큐리티 설치

  1. MVNRepository 에 접속
  2. Spring-Security 검색
  3. Spring-Security Web 선택

https://mvnrepository.com/artifact/org.springframework.security/spring-security-web

상기 링크를 눌러 한번에 이동도 가능.

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>5.3.13.RELEASE</version>
</dependency>

우리는 여러 버전 중 5.3.13 RELEASE 버전을 가져다 쓰기로 결정했다.

상기한 코드를 pom.xml 에 붙여넣기 하고 프로젝트를 다시 로드하면 끝.

이제는 아까 위에서 설명을 생략하고 넘어갔던 암호화/복호화가 적용된 메서드들을 다시 확인해보도록 하자.

스프링 시큐리티 적용

일단 먼저 회원등록 시 비밀번호가 평문으로 저장되지 않도록 해야 하니, UserService 클래스에 가서 register() 메서드를 고치도록 하자.

UserService.java

...
@Override
	public void register(UserVO user) {
		
		//회원 비밀번호를 암호화 인코딩
		BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
		System.out.println("암호화 하기 전 비번: " + user.getPassword());
		
		//비밀번호를 암호화 해서 user객체에 다시 저장하기.
		String securePw = encoder.encode(user.getPassword());
		System.out.println("암호화 후 비번: " + securePw);
		user.setPassword(securePw);
		
		
		mapper.register(user);
	}
...

BCryptPasswordEncoder 라는 클래스를 통해, 비밀번호를 암호화해 줄 객체를 선언할 수 있다. 암호화 시에는 encode() 메서드를 호출하고 매개 변수로는 암호화하고 싶은 String 문자열을 전달한다.

자, 여기까지만 했으면 일단 회원 가입 시에 비밀번호가 평문으로 저장되지 않으므로 안전해졌다고 볼 수는 있다.

문제는, 회원가입 후 로그인을 할 때 발생하는데 로그인 시에는 유저가 암호를 평문으로 입력하지만 저장된 암호는 평문이 아니므로 그대로 불러와 매치시키는 경우 일치하지 않아 문제가 생긴다.

따라서, 우리는 컨트롤러에서 로그인 요청을 받는 메서드를 고쳐줄 필요가 생긴다.

UserController.java

...
//로그인 요청 처리
	@PostMapping("/loginCheck")
	public String loginCheck(@RequestBody UserVO vo, /*HttpServletRequest request*/
								HttpSession session) {
		System.out.println("/user/loginCheck: POST");
		System.out.println("param: " + vo);
		
		//서버에서 세션 객체를 얻는 방법
		//1. HttpServletRequest 객체 사용
		//HttpSession session = request.getSession();
		
		//2. 매개값으로 HttpSession 객체 받아서 사용.
		
		
		UserVO dbData = service.selectOne(vo.getAccount());
		BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
		
		if(dbData != null) {
			if(encoder.matches(vo.getPassword(), dbData.getPassword())) {
				//로그인 성공 회원을 대상으로 세션 정보를 생성
				session.setAttribute("login", dbData);
				
				return "loginSuccess";
			} else {
				return "pwFail";
			}
		} else {
			return "idFail";
		}
	}
...

아까와 마찬가지로 인코딩 객체를 만드는것까진 같다.
하지만 이번엔 encode() 를 부르지 않고, matches() 메서드를 부른다.

이 메서드는 메서드 내부에서 평문과 암호화된 문장이 일치하는지 비교해주는 역할을 한다.

세션 관련

HTTP 프로토콜을 기반으로 하고 있으므로 사용자의 로그인 정보를 쿠키, 세션 등으로 저장하지 않으면 로그인 성공 후 다른 페이지로 넘어갈 때 바로 사용자 정보가 없어지므로 로그인은 무효가 되는 것이나 다름없을 것이다.

스프링에서 세션을 사용하는 방법은 상기한 2가지가 있는 것 같다.

처음 로그인 페이지에 도달했을 때, 세션에 id 속성이 존재하는 경우에는 로그인 중임을 의미하므로 마이 페이지로 리다이렉트시키고, 존재하지 않는 경우에는 로그인 페이지로 리다이렉트를 시킨다.

그리고 redirect 시 파라미터 값을 전달하기 위해서는 addFlashAttribute() 메서드를 사용하면 된다.

로그인 관련 로직, 뷰 수정

header.jsp

<c:if test="${login != null}">
          	<li class="nav-item">
	            <a class="nav-link js-scroll-trigger" href="#">MYPAGE</a>
	        </li>
	        <li class="nav-item">
	            <a class="nav-link js-scroll-trigger" href="<c:url value='/user/logout' />" onclick="return confirm('정말 로그아웃 하시겠어요?')">LOGOUT</a>
	        </li>
          </c:if>
          
          <c:if test="${login == null}">
	          <li class="nav-item">
	            <a class="nav-link js-scroll-trigger" data-toggle="modal" data-target="#log-in">LOGIN</a>
	          </li>
          </c:if>

상단 내비게이션 바에서 내비게이션 항목을 표시하기 전에, login 이라는 세션 값이 true인지 false인지 검사하도록 한다.

유효한 경우에는 MYPAGE 와 LOGOUT 버튼을 표시하고, 유효하지 않은 경우엔 LOGIN 버튼을 표시한다.

그리고 LOGOUT 버튼에 요청을 만들어 두었기 때문에 컨트롤러에서 요청을 처리할 수 있도록 해 준다.

UserController.java

...
//로그아웃 처리
	@GetMapping("/logout")
	public ModelAndView logout(HttpSession session, RedirectAttributes ra) {
		System.out.println("/user/logout: GET");
		
//		session.invalidate();
		session.removeAttribute("login");
		
		ra.addFlashAttribute("msg", "logout");
		
//		ModelAndView mv = new ModelAndView();
//		mv.setViewName("/");
		
		return new ModelAndView("redirect:/");
	}
...

매개값으로 HttpSession 을 받아서 세션값을 얻어오거나 삭제할 수 있도록 해 주고, 뷰에 파라미터 값을 전달하기 위해서 RedirectAttributes 클래스 또한 매개변수로 받아오도록 한다.

header.jsp

...
<script>
	const msg = '${msg}';
	if(msg === 'logout') {
		alert('로그아웃 처리되었습니다.');
	}
</script>
...

이제 다시 header.jsp 에 방금 로그아웃 메서드에서 보낸 msg를 alert로 창을 띄워 내용을 표시해 주면 끝이다.

이것으로 오늘 강의는 종료되었다.

0개의 댓글