관리 메뉴

Just Do it

[SJBoard 프로젝트] 스프링 회원가입 기능 만들기 2 : 회원 정보 조회, 수정, 삭제 기능 추가 본문

신입 개발자가 되기 위해 공부했던 독학 자료들/SJBoard 프로젝트를 통한 스프링 공부

[SJBoard 프로젝트] 스프링 회원가입 기능 만들기 2 : 회원 정보 조회, 수정, 삭제 기능 추가

Seojoo21 2022. 3. 18. 17:03

처음 회원 정보 조회 기능 추가 당시 현재 로그인한 사용자의 정보를 가져올 때 세션을 이용해야 한다는 것을 몰라서 엄청난 시간을 통으로 허비했다.

 

하지만 session으로 아무리 해봐도 제대로 값이 넘어오지 않아 좌절하던 중 결국 방법을 찾아냈는데 바로 컨트롤 단에서 스프링 시큐리티의 Principal과 Authentication 객체를 이용하는 것이었다. 그리고 목요일 밤에 뒤늦게 성공! 

 

그렇게 목요일 밤~금요일에 걸쳐 만든 회원 정보 조회, 수정, 삭제 기능을 완성했다.

 

다른 사람들에게는 아무것도 아닌 쉽고 단순한 작업이겠지만 나에게는 정말 눈물나는 작업이었다.

 

 

자 그럼 이제 그 과정을 살펴보자! 

 

도움 받은 블로그 글:

https://melonpeach.tistory.com/40?category=806570

https://kimvampa.tistory.com/89?category=771727 

 

일단 어제 아래 순서대로 기본적인 회원 가입 기능을 구현하였다.

 

----------------------내가 생각한 회원 가입 기능 구현의 순서-----------------------------------

 

1. 회원 정보를 저장할 데이터베이스 생성 (OracleDB, MySQL등... 나는 OracleDB) 

 

2. 회원 가입 영속 계층 구현

- MemberVO : 회원 정보 변수 선언 

- MemberMapper 인터페이스: 회원 가입 및 회원 정보 처리 관련 SQL 처리를 위한 인터페이스

- MemberMapper.xml : 회원 가입 및 회원 정보 처리 관련 SQL 파일

- MemberMapperTests : 회원 가입 처리 테스트 파일 

 

3. 회원 가입 비즈니스(서비스) 계층 구현 

- MemberService 인터페이스: 실제 회원 가입 서비스 메서드 선언  

- MemberServiceImpl : 실제 회원 가입 서비스 메서드 구현 (MemberService 인터페이스를 구현) 

- MemberServiceTests : 회원 가입 서비스 테스트 파일 

 

4. 회원 가입 프레젠테이션(웹) 계층 구현

- MemberController: 회원 가입 및 회원 정보 처리 관련 컨트롤러 

- member/register.jsp: 회원 가입 페이지 

---------------------------------------------------------------------------------------------

 

그런데 막상 테스트용으로 만든 아이디로 실제 로그인을 해보려고 하니 아래와 같은 에러가 발생하였다.

사실 회원 가입 기능을 추가하기 전에 스프링 시큐리티를 공부하며 이미 기존 프로젝트에 로그인/로그아웃 처리를 해놓았는데, 이로 인해 새로 회원 가입 기능을 추가하면서 새로 가입하게된 신규 회원들의 비밀번호가 시큐리티 조건에 맞게 Bcrypt 로 인코딩 되지 않아 발생하는 에러로 보였다.

 

그래서 회원 가입과 관련된 다른 기능을 추가하기 전에 BCrypt로 암호화 작업을 먼저 해주기로 하였다.  

 

회원 가입 처리 컨트롤러인 MemberController에 가서 비밀번호 암호화를 위해 BCryptPasswordEncoder를 추가해주고,

회원 가입 처리 메서드인 postRegister 안에 BcryptPasswordEncoder로 비밀번호 인코딩을 할 수 있게 코드를 추가하였다. 

