cs/java-spring-boot

[Zero-base] 6-14. 비밀번호 초기화 요청 및 메일 링크를 통한 초기화

Lomo 2022. 2. 18. 15:04
반응형

중복 코드 간소화 - 네비게이션 바

 

layout.html

  • 공통부분에 해당하는 네비게이션 바의 html로 페이지를 만듦
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title> fastlms </title>
</head>
<body>
    <h1>메인 페이지</h1>

    <div th:fragment="fragment-body-menu">
        <div>
            <a href="/member/register">회원가입</a>
            <a href="/member/info">회원정보</a>
            <a href="/member/login">로그인</a>
            <a href="/member/logout">로그아웃</a>
        </div>
        <hr/>
    </div>

</body>
</html>

 

 

 

  • 각 페이지 마다 fragment를 사용 할 수 있는 코드 추가
<div th:replace="/fragments/layout.html :: fragment-body-menu"></div>

 

 

 

  • localhost:8080

 

 

 

  • localhost:8080/member/register

 

 

 

  • localhost:8080/member/info

 

 

 


로그인 페이지에서 비밀번호 찾기 구현

 

login.html

  • 비밀번호 찾기 링크 추가

 

 

 

localhost:8080/member/login

  • 링크가 추가된 모습

 

 

 

MemberController.java

  • 페이지 맵핑

 

 

 

find_password.html

비밀번호 찾기 페이지 작성

  • 이메일과 이름을 입력받고
  • 비밀번호 초기화 요청 버튼 생성

 

 

 

SecurityConfiguration.java

  • 로그인 없이 접근 가능한 페이지 추가

 

 


로그인 페이지에서 비밀번호 찾기 페이지 접근 테스트

 

  • localhost:8080/member/login
  • 비밀번호 찾기 링크 클릭

 

 

 

  • localhost:8080/member/find/password
  • 페이지 접근 가능

 

 


비밀번호 찾기 기능 구현

 

MemberController.java

  • 비밀번호 찾기를 눌렀을 때의 맵핑

 

 

 

ResetPasswordInput.java

  • 입력받은 파라미터를 새로 지정

 

 

 

  • ResetPasswordInput 파라미터를 받고
  • sendResetPassword의 파라미터로 사용
  • 결과를 불린값으로 저장
  • 결과에 따라 비밀번호 찾기 결과 페이지로 보냄

 

 

 

find_password_result.html

  • 비밀번호 찾기 요청 결과 페이지 생성

 

 


비밀번호 찾기 결과 페이지 테스트

 

  • localhost:8080/member/find/password
  • 비밀번호 초기화 요청

 

 

 

  • 비밀번호 찾기 요청 결과 페이지 노출

 

 


비밀번호 찾기 기능 구현

 

MemberService.java

public interface MemberService extends UserDetailsService {

    boolean register(MemberInput parameter);

    /**
     * uuid에 해당하는 계정을 활성화 함.
     */
    boolean emailAuth(String uuid);

    /**
     * 입력한 이메일로 비밀번호 초기화 정보를 전송
     */
    boolean sendResetPassword(ResetPasswordInput parameter);
}

 

 

 

Select Methods to Implement

 

 

 

MemberRepository.java

public interface MemberRepository extends JpaRepository<Member, String> {

    Optional<Member> findByEmailAuthKey(String emailAuthKey);
    Optional<Member> findByUserIdAndUserName(String userId, String userName);
}

 

 

 

Member.java

  • resetPasswordKey: 비밀번호 초기화 인증키
  • restePasswordLimitDt: 비밀번호 초기화 유효시간
public class Member {

    @Id
    private String userId;

    private String userName;
    private String phone;
    private String password;
    private LocalDateTime regDt;

    private boolean emailAuthYn;
    private LocalDateTime emailAuthDt;
    private String emailAuthKey;

    private String resetPasswordKey;
    private LocalDateTime resetPasswordLimitDt;
}

 

 

 

MemberServiceImpl.java

@Override
public boolean sendResetPassword(ResetPasswordInput parameter) {

    Optional<Member> optionalMember = memberRepository.
                    findByUserIdAndUserName(
                            parameter.getUserId(),
                            parameter.getUserName());
    if (!optionalMember.isPresent()) {
        throw new UsernameNotFoundException("회원 정보가 존재하지 않습니다.");
    }

    Member member = optionalMember.get();

    String uuid = UUID.randomUUID().toString();

    member.setResetPasswordKey(uuid);
    member.setResetPasswordLimitDt(LocalDateTime.now().plusDays(1));
    memberRepository.save(member);

    String email = parameter.getUserId();
    String subject = "[fastlms] 비밀번호 초기화 메일입니다.";
    String text = "<p>fastlms 비밀번호 초기화 메일입니다.</p>" +
            "<p>아래 링크를 클릭하셔서 비밀번호를 초기화 해주세요.</p>" +
            "<div><a target='_blank'" +
            "href='http://localhost:8080/member/reset/password?id=" +
            uuid + "'> 비밀번호 초기화 링크 </a></div>";
    mailComponents.sendMail(email, subject, text);

    return false;
}

 

 


