[Spring Boot] 11. Spring Boot Project (Blog v2 - jpa)_11.Interceptor

김미숙's avatar
Jul 22, 2025
[Spring Boot] 11. Spring Boot Project (Blog v2 - jpa)_11.Interceptor
‼️
인증 자동화
리플렉션, 매개변수 분석 불가능
notion image

WebMvcConfig

package shop.mtcoding.blog._core.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import shop.mtcoding.blog._core.interceptor.LoginInterceptor; @Configuration // IoC 등록 public class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/user/**") .addPathPatterns("/board/**") .addPathPatterns("/love/**") .addPathPatterns("/reply/**") .addPathPatterns("/api/**") .excludePathPatterns("/board/{id:\\d+}"); } }

LoginInterceptor

package shop.mtcoding.blog._core.interceptor; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.springframework.web.servlet.HandlerInterceptor; import shop.mtcoding.blog._core.error.ex.Exception401; import shop.mtcoding.blog._core.error.ex.ExceptionApi401; import shop.mtcoding.blog.user.User; public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String uri = request.getRequestURI(); System.out.println("uri: " + uri); HttpSession session = request.getSession(); User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) { if (uri.contains("/api")) { throw new ExceptionApi401("인증이 필요합니다"); // response.setStatus(401); // response.setHeader("Content-Type", "application/json"); // PrintWriter out = response.getWriter(); // Resp<?> resp = Resp.fail(401, "인증이 필요합니다"); // ObjectMapper mapper = new ObjectMapper(); // String responseBody = mapper.writeValueAsString(resp); // out.println(responseBody); // return false; } else { throw new Exception401("인증이 필요합니다"); // response.setStatus(401); // response.setHeader("Content-Type", "text/html"); // PrintWriter out = response.getWriter(); // out.println(Script.href("인증이 필요합니다", "/login-form")); // return false; } } return true; } }

UserController

package shop.mtcoding.blog.user; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import shop.mtcoding.blog._core.util.Resp; import java.util.Map; @RequiredArgsConstructor @Controller public class UserController { private final UserService userService; private final HttpSession session; @GetMapping("/join-form") public String joinForm() { return "user/join-form"; } @PostMapping("/join") public String join(UserRequest.JoinDTO joinDTO) { userService.회원가입(joinDTO); return "redirect:/login-form"; } @GetMapping("/api/check-username-available/{username}") public @ResponseBody Resp<?> checkUsernameAvailable(@PathVariable("username") String username) { Map<String, Object> dto = userService.유저네임중복체크(username); return Resp.ok(dto); } @GetMapping("/login-form") public String loginForm() { return "user/login-form"; } @PostMapping("/login") public String login(UserRequest.LoginDTO loginDTO, HttpServletResponse response) { User sessionUser = userService.로그인(loginDTO); session.setAttribute("sessionUser", sessionUser); if (loginDTO.getRememberMe() == null) { Cookie cookie = new Cookie("username", null); cookie.setMaxAge(0); // 즉시 삭제 response.addCookie(cookie); } else { Cookie cookie = new Cookie("username", loginDTO.getUsername()); cookie.setMaxAge(60 * 60 * 24 * 7); // cookie 하루동안 유지 response.addCookie(cookie); } return "redirect:/"; } @GetMapping("/logout") public String logout() { session.invalidate(); return "redirect:/login-form"; } @GetMapping("/user/update-form") public String updateForm() { // ViewResolver -> prefix = /templates/ suffix = .mustache return "user/update-form"; } @PostMapping("/user/update") public String update(UserRequest.UpdateDTO updateDTO) { User sessionUser = (User) session.getAttribute("sessionUser"); User user = userService.회원정보수정(updateDTO, sessionUser.getId()); // session 동기화 -> 동기화 안해주면 바꾸기 전 정보를 보게 된다 session.setAttribute("sessionUser", user); return "redirect:/"; } }

user/join-form

{{> layout/header}} <div class="container p-5"> <!-- 요청을 하면 localhost:8080/join POST로 요청됨 username=사용자입력값&password=사용자값&email=사용자입력값 --> <div class="card"> <div class="card-header"><b>회원가입을 해주세요</b></div> <div class="card-body"> <form action="/join" method="post" enctype="application/x-www-form-urlencoded" onsubmit="return valid()"> <div class="mb-3"> <input type="text" class="form-control" placeholder="Enter username" name="username" id="username" value="haha"> <button type="button" class="btn btn-warning" onclick="checkUsernameAvailable()">중복확인</button> </div> <div class="mb-3"> <input type="password" class="form-control" placeholder="Enter password" name="password" id="password" value="1234"> </div> <div class="mb-3"> <input type="email" class="form-control" placeholder="Enter email" name="email" value="haha@nate.com"> </div> <button type="submit" class="btn btn-primary form-control">회원가입</button> </form> </div> </div> </div> <script> let isUsernameAvailable = false; // 1. username 변경 감지 let usernameDom = document.querySelector("#username"); usernameDom.addEventListener("keyup", () => { isUsernameAvailable = false; }) // 2. username 중복체크 async function checkUsernameAvailable() { let username = document.querySelector("#username").value; let response = await fetch("/api/check-username-available/" + username); let responseBody = await response.json(); // status = 200, msg = 성공, body: { "available" : true } isUsernameAvailable = responseBody.body.available; if (isUsernameAvailable) { alert("사용 가능한 아이디입니다."); } else { alert("이미 사용 중인 아이디입니다."); } } // 3. 최종 유효성 검사 function valid() { if (!isUsernameAvailable) { alert("아이디 중복체크를 해주세요"); return false; } return true; } </script> {{> layout/footer}}

BoardController

package shop.mtcoding.blog.board; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import shop.mtcoding.blog.user.User; @RequiredArgsConstructor @Controller public class BoardController { private final BoardService boardService; private final HttpSession session; @GetMapping("/") public String list(HttpServletRequest request) { User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) { request.setAttribute("models", boardService.글목록보기(null)); } else { request.setAttribute("models", boardService.글목록보기(sessionUser.getId())); } return "board/list"; } @PostMapping("/board/save") public String save(BoardRequest.SaveDTO saveDTO) { User sessionUser = (User) session.getAttribute("sessionUser"); boardService.글쓰기(saveDTO, sessionUser); return "redirect:/"; } @GetMapping("/board/save-form") public String saveForm() { return "board/save-form"; } @GetMapping("/board/{id}") public String detail(@PathVariable("id") Integer id, HttpServletRequest request) { User sessionUser = (User) session.getAttribute("sessionUser"); // 비로그인 시 상세보기 Integer sessionUserId = (sessionUser == null ? null : sessionUser.getId()); BoardResponse.DetailDTO detailDTO = boardService.글상세보기(id, sessionUserId); request.setAttribute("model", detailDTO); return "board/detail"; } @GetMapping("/board/{id}/update-form") public String updateForm(@PathVariable("id") Integer id, HttpServletRequest request) { User sessionUser = (User) session.getAttribute("sessionUser"); Board board = boardService.업데이트글보기(id, sessionUser.getId()); request.setAttribute("model", board); return "board/update-form"; } @PostMapping("/board/{id}/update") public String update(@PathVariable("id") Integer id, BoardRequest.UpdateDTO reqDTO) { User sessionUser = (User) session.getAttribute("sessionUser"); boardService.게시글수정(reqDTO, id, sessionUser.getId()); return "redirect:/board/" + id; } @PostMapping("/board/{id}/delete") public String delete(@PathVariable("id") Integer id) { User sessionUser = (User) session.getAttribute("sessionUser"); boardService.게시글삭제(id, sessionUser.getId()); return "redirect:/"; } }

LoveController

package shop.mtcoding.blog.love; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import shop.mtcoding.blog._core.util.Resp; import shop.mtcoding.blog.user.User; @RequiredArgsConstructor @RestController // 파일이 아닌 데이터 리턴하는 컨트롤러 (Ajax) public class LoveController { private final LoveService loveService; private final HttpSession session; @PostMapping("/api/love") public Resp<?> saveLove(@RequestBody LoveRequest.SaveDTO reqDTO) { User sessionUser = (User) session.getAttribute("sessionUser"); LoveResponse.SaveDTO respDTO = loveService.좋아요(reqDTO, sessionUser.getId()); return Resp.ok(respDTO); } @DeleteMapping("/api/love/{id}") public Resp<?> deleteLove(@PathVariable("id") Integer id) { User sessionUser = (User) session.getAttribute("sessionUser"); LoveResponse.DeleteDTO respDTO = loveService.좋아요취소(id, sessionUser.getId()); // loveId return Resp.ok(respDTO); } }

detail

{{> layout/header}} <input type="hidden" id="boardId" value="{{model.id}}"> <div class="container p-5"> <!-- 수정삭제버튼 --> {{#model.isOwner}} <div class="d-flex justify-content-end"> <a href="/board/{{model.id}}/update-form" class="btn btn-warning me-1">수정</a> <form action="/board/{{model.id}}/delete" method="post"> <button class="btn btn-danger">삭제</button> </form> </div> {{/model.isOwner}} <div class="d-flex justify-content-end"> <b>작성자</b> : {{model.username}} </div> <!-- 게시글내용 --> <div> <h2><b>{{model.title}}</b></h2> <hr/> <div class="m-4 p-2"> {{model.content}} </div> </div> <!-- AJAX 좋아요 영역 --> <div class="my-3 d-flex align-items-center"> {{#model.isLove}} <i id="loveIcon" class="fa fa-heart" style="font-size:20px; color:red" onclick="deleteLove({{model.loveId}})"></i> {{/model.isLove}} {{^model.isLove}} <i id="loveIcon" class="fa fa-heart" style="font-size:20px; color:black" onclick="saveLove()"></i> {{/model.isLove}} <span class="ms-1"><b id="loveCount">{{model.loveCount}}</b>명이 이 글을 좋아합니다</span> </div> <!-- 댓글 --> <div class="card mt-3"> <!-- 댓글등록 --> <div class="card-body"> <form action="/reply/save" method="post"> <input type="hidden" name="boardId" value="{{model.id}}"> <textarea class="form-control" rows="2" name="content"></textarea> <div class="d-flex justify-content-end"> <button type="submit" class="btn btn-outline-primary mt-1">댓글등록</button> </div> </form> </div> <!-- 댓글목록 --> <div class="card-footer"> <b>댓글리스트</b> </div> <div class="list-group"> <!-- 댓글아이템 --> {{#model.replies}} <div class="list-group-item d-flex justify-content-between align-items-center"> <div class="d-flex"> <div class="px-1 me-1 bg-primary text-white rounded">{{username}}</div> <div>{{content}}</div> </div> {{#isOwner}} <form action="/reply/{{id}}/delete" method="post"> <button type="submit" class="btn">🗑</button> </form> {{/isOwner}} </div> {{/model.replies}} </div> </div> </div> <script> // setInterval(()=>{ // location.reload(); // },1000) let boardId = document.querySelector("#boardId").value; async function saveLove() { let requestBody = {boardId: boardId}; let response = await fetch(`/api/love`, { method: "POST", body: JSON.stringify(requestBody), headers: {"Content-Type": "application/json"} }); let responseBody = await response.json(); // { loveId, loveCount } console.log(responseBody); if (responseBody.status != 200) { alert(responseBody.msg); } else { // DOM 업데이트 let loveIcon = document.querySelector('#loveIcon'); let loveCount = document.querySelector('#loveCount'); loveIcon.style.color = 'red'; loveIcon.setAttribute('onclick', `deleteLove(${responseBody.body.loveId})`); loveCount.innerHTML = responseBody.body.loveCount; } } async function deleteLove(loveId) { let response = await fetch(`/api/love/${loveId}`, { method: "DELETE" }); let responseBody = await response.json(); // response.text(); (X) console.log(responseBody); if (responseBody != 200) { alert(responseBody.msg); } else { // DOM 업데이트 let loveIcon = document.querySelector('#loveIcon'); let loveCount = document.querySelector('#loveCount'); loveIcon.style.color = 'black'; loveIcon.setAttribute('onclick', `saveLove()`); loveCount.innerHTML = responseBody.body.loveCount; } } </script> {{> layout/footer}}

ReplyController

package shop.mtcoding.blog.reply; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import shop.mtcoding.blog.user.User; @RequiredArgsConstructor @Controller public class ReplyController { private final ReplyService replyService; private final HttpSession session; @PostMapping("/reply/save") public String save(ReplyRequest.SaveDTO reqDTO) { User sessionUser = (User) session.getAttribute("sessionUser"); replyService.댓글쓰기(reqDTO, sessionUser); return "redirect:/board/" + reqDTO.getBoardId(); } @PostMapping("/reply/{id}/delete") public String delete(@PathVariable("id") Integer id) { User sessionUser = (User) session.getAttribute("sessionUser"); Integer boardId = replyService.댓글삭제(id, sessionUser.getId()); replyService.댓글삭제(id, sessionUser.getId()); return "redirect:/board/" + boardId; } }
Share article

parangdajavous