그리고 서버를 실행해 웹브라우저에서 테스트용 회원 가입을 하고 로그를 확인해보니 비밀번호에 해당하는 userpw에 BCrypt로 제대로 암호화 처리되어 데이터베이스에 저장되는 것을 확인하였다. 

STS 로그 
오라클 DB

그리고나서 새로 가입한 테스트용 아이디로 로그인을 해보니 정상적으로 로그인이 되고 STS 콘솔창에도 아래와 같이 UserDetailsService에서 암호화된 비밀번호를 제대로 인식하여 로그인이 문제 없이 처리되는 것을 확인할 수 있었다. 

--------------------------------------------------------------------------------------------------------------------

 

그 다음 회원 정보 수정 기능을 추가하기로 했다.

일단 사용자 입장에서 생각해봤을 때 회원 정보 관련 페이지 접속 및 처리 순서는 다음과 같았다.

 

1. 로그인 시 내 정보를 확인할 수 있는 '회원정보' 메뉴가 메뉴탭에 나타난다.

2. '회원정보' 페이지에 들어가면 내 정보를 수정할 수 있다. 

3. 그리고 회원정보 수정 뿐만 아니라 탈퇴까지 할 수 있다. 

 

이를 바탕으로 회원 정보 수정 기능 구현을 위한 작업 순서를 아래와 같이 정리해보았다. 

 

1. 영속 계층 

- MemberMapper 인터페이스: 회원 정보 조회, 수정, 탈퇴 메서드를 선언

- MemberMapper.xml: 회원 정보 조회, 수정, 탈퇴 처리 SQL문 추가 

- MemberMappterTests : 영속 계층 단계에서 기능 작동 테스트 

 

2. 비즈니스(서비스) 계층

- MemberService 인터페이스: 실제 회원 정보 조회, 수정, 탈퇴 메서드 선언

- MemberServiceImpl 인터페이스: 실제 회원 정보 조회, 수정, 탈퇴 메서드 구현 

- MemberServiceTests: 비즈니스 계층에서 기능 작동 테스트 

 

3. 프레젠테이션(웹) 계층

- MemberController: 회원 정보 조회, 수정, 탈퇴 처리 기능을 컨트롤러에 추가

- member/profile.jsp: 회원 정보 조회 페이지 

- member/update.jsp: 회원 정보 수정 페이지

- member/delete.jsp: 회원 탈퇴 페이지 

 

그럼 START!

 

====================================START===============================================

1. 영속 계층 

1. MemberMapper 인터페이스에 회원 정보 조회, 수정, 탈퇴 메서드를 선언한다. 

(수정 및 탈퇴 처리가 성공했을 때 숫자 1을 반환하도록 하고자 반환 타입을 int로 했다.)  

 

2. MemberMapper.xml에 회원 정보 조회, 수정, 탈퇴를 위한 SQL문을 작성한다.

 

3. MemberMapperTests에서 회원 정보 조회, 수정 탈퇴 처리 기능이 영속 계층 단계에서 이상 없이 작동하는지 확인한다.  

 

2. 비즈니스(서비스) 계층 

1. MembserService 인터페이스에 회원 정보 조회, 수정, 탈퇴 메서드를 선언한다. 

2. MemberServiceImpl에 MemberService에서 추가한 회원 정보 조회, 수정, 탈퇴 메서드를 오버라이드하여 구현한다.

 

3. MemberServiceTests에서 회원 정보 조회, 수정, 탈퇴 처리 기능이 비즈니스 계층 단계에서 이상 없이 작동하는지 확인한다.  

 

3. 프레젠테이션(웹) 계층

1. 회원 정보 조회 

1. MemberController에 회원 정보 조회 기능을 추가한다.

 

스프링 시큐리티를 이용하여 회원 정보를 조회해야 한다. 이 부분에서 굉장히 애를 많이 먹었는데, 내가 참고한 블로그 글들은 아래와 같다.

 

