소통 하고싶은 개발자

프로젝트 진행 ④ : 비밀번호 재설정 기능 구현(JSP) 본문

토이 프로젝트/익명 채팅 사이트

프로젝트 진행 ④ : 비밀번호 재설정 기능 구현(JSP)

OhPro 2022. 7. 5. 19:45
반응형

목차

  • 시작하며
  • 페이지 구성
  • 시나리오
  • 비밀번호 재설정 구현 방법
  • 프로퍼티 읽기 클래스 추가
  • 마치며

 


 

시작하며

사실 이번 포스팅은 이전처럼 기능 구현에 대한 생각보단 'url 패턴, 이대로 괜찮은가' 에 대한 생각이 더 많았던 것같다.

 

페이지마다 url가 다르므로 컨트롤러가 따로 있어야 되는건 어떻게든 그럴수도 있지 하고 넘기려 했지만 한 페이지에 여러 기능이 존재한다면 페이지마다 컨트롤러가 1개 이상이 되고, 내가 알기로 현재 예상하고있는 페이지 수가 약 20 페이지인데 이러다가 컨트롤러 클래스만 4~50개가 되는건 아닌지 걱정이 됐다.

 

사실 이전부터 uri에 대한 생각을 해오면서 불안함이 있긴 했었다. 그런데 페이지를 만들고 기능을 추가할 때마다 느껴지는 위화감을 씻을 수 없었다. 컨트롤러가 너무 많아졌기 때문이다. 

 

이런 위화감때문에 이전부터 구글링을 적잖게 했지만, Rest API에 대한 내용말고는 그닥 컨트롤러의 수를 줄이기에는 올바르지 않은 내용들이었다. 물론 url 매핑을 Restful하게 하면 좋겠지만 그러기 위해선 ajax라던지 다른 기술을 써야한다. 구글링해보니 그닥 어렵진 않아보여서 해볼 수도 있지만, 프로젝트를 설계하면서 일정을 만들어놓았기 때문에 최대한 일정에 맞추기 위해서 해당 기술은 프로젝트 완료 후에 기능 추가 건으로 해보겠다.

 


 

페이지 구성

일단 비밀번호 재설정 관련하여 구상했었던 와이어 프레임은 아래와 같다.

 

와이어 프레임

 

그리고 아래는 실제 내가 작성한 JSP가 서버에서 실행되었을 때의 모습이다.

 

비밀번호 찾기 페이지

 

비밀번호 재설정 페이지

 

비밀번호 재설정 완료 페이지

 


 

시나리오

비밀번호 재설정 완료까지의 시나리오는 다음과 같다.

 

  1. 비밀번호 찾기 페이지에서 아이디, 이메일 정보를 입력하고 인증번호 받기 버튼을 누른다.
  2. 인증번호가 담긴 이메일에서 인증번호를 확인한다.
  3. 비밀번호 찾기 페이지에 인증번호를 입력 후 인증하고 비밀번호 재설정하기 버튼을 클릭한다.
  4. 인증번호가 올바를 시 비밀번호 재설정 페이지로 이동한다.
  5. 비밀번호 재설정 페이지에서 비밀번호비밀번호 확인 입력 상자에 값을 입력하고 재설정하기 버튼을 클릭한다. 
  6. 비밀번호와 비밀번호 확인이 같은 값일 시 비밀번호가 수정되고 비밀번호 완료 페이지로 이동한다.

 

여기까지가 유저 입장에서의 시나리오이고, 시스템 관점에서의 시나리오는 가독성을 위해 그림으로 대체하겠다.

 

인증번호 받기까지 과정

 

인증하기까지 과정

 

비밀번호 재설정 완료까지 과정

 

시나리오도 시나리오지만 리다이렉팅을 하면 url요청이 또 들어오니까 간단한 뷰라도 컨트롤러를 만들 수밖에 없었다.

 


 

비밀번호 재설정 구현 방법

프로젝트 소스 구조

 

이전에 말했던 것처럼 간단한 페이지에도 필수적으로 하나 이상의 컨트롤러가 존재해야 하는 경우가 많다보니 컨트롤러 패키지를 login -> id/password로 나누는게 더 보기 편했다.

 

제일 먼저, 유저가 아이디와 비밀번호 입력 후 인증번호 받기 버튼을 클릭 시 아래의 PwCertificationController가 일을 위임받는다.

 

