Spring boot

2024.10.08 Blog 프로젝트 만들기(JPA) 로그인 & 로그아웃 구현하기

정훈5 2024. 10. 8. 10:42

 

학습 목표 

1. Spring Boot와 JPA를 사용하여 로그인 및 로그아웃 기능을 구현할 수  있다.

 

1. 로그인 쿼리 만들기

먼저, UserRepository를 통해 사용자 이름과 비밀번호로 사용자를 조회하는 메서드를 만듭니다.

이 메서드는 JPQL을 사용하여 데이터베이스에서 사용자를 검색합니다.

 

 

UserRepository.java

더보기
package com.tenco.blog_v1.user;

import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class UserRepository {

    private final EntityManager em;

    /**
     * 사용자 이름과 비밀번호로 사용자 조회
     * @param username
     * @param password
     * @return 조회된 User 엔티티, Null
     */
    public User findByUserNameAndPassword(String username, String password) {
        TypedQuery<User> jpql =
                em.createQuery("SELECT u FROM User u WHERE u.username = :username AND u.password = :password", User.class);
        jpql.setParameter("username", username);
        jpql.setParameter("password", password);

        return jpql.getSingleResult();
    }

}

 

 

 

LoginDTO 만들기

로그인 요청을 처리하기 위해 Data Transfer Object (DTO)를 만듭니다.

이는 클라이언트로부터 전달받은 데이터를 안전하게 관리하는 데 사용됩니다.

UserDTO.JAVA

더보기
package com.tenco.blog_v1.user;

import lombok.Data;

@Data
public class UserDTO {

    // 정적 내부 클래스로 모으자
    @Data
    public static class loginDTO {
        private String username;
        private String password;
    }

    // 정적 내부 클래스로 모으자
    @Data
    public static class JoinDTO {
        private String username;
        private String password;
        private String email;
    }
}

 

UserController 구현

UserController를 구현하여 로그인 및 로그아웃 요청을 처리합니다.

이 컨트롤러는 사용자의 로그인 정보를 검증하고, 세션을 관리합니다.

UserController.java 추가

더보기
package com.tenco.blog_v1.user;

import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@Slf4j
@Controller
@RequiredArgsConstructor
public class UserController {
    
    // DI 처리
    private final UserRepository userRepository;
    private final HttpSession session;

    /**
     * 자원에 요청은 GET 방식이지만 보안의 이유로 예외!
     * 로그인 처리 메서드
     * 요청 주소 : POST  http://localhost:8080/login
     * @param reqDTO
     * @return
     */
    @PostMapping("/login")
    public String login(UserDTO.LoginDTO reqDTO) {
        try {
            User sessionUser = userRepository.findByUserNameAndPassword(reqDTO.getUsername(), reqDTO.getPassword());
            session.setAttribute("sessionUser", sessionUser);
            return "redirect:/";
        } catch (Exception e) {
            // 쿼리 스트링으로 error 이 들어오면 따로 처리하는 방식도 있다.
            return "redirect:/login-form?error";
        }
    }

    /**
     * 로그아웃
     * 주소 : http://localhost:8080/logout
     * @return
     */
    @GetMapping("/logout")
    public String logout() {
        session.invalidate(); // 세션을 무효화 한다. (로그아웃)
        return "redirect:/";
    }

    /**
     * 회원가입 페이지 요청
     * 주소 설계 : http://localhost:8080/join-form
     *
     * @param model
     * @return 문자열
     * 반환되는 문자열을 뷰 리졸버가 처리하면
     * 머스태치 템플릿 엔진을 통해서 뷰 파일을 렌더링 합니다.
     */
    @GetMapping("/join-form")
    public String joinForm(Model model) {

        log.info("회원가입 페이지");
        model.addAttribute("name", "회원가입 페이지");
        return "user/join-form"; // 템플릿 경로: user/join-form/mustache
    }

    /**
     * 로그인 페이지 요청
     * 주소 설계 : http://localhost:8080/login-form
     *
     * @param model
     * @return 문자열
     * 반환되는 문자열을 뷰 리졸버가 처리하면
     * 머스태치 템플릿 엔진을 통해서 뷰 파일을 렌더링 합니다.
     */
    @GetMapping("/login-form")
    public String loginForm(Model model) {

        log.info("로그인 페이지");
        model.addAttribute("name", "로그인 페이지");
        return "user/login-form"; // 템플릿 경로: user/login-form/mustache
    }

    /**
     * 회원 정보 페이지 요청
     * 주소 설계 : http://localhost:8080/user/update-form
     *
     * @param model
     * @return 문자열
     * 반환되는 문자열을 뷰 리졸버가 처리하면
     * 머스태치 템플릿 엔진을 통해서 뷰 파일을 렌더링 합니다.
     */
    @PostMapping("/user/update-form")
    public String updateForm(Model model) {

        log.info("회원 수정 페이지");
        model.addAttribute("name", "회원 수정 페이지");
        return "user/update-form"; // 템플릿 경로: user/update-form/mustache
    }

}

 

header.mustache 코드 수정

로그인을 하게되면 session 을 통해 board 에 있는 글이 달라진다.

더보기
<!DOCTYPE html>
<html lang="en">

<head>
    <title>Blog</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"/>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>

<body>
<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="/">Metacoding</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#collapsibleNavbar">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="collapsibleNavbar">
            <ul class="navbar-nav">
                {{# sessionUser}}
<!-- 로그인을 하게되면 세션이 생기는데 세션이 있다면  -->
                    <li class="nav-item">
                        <a class="nav-link" href="/board/save-form">글쓰기</a>
                    </li>

                    <li class="nav-item">
                        <a class="nav-link" href="/user/update-form">회원정보보기</a>
                    </li>

                    <li class="nav-item">
                        <a class="nav-link" href="/logout">로그아웃</a>
                    </li>
                {{/sessionUser}}


                {{^sessionUser}}
                    <!-- 로그인을 하게되면 세션이 생기는데 세션이 없다면  -->
                    <li class="nav-item">
                        <a class="nav-link" href="/join-form">회원가입</a>
                    </li>

                    <li class="nav-item">
                        <a class="nav-link" href="/login-form">로그인</a>
                    </li>

                {{/sessionUser}}




            </ul>
        </div>
    </div>
</nav>

 

로그아웃 클릭시 회원가입, 로그인 베너만 나온다.

 

 

등록되지 않는 유저로 로그인을 하게되면 ?error를 포함하게 된다.