https://sowon-dev.github.io/2020/10/21/201022spring/

https://itstory.tk/entry/Spring-Security-현재-로그인한-사용자-정보-가져오기

http://macaronics.net/index.php/m01/spring/view/1405

https://coding-nyan.tistory.com/127

https://pro-pennek.tistory.com/entry/Spring-컨트롤러에서-서비스로-Session값-넘기기

https://jason-moon.tistory.com/132

https://blog.nachal.com/1629

 

HttpSession에 setAttribute()로 값을 설정해서 가져오는 방법, HttpSession getAttribute()로 가져오는 방법 등이 있었으나 나는 스프링 시큐리티의 Principal & Authentication 객체를 사용하여 회원 정보 조회 기능을 구현했다. 

 

Principal & Authentication 객체를 사용하는 것은 컨트롤단에서 매개변수로 받아서 사용하는 방법이다.

이렇게 하면 현재 로그인한 사용자가 자신의 회원 정보를 볼 수 있다. 

 

2. 회원 정보 확인 페이지인 member/profile.jsp를 만든다. readonly 속성을 이용해 현재 로그인 회원의 회원 정보를 읽을 수만 있게 한다.

 

그리고 <form>의 action을 회원 정보 수정 페이지인 '/member/update'로 설정하여 회원 정보 수정 버튼 클릭 시 회원 정보 수정 페이지로 이동하게 한다. 

 

get방식이기 때문에 <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token }"> 태그를 넣지 않았고 쿼리스트링이 URL에 회원 정보가 쿼리스트링으로 길게 연결되지만, 컨트롤러에서 회원 정보 수정 페이지 접근에 제한을 둘 것이므로 넘어간다.

 

** <body> 태그 내 일부  

....

<form id="operForm" action="/member/update" method="get"> 
	                       	 	<div class="form-group">
	                            	<label>아이디</label>
	                                	<input class="form-control" name='userid' value='<c:out value="${profile.userid }"/>' readonly="readonly">
	                            </div>
	                            <div class="form-group">
	                            	<label>사용자 이름(별명)</label>
	                                	<input class="form-control" name='userName' value='<c:out value="${profile.userName }"/>' readonly="readonly">
	                            </div>
	                            <div class="form-group">
	                            	<label>사용자 가입일 </label>
	                                	<input class="form-control" name='regdate' value='<fmt:formatDate pattern="yyyy/MM/dd" value="${profile.regdate}"/>' readonly="readonly">
	                            </div>
	                            <div class="form-group">
	                            	<label>최근 사용자 정보 수정일 </label>
	                                	<input class="form-control" name='updateDate' value='<fmt:formatDate pattern="yyyy/MM/dd" value="${profile.updateDate}"/>' readonly="readonly">
	                            </div>
	                            <div class="form-group">
	                            	<label>이메일</label>
	                                	<input class="form-control" name='email' value='<c:out value="${profile.email }"/>' readonly="readonly">
	                            </div>
	                            <div class="form-group">
	                            	<label>사용자 권한</label>
	                                	<input class="form-control" name='authList' value='<c:out value="${profile.authList[0] }"/>' readonly="readonly">
	                            </div>
                       		</form>
                       		
                       		<sec:authentication property="principal" var="pinfo"/>
                       		<sec:authorize access="isAuthenticated()">
                            	<c:if test="${pinfo.username eq profile.userid }">
                                    <button id="submitBtn"type="submit" class="btn btn-primary">회원 정보 수정</button>
                                </c:if>
                            </sec:authorize>      		
....

** 버튼 동작 자바스크립트 부분 

<script type="text/javascript">

//회원 정보 수정 버튼 클릭 시 
$("#submitBtn").on("click", function(e){
		$("#operForm").attr("action", "/member/update").submit();
	});

</script>

 

 

2. 회원 정보 수정

1. MemberController에 회원 정보 수정 기능을 추가한다.

