52일차(2)/Spring(11) : Interceptor 추가, 비밀번호 암호화 및 수정, 회원 삭제 기능 구현
UsersController (전체)
package com.sy.spring04.users.controller;
import java.net.URLEncoder;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import com.sy.spring04.users.dto.UsersDto;
import com.sy.spring04.users.service.UsersService;
@Controller
public class UsersController {
@Autowired
private UsersService service;
/*
* GET 방식 /users/signup_form 요청을 처리할 메소드
* - 요청방식이 다르면 실행되지 않는다.
*/
@RequestMapping(method = RequestMethod.GET, value = "/users/signup_form")
public String signupform() {
return "users/signup_form";
}
//회원가입 요청 처리
@RequestMapping(method = RequestMethod.POST, value = "/users/signup")
public ModelAndView signup(ModelAndView mView, UsersDto dto) {
service.addUser(dto);
mView.setViewName("users/signup");
return mView;
}
//로그인 폼 요청 처리
@RequestMapping(method = RequestMethod.GET, value="/users/loginform")
public String loginForm() {
return "users/loginform";
}
//로그인 요청 처리
@RequestMapping("/users/login")
public ModelAndView login(ModelAndView mView, UsersDto dto, String url, HttpSession session) {
/*
* 서비스에서 비즈니스 로직을 처리할 때 필요로 하는 객체를 컨트롤러에서 직접 전달해주어야 한다.
* 주로, HttpServletRequest, HttpServletResponse, HttpSession, ModelAndView
* 등등의 객체이다.
*/
service.loginProcess(dto, session);
//로그인 후에 가야할 목적지 정보를
String encodedUrl=URLEncoder.encode(url);
mView.addObject("url", url);
mView.addObject("encodedUrl", encodedUrl);
mView.setViewName("users/login");
return mView;
}
//로그아웃 요청 처리
@RequestMapping("/users/logout")
public String logout(HttpSession session) {
//세션에서 id라는 키값으로 저장된 값 삭제
session.removeAttribute("id");
return "users/logout";
}
//개인정보 보기 요청 처리
@RequestMapping("/users/info")
public ModelAndView info(HttpSession session, ModelAndView mView) {
service.getInfo(session, mView);
mView.setViewName("users/info");
return mView;
}
//비밀번호 수정 폼 요청 처리
@RequestMapping("/users/pwd_updateform")
public String pwdUpdateForm() {
return "users/pwd_updateform";
}
@RequestMapping("users/pwd_update")
public ModelAndView pwdUpdate(UsersDto dto, ModelAndView mView, HttpSession session) {
//서비스에 필요한 객체의 참조값을 전달해서 비밀번호 수정 로직을 처리한다.
service.updateUserPwd(session, dto, mView);
//view page로 forward 이동해서 작업결과를 응답한다.
mView.setViewName("users/pwd_update");
return mView;
}
//회원 탈퇴 요청 처리
@RequestMapping("/users/delete")
public ModelAndView delete(HttpSession session, ModelAndView mView) {
service.deleteUser(session, mView);
mView.setViewName("users/delete");
return mView;
}
UsersServiceImpl (전체)
package com.sy.spring04.users.service;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import com.sy.spring04.users.dao.UsersDao;
import com.sy.spring04.users.dto.UsersDto;
@Service
public class UsersServiceImpl implements UsersService{
@Autowired
private UsersDao dao;
@Override
public Map<String, Object> isExistId(String inputId) {
// TODO Auto-generated method stub
return null;
}
//회원 한명의 정보를 추가하는 메소드
@Override
public void addUser(UsersDto dto) {
//가입시 입력한 비밀번호를 읽어와서
String pwd=dto.getPwd();
//암호화한 후에
BCryptPasswordEncoder encoder=new BCryptPasswordEncoder();
String encodedPwd=encoder.encode(pwd);
//dto에 다시 넣어준다.
dto.setPwd(encodedPwd);
//암호화된 비밀번호가 들어있는 dto를 dao에 전달해서 새로운 회원정보를 추가한다.
dao.insert(dto);
}
//로그인 처리를 하는 메소드
@Override
public void loginProcess(UsersDto dto, HttpSession session) {
//입력한 정보가 맞는지 여부
boolean isValid=false;
//아이디를 이용해서 회원정보를 얻어온다.
UsersDto resultDto=dao.getData(dto.getId());
//만일 select된 회원정보가 존재하고 (아이디가 없을경우 null이 리턴된다.)
if(resultDto!=null) {
//Bcrypt 클래스의 static 메소드를 이용해서 입력한 비밀번호와 암호화해서 저장된 비밀번호의 일치 여부를 알아내야 한다.
isValid=BCrypt.checkpw(dto.getPwd(), resultDto.getPwd());
}
//만일 유효한 정보이면
if(isValid) {
//로그인 처리를 한다.
session.setAttribute("id", resultDto.getId());
}
}
//정보를 가져오는 메소드
@Override
public void getInfo(HttpSession session, ModelAndView mView) {
//로그인된 아이디를 읽어온다.
String id=(String)session.getAttribute("id");
//DB에서 회원정보를 얻어와서
UsersDto dto=dao.getData(id);
//ModelAndView 객체에 담아준다.
mView.addObject("dto", dto);
}
@Override
public void updateUserPwd(HttpSession session, UsersDto dto, ModelAndView mView) {
//세션 영역에서 로그인된 아이디 읽어오기
String id=(String)session.getAttribute("id");
//DB 에 저장된 회원정보 얻어오기
UsersDto resultDto=dao.getData(id);
//DB 에 저장된 암호화된 비밀 번호
String encodedPwd=resultDto.getPwd();
//클라이언트가 입력한 비밀번호
String inputPwd=dto.getPwd();
//두 비밀번호가 일치하는지 확인
boolean isValid=BCrypt.checkpw(inputPwd, encodedPwd);
//만일 일치 한다면
if(isValid) {
//새로운 비밀번호를 암호화 한다.
BCryptPasswordEncoder encoder=new BCryptPasswordEncoder();
String encodedNewPwd=encoder.encode(dto.getNewPwd());
//암호화된 비밀번호를 dto 에 다시 넣어준다.
dto.setNewPwd(encodedNewPwd);
//dto 에 로그인된 아이디도 넣어준다.
dto.setId(id);
//dao 를 이용해서 DB 에 수정 반영한다.
dao.updatePwd(dto);
//로그아웃 처리
session.removeAttribute("id");
}
//작업의 성공여부를 ModelAndView 객체에 담아 놓는다(결국 HttpServletRequest 에 담긴다)
mView.addObject("isSuccess", isValid);
//로그인된 아이디도 담아준다.
mView.addObject("id", id);
}
@Override
public Map<String, Object> saveProfileImage(HttpServletRequest request, MultipartFile mFile) {
// TODO Auto-generated method stub
return null;
}
@Override
public void updateUser(UsersDto dto, HttpSession session) {
// TODO Auto-generated method stub
}
@Override
public void deleteUser(HttpSession session, ModelAndView mView) {
//로그인된 아이디를 얻어와서
String id=(String)session.getAttribute("id");
//해당 정보를 DB에서 삭제하고
dao.delete(id);
//로그아웃 처리도 한다.
session.removeAttribute("id");
//ModelAndView 객체에 탈퇴한 회원의 아이디를 담아준다.
mView.addObject("id", id);
}
}
- info(개인정보 보기) 페이지는 로그인한 사람만 들어갈 수 있도록 하기.
- Loginfilter대신 인터셉터 사용!
- com.sy.spring04.interceptor패키지 생성
LoginInterceptor (이전에 03에서 만든 LoginIntercepter 가져오기)
package com.sy.spring04.interceptor;
import java.net.URLEncoder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
//로그인된 사용자인지 검사할 인터셉터
public class LoginInterceptor implements HandlerInterceptor {
//Controller 메소드 수행 직전에 로그인된 사용자인지 검증을 해서
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//세션 객체의 참조값을 얻어와서
HttpSession session=request.getSession();
String id=(String)session.getAttribute("id");
//만일 로그인을 하지 않았다면
if(id==null) {
//로그인 페이지로 리다일렉트 이동시키고 false를 리턴한다.
//원래 가려던 url 정보 읽어오기
String url=request.getRequestURI();
//GET 방식 전송 파라미터를 query 문자열로 읽어오기 ( a=xxx&b=xxx&c=xxx )
String query=request.getQueryString();
//특수 문자는 인코딩을 해야한다.
String encodedUrl=null;
if(query==null) {//전송 파라미터가 없다면
encodedUrl=URLEncoder.encode(url);
}else {
// 원래 목적지가 /test/xxx.jsp 라고 가정하면 아래와 같은 형식의 문자열을 만든다.
// "/test/xxx.jsp?a=xxx&b=xxx ..."
encodedUrl=URLEncoder.encode(url+"?"+query);
}
//3. 로그인을 하지 않았다면 /users/loginform 페이지로 리다일렉트 이동 시킨다. (HttpServletResponse)
String cPath=request.getContextPath();
response.sendRedirect(cPath+"/users/loginform?url="+encodedUrl);
return false;
}
//로그인을 했다면 흐름을 이어간다.
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// TODO Auto-generated method stub
}
}
- 서버가 클라이언트를 구분하는 방법은? 클라이어트별로 고유한 session영역이 있다.
- 로그인 처리란 그 고유한 session영역에 클라이언트를 식별할 수 있는 값을 담아놓는 것이다.
- 인터셉터는 정의한다고 해서 동작하는 것은 아니다.
- bean으로 만들려면?
→ @component 어노테이션을 붙여도 되고, servlet-context.xml 에 작성해줄 수도 있다.
- 이렇게 작성해주기. bean의 class는 ctrl+space로 자동완성시킬 수 있다.
- 이렇게 4개 페이지만 exclude(제외) 시키고, 나머지 모든 요청은 interceptor가 동작하도록 한다.
- 이제 로그아웃 상태에서 info 요청을 하면 자동으로 로그인 폼으로 이동한다.
- interceptor가 정상적으로 작동하는 것!
- 주소창을 보면 url이라는 파라미터명으로 원래 가야할 목적지 정보를 담고 있는 것을 볼 수 있다.(인코딩을 한 상태)
- 현재 quantum DB에서 테이블을 확인해 보면 이런데,
원래는 관리자도 DB에서 사용자의 비밀번호를 볼 수 있으면 안된다.
→ 비밀번호를 암호화해서 저장해야 한다!
- pom.xml에 필요한 라이브러리는 이미 추가되어 있다.(원래는 추가해주어야 한다)
- 회원가입시에 비밀번호를 저장할 때 아예 암호화를 해야한다.
- DELETE FROM users 로 일단 현재 가입된 가입자 정보를 모두 삭제하기!
- service로 가기
//회원 한명의 정보를 추가하는 메소드
@Override
public void addUser(UsersDto dto) {
//가입시 입력한 비밀번호를 읽어와서
String pwd=dto.getPwd();
//암호화한 후에
BCryptPasswordEncoder encoder=new BCryptPasswordEncoder();
String encodedPwd=encoder.encode(pwd);
//dto에 다시 넣어준다.
dto.setPwd(encodedPwd);
//암호화된 비밀번호가 들어있는 dto를 dao에 전달해서 새로운 회원정보를 추가한다.
dao.insert(dto);
}
- 회원 한명의 정보를 추가하는 메소드(addUser)에서
그냥 DB에 추가하는 것이 아니고 암호화 과정을 거친다!
- 비밀번호 암호화에는 BCrypt password Encoder 객체를 사용한다.
- 메소드가 CharSequence 타입을 받는다. CharSequence 타입은 String이라고 생각하면 된다.
- 즉 문자열을 넣어주면 암호화된 문자열을 리턴해주는 메소드이다.
- 이 리턴된 값을 DB에 저장해주면 된다.
- String을 Serializable, CharSequence, Comparable 타입으로도 받을 수 있다.
- 모두 String이 구현한 인터페이스인 것!
- 이렇게 메소드를 저장해주면 다른 값으로 저장된다.
- 그러면 로그인 메소드도 수정해줘야 한다.
- 현재 상태로는 입력한 값이 DB의 변환된(암호화된) 글자와 같은지만을 비교하므로 로그인이 되지 않는다.
loginProcess (수정)
//로그인 처리를 하는 메소드
@Override
public void loginProcess(UsersDto dto, HttpSession session) {
//입력한 정보가 맞는지 여부
boolean isValid=false;
//아이디를 이용해서 회원정보를 얻어온다.
UsersDto resultDto=dao.getData(dto.getId());
//만일 select된 회원정보가 존재하고 (아이디가 없을경우 null이 리턴된다.)
if(resultDto!=null) {
//Bcrypt 클래스의 static 메소드를 이용해서 입력한 비밀번호와 암호화해서 저장된 비밀번호의 일치 여부를 알아내야 한다.
isValid=BCrypt.checkpw(dto.getPwd(), resultDto.getPwd());
}
//만일 유효한 정보이면
if(isValid) {
//로그인 처리를 한다.
session.setAttribute("id", resultDto.getId());
}
}
- 원본 pwd 문자열이 무엇인지는 알 수 없지만 입력한 값과의 일치 여부는 알아낼 수 있다.
- BCrypt의 checkpw() 메소드를 사용한다!
- 일반 텍스트, hashed된 텍스트를 넣으면 boolean 값을 리턴해준다.
- 이렇게 수정할 수 있다.
- BCrypt 객체를 사용하려면 security 패키지가 사용 가능해야 한다.(그래야 import 가능)
- 이 패키지를 사용가능하게 하기 위해 위의 pom.xml에 명시해놓은 것!
비밀번호 수정 updatepwd
- 컨트롤러 추가
//비밀번호 수정 폼 요청 처리
@RequestMapping("/users/pwd_updateform")
public String pwdUpdateForm() {
return "users/pwd_updateform";
}
pwd_updateform.jsp 추가
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/users/pwd_updateform.jsp</title>
</head>
<body>
<div class="container">
<h1>비밀 번호 수정 폼</h1>
<form action="${pageContext.request.contextPath}/users/pwd_update" method="post" id="myForm">
<div>
<label for="pwd">기존 비밀 번호</label>
<input type="password" name="pwd" id="pwd"/>
</div>
<div>
<label for="newPwd">새 비밀번호</label>
<input type="password" name="newPwd" id="newPwd"/>
</div>
<div>
<label for="newPwd2">새 비밀번호 확인</label>
<input type="password" id="newPwd2"/>
</div>
<button type="submit">수정하기</button>
<button type="reset">리셋</button>
</form>
</div>
<script>
//폼에 submit 이벤트가 일어났을때 실행할 함수를 등록하고
document.querySelector("#myForm").addEventListener("submit", function(e){
let pwd1=document.querySelector("#newPwd").value;
let pwd2=document.querySelector("#newPwd2").value;
//새 비밀번호와 비밀번호 확인이 일치하지 않으면 폼 전송을 막는다.
if(pwd1 != pwd2){
alert("비밀번호를 확인 하세요!");
e.preventDefault();//폼 전송 막기
}
});
</script>
</body>
</html>
- 비밀번호 두개를 똑같이 작성하지 않으면 javascrpit로 전송을 막는다.
- pwd_update 컨트롤러 추가
@RequestMapping("users/pwd_update")
public ModelAndView pwdUpdate(UsersDto dto, ModelAndView mView, HttpSession session) {
//서비스에 필요한 객체의 참조값을 전달해서 비밀번호 수정 로직을 처리한다.
service.updateUserPwd(session, dto, mView);
//view page로 forward 이동해서 작업결과를 응답한다.
mView.setViewName("users/pwd_update");
return mView;
}
- 로그인된 아이디도 필요하므로 인자로 session도 받아야 한다.
- 안에서 메소드를 호출하면 이렇게 전달된다.
- 서비스 updateUserPwd 메소드
@Override
public void updateUserPwd(HttpSession session, UsersDto dto, ModelAndView mView) {
//세션 영역에서 로그인된 아이디 읽어오기
String id=(String)session.getAttribute("id");
//DB 에 저장된 회원정보 얻어오기
UsersDto resultDto=dao.getData(id);
//DB 에 저장된 암호화된 비밀 번호
String encodedPwd=resultDto.getPwd();
//클라이언트가 입력한 비밀번호
String inputPwd=dto.getPwd();
//두 비밀번호가 일치하는지 확인
boolean isValid=BCrypt.checkpw(inputPwd, encodedPwd);
//만일 일치 한다면
if(isValid) {
//새로운 비밀번호를 암호화 한다.
BCryptPasswordEncoder encoder=new BCryptPasswordEncoder();
String encodedNewPwd=encoder.encode(dto.getNewPwd());
//암호화된 비밀번호를 dto 에 다시 넣어준다.
dto.setNewPwd(encodedNewPwd);
//dto 에 로그인된 아이디도 넣어준다.
dto.setId(id);
//dao 를 이용해서 DB 에 수정 반영한다.
dao.updatePwd(dto);
//로그아웃 처리
session.removeAttribute("id");
}
//작업의 성공여부를 ModelAndView 객체에 담아 놓는다(결국 HttpServletRequest 에 담긴다)
mView.addObject("isSuccess", isValid);
//로그인된 아이디도 담아준다.
mView.addObject("id", id);
}
- 해야할 것이 많다.
1) 암호화된 비밀번호를 가져와서 입력한 값과 같은지 확인(기존 비밀번호가 맞지 않으면 처리해주면 안된다.)
2) 새 비밀번호 암호화해서 dto에 넣어서 DB에 수정 반영 + 로그아웃 처리
3) 응답
1) 기존 비밀번호 입력값이 DB에 저장된 비번과 일치하는지
2) 새 비밀번호를 암호화해서 수정반영
3) 응답을 위해 성공여부와 현재 로그인된 id를 담아준다.
- ModelAndview에 담으면 session이 아니라 request 영역에 담긴다.
pwd_update.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/users/pwd_update.jsp</title>
</head>
<body>
<div class="container">
<c:choose>
<c:when test="${isSuccess }">
<p>
<strong>${id }</strong> 님 비밀번호를 수정하고 로그아웃되었습니다.
<a href="${pageContext.request.contextPath}/users/loginform">로그인하러 가기</a>
</p>
</c:when>
<c:otherwise>
<p>
이전 비밀번호가 일치하지 않습니다.
<a href="${pageContext.request.contextPath}/users/pwd_updateform">다시 시도</a>
</p>
</c:otherwise>
</c:choose>
</div>
</body>
</html>
<c:when test="${isSuccess }">
- 위 코드를 사용해서 성공여부를 읽어와서 성공/실패 여부에 따라 다른 응답을 해준다.
- 새 비밀번호/새 비밀번호 확인 이 일치하지 않을 경우!
- 비밀번호 변경 완료!
- delete 요청 처리
- controller
//회원 탈퇴 요청 처리
@RequestMapping("/users/delete")
public ModelAndView delete(HttpSession session, ModelAndView mView) {
service.deleteUser(session, mView);
mView.setViewName("users/delete");
return mView;
}
서비스 만들기(deleteUser)
@Override
public void deleteUser(HttpSession session, ModelAndView mView) {
//로그인된 아이디를 얻어와서
String id=(String)session.getAttribute("id");
//해당 정보를 DB에서 삭제하고
dao.delete(id);
//로그아웃 처리도 한다.
session.removeAttribute("id");
//ModelAndView 객체에 탈퇴한 회원의 아이디를 담아준다.
mView.addObject("id", id);
}
- 이미 지웠기 때문에 ModelAndView 객체에 탈퇴한 아이디를 다시 담아주는 작업이 필요한 것
delete.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/users/delete.jsp</title>
</head>
<body>
<div class="container">
<h1>알림</h1>
<p>
<strong>${requestScope.id}</strong> 님 탈퇴 처리 되었습니다.
<a href="${pageContext.request.contextPath}/">인덱스로 가기</a>
</p>
</div>
</body>
</html>
- 위에서 ModelAndView 객체에 담은 것을 여기서 활용!
- 회원 삭제에 대한 응답!
참고) ChatGPT (chat.openai.com/chat)
- 개발 관련된 질문으로 인공지능이 채팅을해주는 open ai 이다.(개발관련)
- 알아서 검색해서 추천 코드도 주고 대화도 해준다.
- 인간인지 ai인지 구분하기 어렵다고 한다.
- 내 코드를 넣어주면 리뷰도 해주고, 질문하면 답변을 주기도 한다.
- 이런식으로 질문하면
- 이렇게 답변해준다.
- 이런식으로 코드 샘플을 주기도 한다.
- AI가 이 대화를 generate 하는것!
- 위의 updateUserPwd 를 넣어보니 이렇게 코드 리뷰를 해주었다... 공부하는 데에 참고!
'국비교육(22-23)' 카테고리의 다른 글
54일차(1)/Spring(13) : 자료실 게시판 만들기, 파일 업로드 기능 구현 (1) | 2022.12.22 |
---|---|
53일차(1)/Spring(12) : ajax 요청 JSON으로 응답하기, 파일 업로드 처리 (0) | 2022.12.21 |
52일차(1)/Spring(10) : 회원가입, 로그인, 로그아웃, 회원정보 보기 기능 구현 (1) | 2022.12.20 |
51일차(2)/Spring(9) : 파일 업로드 기능 구현 / SmartEditor 적용 (0) | 2022.12.19 |
51일차(1)/Spring(8) : Interceptor (0) | 2022.12.19 |