[Spring] 실전! 스프링 부트와 JPA 활용 Login 기능 구현

김상현·2022년 11월 13일
2

Spring

목록 보기
6/13
post-thumbnail

📒 [실전! 스프링 부트와 JPA 활용 - 김영한] 프로젝트를 완성한 후 부족한 기능을 추가한 후 정리하는 글입니다.


📍 로그인 기능

🧷 로그인 전

🧷 로그인 후


📌 Memeber Entity 수정

private String email;
private String password;
  • 첫 번째로 수정한 부분은 Member Entity의 속성이다.
  • 로그인 기능을 위해서 필요한 추가적인 속성인 이메일(email), 비밀번호(password) 속성을 Member Entity에 추가하였다.
public boolean checkPassword(String password){
    return this.password.equals(password);
}
  • 다음으로 Member Entity에 비밀번호 검증을 위한 메서드를 추가하였다.
  • 메서드의 인자로 받은 비밀번호 값(password)과, 현재 개체의 비밀번호 값(this.password)을 equals() 메서드를 통해 boolean 값을 반환하는 checkPassword() 메서드를 추가하였다.

📌 MemeberRepository 수정

public Optional<Member> findByEmail(String email){
    return em.createQuery("select m from Member m where m.email = :email",Member.class)
            .setParameter("email", email)
            .getResultList().stream().findAny();
}
  • MemberRepository에 데이터베이스에서 이메일(email)을 통해 Member 개체를 찾는 함수를 구현하였다.
  • 이메일(email)에 해당하는 개체가 존재하지 않은 경우 NullPointException 예외가 발생하기 때문에 이를 방지하기 위해서 Optional 클래스를 사용하였다.
  • 데이터베이스에서 이메일(email) 정보는 중복이 존재하지 않는 속성이기 때문에 getResultList() 대신 getSingleResult() 를 사용하고자 했지만, getSingleResult()는 데이터베이스에 해당 이메일 속성을 가진 개체가 존재하지 않으면 Null을 반환하는 것이 아닌 예외가 발생하는 문제가 발생하기 때문에 getResultList()를 사용하였다.

📌 LoginService 생성

private final MemberRepository memberRepository;

public Member login(String email, String password) {
    Optional<Member> findMember = memberRepository.findByEmail(email);
    if(!findMember.orElseThrow(()->new NotCorrespondingEmailException("해당 이메일이 존재하지 않습니다.")).checkPassword(password)){
        throw new IllegalStateException("이메일과 비밀번호가 일치하지 않습니다.");
    }
    return findMember.get();
}
  • 데이터베이스에 존재하는 Member 개체에 접근하기 위해서 MemberRepository 를 생성한 후 의존성 주입(DI)을 해준다.
  • findByEmail() 메서드를 통해 반환받은 Optional 클래스가 적용된 Member 개체가 Null이 아닌지 확인하기 위해 orElseThrow() 메서드를 적용한다.
    • 만약 Member 개체가 Null이라면 사용자 정의 예외(NotCorrespondingEmailException)를 터뜨려준다.
    • Member 개체가 Null이 아니라면 checkPassword() 메서드를 통해 고객으로부터 전달받은 비밀번호(password)와 객체의 비밀번호가 일치하는지 확인한다.
      • 일치하지 않는다면 IllegalStateException() 예외를 터뜨려준다.
      • 일치한다면 findByEmail() 메서드를 통해 반환받은 Member 개체를 반환해준다.

📌 HomeController 수정

@GetMapping("/")
public String homeLogin(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false)Member member, Model model){
    /**
     * Login 페이지로 이동
     **/
    if(member == null) {
        model.addAttribute("loginForm", new LoginForm());
        return "logins/loginForm";
    }
    /**
     * 메인 페이지로 이동
     **/
    model.addAttribute("member", member);
    return "home";
}
  • '/' 에 해당하는 페이지에서 현재 로그인 Session 존재 여부에 따라 다른 페이지로 연결이 된다.
  • @SessionAttribute 어노테이션을 활용하여 현재 SessionConst.LOGIN_MEMBER 에 해당하는 Session이 있는지 확인한다.
    • @SessionAttribute 어노테이션의 required 속성은 기본값이 true이다.
    • required 속성은 값이 true일 경우, Session이 존재하지 않는다면 Session이을 생성하여 반환한다.
    • 항상 Session을 생성하여 반환한다면 현재 상태가 로그인 상태인지, 비로그인 상태인지 구별이 불가능하기 때문에 required 속성의 값을 false 로 할당한다.
  • 현재 SessionConst.LOGIN_MEMBER 에 해당하는 Session이 존재하지 않는다면 ModelLoginForm 개체를 추가하여 로그인 페이지로 이동한다.
  • 반대로 Session이 존재한다면 Model에 현재 로그인한 Member 개체를 추가하여 메인 페이지로 이동한다.