먼저 GET 방식으로 회원 정보 수정 페이지로 이동한 다음, 실제 수정은 POST 방식으로 진행된다. 

 

2. 회원 정보 수정 페이지인 member/update.jsp를 만든다. 

 

** <body> 태그 내 일부  

** 버튼 동작 자바스크립트 부분

<script type="text/javascript">
$(document).ready(function(){
	
	var operForm = $("form");
	
	$("button").on("click",function(e){
		e.preventDefault();
		
		var operation = $(this).data("oper");
		
		if(operation === "update" ) {
			
			// 비밀번호를 입력하지 않았을 때
			if($("input[name='userpw']").val()==""){
				alert("비밀번호를 입력해주세요.");
				$("input[name='userpw']").focus();
				return false;
			}
			// 비밀번호의 길이가 8자보다 짧을 경우 
			if($("input[name='userpw']").val().length < 8){
				alert("비밀번호는 8자 이상 입력해주세요.");
				$("input[name='userpw']").focus();
				return false;
			}
			
			// 사용자 이름을 입력하지 않았을 경우 
			if($("input[name='userName']").val()==""){
				alert("사용자 이름을 입력해주세요.");
				$("input[name='userName']").focus();
				return false;
			}
				
			// 사용자 이름이 10자 보다 길 경우 
			if($("input[name='userName']").val().length > 10){
				alert("사용자 이름은 10자 이내로 입력해주세요.");
				$("input[name='userName']").focus();
				return false;
			}
			
			// 이메일 주소를 입력하지 않았을 경우 
 			if($("input[name='email']").val()==""){
				alert("이메일 주소를 입력해주세요.");
				$("input[name='email']").focus();
				return false;
			}
			
			alert("회원 정보 수정이 완료되었습니다. 다시 로그인 해주세요.")
			operForm.submit();
		}
		
	});
	
});

</script>

 

 

3. 회원 탈퇴

1. MemberController에 회원 탈퇴 기능을 추가한다. 회원 정보 수정과 마찬가지로 먼저 GET 방식으로 회원 탈퇴 페이지로 이동한 다음, 실제 탈퇴는 POST 방식으로 진행된다. 

로그인한 사용자의 비밀번호와 탈퇴 페이지에서 입력한 비밀번호가 같아야만 회원 탈퇴가 될 수 있도록 BCryptPasswordEncoder의 matches() 메서드를 이용한다.

 

그리고 비밀번호 불일치 시 비밀번호를 다시 입력해달라는 메세지를 띄울 수 있도록 RedirectAttributes를 파라미터로 넣어 addFlashAttribute() 메서드를 이용한다.  

 

마지막으로 비밀번호가 일치하여 회원 탈퇴가 되면 addFlashAttribute()로 홈 화면으로 이동하면서 "회원 탈퇴 처리가 되었습니다." 라는 모달창을 띄우려고 계획했기 때문에 회원 탈퇴 시에도 addFlashAttribute() 메서드를 넣어놓는다. (모달창은 홈 화면에서 구현한다.)   

 

2. 회원 탈퇴 페이지인 member/delete.jsp를 만든다. 

3. 이렇게만 하고 웹 브라우저에서 실행해보면 회원 탈퇴 처리가 제대로 되지 않는다. 왜냐하면 xml 파일에서 쿼리문을 수정해주어야 하기 때문이다.

 

MemberMapper.xml 에 들어가서 회원 탈퇴 처리 쿼리문 where 조건에 있는 "and userpw = #{userpw}" 를 지워줘야한다. (혹은 주석처리) 회원 탈퇴 시 브라우저에서 사용자가 직접 비밀번호를 입력하고, 그 비밀번호를 컨트롤단에서 비교하여 회원 탈퇴 처리 여부를 결정하기 때문에 쿼리문의 조건절에는 userid = #{userid}만 있으면 된다.  

그럼 회원 탈퇴 처리 작업이 제대로 동작 하는 것을 볼 수 있다.