public class PwCertificationController extends ControllerTemplete {
    @Override
    public Object doPost() {
        String id = (String) req.getParameter("id");
        String email = (String) req.getParameter("email");
        String domain = (String) req.getParameter("domain");
        String emailAddress = email + "@" + domain;

        FindAccountService findAccountService = new FindAccountService();
        PwCertificationInfo certificationInfo = findAccountService.getPwCertification(id, emailAddress);
        if (certificationInfo== null) {
            req.setAttribute("noticeMessage", "입력 정보가 잘못되었습니다 !!");
            return "findPw";
        }

        req.setAttribute("noticeMessage", "이메일을 확인해주세요 !!");

        HttpSession httpSession = req.getSession();
        httpSession.setAttribute("pwCertificationInfo", certificationInfo);
        return "findPw";
    }
}

 

컨트롤러에서 사용하는 서비스 객체인 FindAccountService는 아이디찾기, 비밀번호 찾기 시 사용 가능하도록 만들어 두었다. 둘 다 이메일 인증을 해야하고 ACCOUNT 테이블에 접근하기 때문이다. 이 과정에서는 FindAccountService getPwCertification() 메서드를 사용하여 인증메일을 보내고 인증관련 객체를 전달받고, 전달받은 객체를 세션에 저장한다.

 

    public PwCertificationInfo getPwCertification(String id, String email){
        int accountNo = getAccountNo(id, email);
        if (!isCorrectAccountNo(accountNo))
            return null;

        String certificationValue = getRandomValue();
        sendMail(email, certificationValue);

        return new PwCertificationInfo(accountNo, certificationValue);
    }

 

  • getRandomValue() : 6자리 난수로 이루어진 인증 번호를 생성하는 메서드
  • sendMail() : 메일을 보내는 과정은 아이디찾기, 비밀번호찾기 둘 다 동일하므로 하나의 메서드로 만들었다. (저번 포스팅 참고)

 

메일 송신이 완료되어 유저가 메일함을 확인하고 인증번호 입력 후 인증하고 비밀번호 재설정하기 버튼을 클릭하면 FindPwController가 일을 위임받는다.

 

public class FindPwController extends ControllerTemplete {

    @Override
    public Object doGet() {
        if (isUserExist())
            return "main";

        HttpSession session = req.getSession();
        session.setAttribute("pwCertificationInfo", null);

        return "findPw";
    }

    @Override
    public Object doPost() {

        HttpSession session = req.getSession();
        PwCertificationInfo certificationInfo =
                (PwCertificationInfo) session.getAttribute("pwCertificationInfo");
        String inputValue = (String) req.getParameter("pwCertificationInput");

        ViewInfo viewInfo = new ViewInfo("findPw");

        if (certificationInfo == null) {
            req.setAttribute("noticeMessage", "인증을 재시도해주세요 !!");
            return viewInfo;
        }

        if (!certificationInfo.getCertificationValue().equals(inputValue)) {
            req.setAttribute("noticeMessage", "인증번호를 확인 후 재시도해주세요 !!");
            return viewInfo;
        }

        viewInfo.setRedirectRequired();
        viewInfo.setViewName("pwReset");

        return viewInfo;
    }
}

 

  • doGet() : get 메서드로 요청이 왔을 때는 페이지 자체를 요청한 것처럼 작동하게 해놓아서 만약 유저가 뒤로가기로 페이지에서 이탈했다가 돌아오면 인증 정보를 초기화하게 해놓았다. (임시)
  • doPost() : post 메서드는 유저가 인증번호를 입력하고 인증하고 비밀번호 재설정하기 버튼을 클릭했을 때 실행되고, 입력받은 인증번호가 유효하면 비밀번호 재설정 페이지를 반환시킨다. 

 

비밀번호 재설정 페이지로 이동할 때 리다이렉트가 일어나게 해놓았으므로 비밀번호 재설정 페이지에서 사용할 컨트롤러로 ResetPwController를 추가했다. 비밀번호 재설정 페이지에서 유저가 비밀번호와 비밀번호 확인을 입력하고 재설정하기 버튼을 클릭하면 ResetPwController가 일을 위임받는다.

 

리다이렉트로 설정한 이유는 인증을 마치고 비밀번호 재설정 페이지로 넘어왔는데 브라우저에는 이전 url이 적혀져 있지 않게 하기 때문이다. 더 정확한 이유는 저번 포스팅에서 설명했었으니 참고하면 된다.

 

public class ResetPwController extends ControllerTemplete {

    @Override
    public Object doGet() {
        return "pwReset";
    }