📌 LoginController 생성

@PostMapping("/login")
public String login(@Valid LoginForm form, BindingResult bindingResult, HttpServletRequest request) {

    // LoginForm 에 email 혹은 password 의 값이 존재하지 않을 때
    if (bindingResult.hasErrors()) {
        return "/logins/loginForm";
    }

    Member loginMember = loginService.login(form.getEmail(), form.getPassword());

    /**
     * 로그인 성공 처리
     **/
    //세션이 있으면 있는 세션 반환, 없으면 신규 세션을 생성
    HttpSession session = request.getSession();
    //세션에 로그인 회원 정보 보관
    session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
    return "redirect:/";
}
  • loginLoginForm 객체와 검증 오류가 발생할 경우 오류 내용을 보관하는 스프링 프레임워크에서 제공하는 객체인 BindingResult 그리고 Sessions 생성을 위한 HttpServletRequest 를 인자로 사용한다.
    • 만약 오류가 발생해서 BindingResult 에 값이 존재한다면 현재 페이지를 재시작하여 고객에게 현재 발생한 문제 정보를 전달한다.
    • LoginForm 에 저장된 이메일(email), 비밀번호(password) 정보를 이용하여 LoginServicelogin 메서드를 실행한 후 Member 객체를 반환받는다.
    • HttpServletRequestgetSession() 메서드를 통해 Session을 생성한 후 setAttribute() 메서드를 통해 생성한 Session을 등록한 후 메인 페이지로 이동한다.
// 서블릿 HTTP 세션 사용
@PostMapping("/logout")
public String logout(HttpServletRequest request) {
    // getSession(false) 를 사용해야 함 (세션이 없더라도 새로 생성하면 안되기 때문)
    HttpSession session = request.getSession(false);
    if (session != null) {
        session.invalidate();
    }
    return "redirect:/";
}
  • logoutHttpServletRequest 에서 getSession() 메서드를 통해서 Session을 불러온 후 invalidate() 메서드를 통해 Session을 삭제한다.

📍 기능 구현을 통해 학습한 내용

📌 Optional

📒 참고 : JAVA Docs

  • 개발시에 발생하는 NPE(Null Pointer Exception)을 처리하기 위해 자바에서 제공하는 클래스이다.
  • Optional 은 Null이 아닌 값을 포함하거나 포함하지 않을 수 있는 컨테이너 객체이다.
  • Optional 은 클래스이기 때문에 다양한 메서드를 지원한다.
  • 즉, Optional 클래스는 반환값이 Null이 발생할 수도 있는 메서드에 사용하면 NPE를 피할 수 있고, 다양한 Optional 의 메서드를 통해 Null이 발생했을 때 문제를 해결할 수 있다.

📌 @SessionAttribute

📒 참고 : Spring Docs

  • @SessionAttribute 은 메소드 매개변수를 세션 속성에 바인딩하기 위한 어노테이션이다.
  • 기존의 영구적인 세션 속성(사용자 인증)에 대한 편리한 접근을 제공한다.
  • @SessionAttribute 의 속성
    • name : 바인딩할 세션 속성의 이름
    • value : name()에 대한 별칭
    • required : 세션 속성이 필요한지 여부

📌 BindingResult

📒 참고 : Spring Docs

  • BindingResult 는 검증 오류가 발생할 경우 오류 내용을 보관하는 스프링 프레임워크에서 제공하는 객체이다.
  • 이를 이용하여 오류가 발생했을 때의 상황을 예외적으로 처리할 수 있다.

📌 HttpServletRequest

📒 참고 : JAVA Docs

  • HttpServletRequest 는 HTTP 서블릿에 대한 요청 정보를 제공한다.
  • 서블릿 컨테이너는 객체를 생성하고 이를 서블릿의 서비스 메소드 HttpServletRequest 에 인수로 전달한다.
profile
목적 있는 글쓰기

0개의 댓글