비밀번호 찾기 기능 테스트

 

  • 비밀번호 초기화 요청

 

 

 

  • 비밀번호 찾기 요청 결과 페이지 노출

 

 

 

MemberController.java

  • 해당 값이 없는 케이스 대비

 

 

 

find_password_result.html

  • 값이 없거나 틀린 경우 다시 시도 링크를 보여줌
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title> 회원 비밀번호 찾기 요청 결과 </title>
</head>
<body>

    <h1>회원 비밀번호 찾기 요청 결과</h1>
    <div th:replace="/fragments/layout.html :: fragment-body-menu"></div>

    <div th:if="${result eq true}">
        <p> 입력하신 이메일로 비밀번호 초기화 정보를 발송했습니다. </p>
    </div>

    <div th:if="${result eq false}">
        <p> 요청하신 정보가 존재하지 않습니다. </p>
        <div>
            <a href="/member/find/password">다시 시도</a>
        </div>
    </div>

</body>
</html>

 

 

 

  • 임의의 값 입력

 

 

  • 해당 값이 없으므로 다시 시도 링크 노출

 

 

 

find_password.html

  • 이메일 입력받는 부분의 형식을 'email'로 수정

 

 

 

  • 비밀번호 초기화 요청 전 두 값 모두 <null>

 

 

  • 이메일 및 이름 입력
  • 비밀번호 초기화 요청

 

 

 

  • 초기화를 위한 인증키 값과 유효기간 값 저장

 

 

 

MemberService.java

  • return 값 수정

 

 

 

  • 회원 비밀번호 찾기 요청 결과
  • 이메일로 비밀번호 초기화 정보 발송

 

 

  • 인증하지 않고 다시 초기화 요청한 경우 인증키가 갱신됨

 

 

 

  • 수신한 메일에서 비밀번호 초기화 링크 클릭

 

 

 

  • 로그인 없이 접근 가능한 페이지 등록

 

 

 

  • 페이지는 없으나 접속가능하고,
  • 인증키 값이 보이는 것을 확인

 

 


비밀번호 초기화 페이지 구현

 

MemberController.java

  • 페이지 맵핑

 

 

 

reset_password.html

  • 회원 비밀번호 페이지 생성

 

 


비밀번호 초기화 페이지 연결 테스트

 

  • 확인 비밀번호는 서버로 전송되는 값이 아니라
  • 입력값이 맞는지 확인하는 용도로 클라이언트에서 확인용으로 사용

 

 


jQuery

 

https://releases.jquery.com/

  • 다운받기(minified)

 

 

 

  • 스크립트 복사

 

 

 

reset_password.html

  • 스크립트 붙여넣기

 

 

 

 

  • alert() 테스트

 

 

 

  • 입력 비빌번호를 출력

 

 

 

  • 비밀번호 입력
  • 비밀번호 재설정

 

 

 

  • alert()를 이용하여 입력값이 다름을 확인

 

 

 

  • 응용하여서 값이 받아온 뒤 비교 후
  • 다른 경우 불일치 메시지 노출

 

 

 

  • 불일치 테스트
  • 불일치 메시지 노출

 

 


비밀번호 초기화 기능 구현

 

MemberController.java

  • resetPassword() 에서는 초기화 인증키 값(uuid)를 받아오고
  • resetPasswordSumit()을 생성하고 맵핑

 

 

 

ResetPasswordInput.java

  • password 변수 추가

 

 

 

reset_password.html

  • 비밀번호 초기화 페이지에서 uuid 값 노출

 

 

 

  • 웹페이지에서 uuid 값이 보이는 것을 확인

 

 

 

ResetPasswordInput.java

  • 변경할 비밀번호의 id를 저장할 입력값 지정

 

 

 

MemberController.java

입력받은 비밀번호 parameter 값을 받아옴

 

 

 

reset_password.html

  • 입력받은 비밀번호 parameter값을 출력하는 부분 추가

 

 

 

  • 회원 비밀번호 초기화 - 비밀번호 재설정

 

 

 

  • 입력값이 정상적으로 넘어오는 것을 확인

 

 

 

MemberController.java

  • 비밀번호를 초기화할 ID와 비밀번호를 받아오고
  • 결과를 boolean 값으로 출력

 

 

 

  • 비밀번호를 초기화는 메소드 생성

 

 

 

