인증 자동화
리플렉션, 매개변수 분석 불가능

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