    @Override
    public Object doPost() {
        HttpSession session = req.getSession();
        PwCertificationInfo certificationInfo =
                (PwCertificationInfo) session.getAttribute("pwCertificationInfo");
        int accountNo = certificationInfo.getAccountNo();

        String inputPw = req.getParameter("password");
        String inputPwCheck = req.getParameter("passwordCheck");

        ViewInfo viewInfo = new ViewInfo("pwReset");

        if (!inputPw.equals(inputPwCheck)) {
            req.setAttribute("isCorrect", "'비밀번호'와 '비밀번호 확인'이 동일하지 않습니다 !!");
            return viewInfo;
        }

        FindAccountService findAccountService = new FindAccountService();
        boolean isFinished = findAccountService.resetPassword(accountNo, inputPw);

        if (isFinished){
            viewInfo.setViewName("pwResetComplete");
            viewInfo.setRedirectRequired();
            return viewInfo;
        }

        return viewInfo;
    }
}

 

ResetPwController 컨트롤러는 FindAccountService 객체의 resetPassword() 메서드를 사용하여 비밀번호를 재설정한다.

 

public boolean resetPassword(int accountNo, String password){
    AccountDao accountDao = new AccountDao();
    return accountDao.setAccountPw(accountNo, password);
}

 

그리고 비밀번호 재설정이 완료되어도 리다이렉팅하도록 해놓았기 때문에 ResetPwCompleteController 컨트롤러도 추가했다..

 


 

프로퍼티 읽기 클래스 추가

메일 API를 사용해서 인증 기능을 추가하고 깃허브에 Push하기 위해 터미널에 아래와 같이 입력하였다.

 

git push main main

 

푸쉬가 잘 되는 것까지 확인했는데 갑자기 메일이 하나 날라왔다.

 

깃 가디언

 

깃 가디언이라는 발신자가 SMTP 관련하여 뭔가 문제가 있다고 메일을 보낸 것이다. 링크가 딸려있어서 찾아가보니 메일 API를 사용하려면 코드상 구글 계정의 아이디와 비밀번호를 알고 해당 값을 사용하여 소켓으로 연결해야한다. 그런데 아이디와 비밀번호를 코드에 박아놓고 저장하여 푸쉬를 하니 깃허브에서 이를 눈치채고 위험하다는 메일을 보낸 것이었다.

 

솔직히 개쩐다고 생각했다. 그런 것까지 확인하는 깃허브의 기술력.. 어떤 코드를 사용했길래 계정 정보까지 알아내는 것일까 궁금했다. 아무튼 이대로는 프로젝트 관리자용 계정을 아무나 사용하여 메일을 보내버릴 수도 있기 때문에 프로퍼티로 만들어서 동적으로 가져오기로 했다.

 

ResourceUtil resourceUtil = new ResourceUtil("/mail_properties.properties");
String password = (String) resourceUtil.getProperty("gmail.password");

 

메일 유틸리티 클래스에서 ResourecUtil 클래스를 사용하여 "gmail.password"라는 이름의 패스워드를 불러오는 구문이다. 이 ResourecUtil 클래스를 생성할 때 혹은 생성 후 setPath() 메서드를 사용해서 "/" + 파일 이름을 입력해주면 properties들이 저장된 경로에서 프로퍼티를 읽어올 수 있게 만들었다.

 

public class ResourceUtil {
    private Properties properties;
    private String path;

    public ResourceUtil(String path) {
        properties = new Properties();
        setPath(path);
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
        Class clazz = getClass();
        URL url = clazz.getResource(path);
        try {
            FileReader fileReader = new FileReader(url.getPath());
            properties.load(fileReader);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Object getProperty(String key) {
        return properties.get(key);
    }

    public boolean putProperty(String key, Object value) {
        if (properties.containsKey(key))
            return false;
        properties.put(key, value);
        return true;
    }
}

 

만약 프로젝트 내부 경로가 바뀌거나 .properties 파일들을 저장하는 경로가 달라지면 수정해야 될 수도 있지만 그래도 ResourecUtil 클래스 내부만 수정해주면 불러올 수 있도록 만들어두었다. (나름 편함..ㅎㅎ)

 


 

마치며

하다보니까 컨트롤러가 너무 많아지는 느낌을 받았다. 뭔가 경험적으로 찾는 해답같지만 스프링처럼 빈을 등록시키는 것까지는 아니라도 여러 컨트롤러 중 하나를 기준으로 해당 컨트롤러가 속했는 패키지에 모든 클래스들을 DFS로 탐색하여 내부의 메서드들을 확인하고 메서드가 갖고있는 어노테이션으로 url을 매핑해주면 괜찮을 것같다라는 생각을 했는데, 이것만 해보고 안되면 컨트롤러.. 많이 만들어야겠지..

 

감사합니다!!

반응형