MemberServiceImpl.java

  • Implement methods 추가

 

 

 

Select Methos to Impement

 

 

 

MemberServiceImpl.java

  • 정보가 없는 경우 출력값 지정

 

 

 

MemberRepository.java

  • Optional<Member> 추가

 

 

 

MemberServiceImpl.java

  • resetPassword() 구현

 

 

 

resetPassword()

@Override
public boolean resetPassword(String uuid, String password) {

    Optional<Member> optionalMember = memberRepository.findByResetPasswordKey(uuid);
    if (!optionalMember.isPresent()) {
        throw new UsernameNotFoundException("회원 정보가 존재하지 않습니다.");
    }

    Member member = optionalMember.get();

    //초기화 기간이 유효한지 체크
    if (member.getResetPasswordLimitDt() == null) {
        throw new RuntimeException(" 유효한 날짜가 아닙니다. ");
    }

    if (member.getResetPasswordLimitDt().isBefore(LocalDateTime.now())) {
        throw new RuntimeException(" 유효한 날짜가 아닙니다. ");
    }

    String encPassword = BCrypt.hashpw(password, BCrypt.gensalt());
    member.setPassword(encPassword);
    member.setResetPasswordKey("");
    member.setResetPasswordLimitDt(null);
    memberRepository.save(member);

    return true;
}

 

 

 

MemberController.java

@PostMapping("/member/reset/password")
public String resetPasswordSubmit(
        Model model,
        ResetPasswordInput parameter) {

    boolean result = false;
    try {
        result = memberService.resetPassword(
                parameter.getId(), parameter.getPassword());
    } catch (Exception e) {
    }

    model.addAttribute("result", result);

    return "member/reset_password_result";
}

 

 

 

reset_password_result.html

<body>

    <h1>회원 비밀번호 초기화 결과</h1>
    <!--<div th:replace="/fragments/layout.html :: fragment-body-menu"></div>-->

    <div th:if="${result eq true}">
        <p> 비밀번호가 초기화 되었습니다. </p>
        <div>
            <a href="/member/login">로그인 하기</a>
        </div></div>


    <div th:if="${result eq false}">
        <p> 비밀번호가 초기화에 실패하였습니다. </p>
        <div>
            <a href="/member/find/password">로그인 다시 찾기</a>
        </div></div>


</body>

 

 


비밀번호 초기화 기능 테스트

 

  • 비밀번호 입력 - 재설정

 

 

 

  • 비밀번호 초기화 결과 - 성공 - 로그인 하기

 

 

 

  • 회원 로그인 페이지로 이동

 

 


uuid가 유효한지 확인하는 기능 구현

 

checkResetPassword(uuid)

 

 

 

MemberService.java

 

 

 

MemberServiceImpl.java

@Override
public boolean checkResetPassword(String uuid) {

    Optional<Member> optionalMember = memberRepository.findByResetPasswordKey(uuid);
    if (!optionalMember.isPresent()) {
        return false;
    }

    Member member = optionalMember.get();

    //초기화 기간이 유효한지 체크
    if (member.getResetPasswordLimitDt() == null) {
        throw new RuntimeException(" 유효한 날짜가 아닙니다. ");
    }

    if (member.getResetPasswordLimitDt().isBefore(LocalDateTime.now())) {
        throw new RuntimeException(" 유효한 날짜가 아닙니다. ");
    }

    return true;
}

 

 

 

MemberController.java

@GetMapping("/member/reset/password")
public String resetPassword(
        Model model,
        HttpServletRequest request) {

    String uuid = request.getParameter("id");

    boolean result = memberService.checkResetPassword(uuid);

    model.addAttribute("result", result);

    return "member/reset_password";
}

 

 

 

reset_password.html

<body>

    <h1>회원 비밀번호 초기화</h1>
    <!--<div th:replace="/fragments/layout.html :: fragment-body-menu"></div>-->

    <div th:if="${result eq true}">
        <form method="post">

            <div th:text="${uuid}"></div>
            <div th:text="${parameter}"></div>

            <div>
                <input type="password"
                       name="password"
                       placeholder="비밀번호 입력" required/>
            </div>
            <div>
                <input type="password"
                       name="rePassword"
                       placeholder="확인 비밀번호 입력" required/>
            </div>
            <div>
                <button typre="submit"> 비밀번호 재설정 </button>
            </div>

        </form>
    </div>

    <div th:if="${result eq false}">
        <p> 입력 값이 유효하지 않습니다. </p>
        <div>
            <a href="/"> 메인으로 이동 </a>
        </div>
    </div>

</body>

 

 

 

  • 유효하지않은 인증 키값으로의 접근
  • 초기화 진행되지 않음

 


반응형