[๊ฐ์] ์คํ๋ง MVC 2ํธ - ๋ฐฑ์๋ ์น ๊ฐ๋ฐ ํต์ฌ ๊ธฐ์ 5
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2
์คํ๋ง MVC 2ํธ - ๋ฐฑ์๋ ์น ๊ฐ๋ฐ ํ์ฉ ๊ธฐ์ - ์ธํ๋ฐ | ๊ฐ์
์น ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ์ ํ์ํ ๋ชจ๋ ์น ๊ธฐ์ ์ ๊ธฐ์ด๋ถํฐ ์ดํดํ๊ณ , ์์ฑํ ์ ์์ต๋๋ค. MVC 2ํธ์์๋ MVC 1ํธ์ ํต์ฌ ์๋ฆฌ์ ๊ตฌ์กฐ ์์ ์ค๋ฌด ์น ๊ฐ๋ฐ์ ํ์ํ ๋ชจ๋ ํ์ฉ ๊ธฐ์ ๋ค์ ํ์ตํ ์ ์
www.inflearn.com
6. ๋ก๊ทธ์ธ ์ฒ๋ฆฌ1 - ์ฟ ํค, ์ธ์
- ๋ก๊ทธ์ธ ์๊ตฌ ์ฌํญ
- ํ ํ๋ฉด - ๋ก๊ทธ์ธ ์
- ํ์ ๊ฐ์
- ๋ก๊ทธ์ธ
- ํ ํ๋ฉด - ๋ก๊ทธ์ธ ํ
- ๋ณธ์ธ ์ด๋ฆ(๋๊ตฌ๋ ํ์ํฉ๋๋ค.)
- ์ํ ๊ด๋ฆฌ
- ๋ก๊ทธ ์์
- ๋ณด์ ์๊ตฌ์ฌํญ
- ๋ก๊ทธ์ธ ์ฌ์ฉ์๋ง ์ํ์ ์ ๊ทผํ๊ณ , ๊ด๋ฆฌํ ์ ์์
- ๋ก๊ทธ์ธ ํ์ง ์์ ์ฌ์ฉ์๊ฐ ์ํ ๊ด๋ฆฌ์ ์ ๊ทผํ๋ฉด ๋ก๊ทธ์ธ ํ๋ฉด์ผ๋ก ์ด๋
- ํ์ ๊ฐ์ , ์ํ ๊ด๋ฆฌ
- ํ๋ก์ ํธ ์์ฑ
- ์์กด๊ด๊ณ๋ ๋จ๋ฐฉํฅ์ผ๋ก ํ๋ฌ์ผ ํ๋ค
๋๋ฉ์ธ์ด ๊ฐ์ฅ ์ค์ํ๋ฏ๋ก ์น์ด ๋๋ฉ์ธ์ ์ฐธ์กฐํ๋๋ก ํด์ผํ๋ค.
์น์ ๋ค์ด๋ด์ ์ฝ๋๋ฅผ ๋ค ๊ฐ์ ์์ด๋ ๋๋ฉ์ธ์๋ ์๋ฌด ๋ฌธ์ ๊ฐ ์๊ธฐ์ง ์๋๋ก
- ํ์ ๊ฐ์
- ๋ก๊ทธ์ธ ํผ html ์ถ๊ฐ
- ๋ก๊ทธ์ธ ๋ ํฌ ์ถ๊ฐ(List๋ฅผ ์ด์ฉํด ์์ ๋ ํฌ, ํ ์คํธ์ฉ ๋ฐ์ดํฐ ๋ฃ๋ ์ฝ๋๋ ํจ๊ป)
- ๋ก๊ทธ์ธ ์ปจํธ๋กค๋ฌ
@Controller
@RequiredArgsConstructor
@RequestMapping("/members")
public class MemberController {
private final MemberRepository memberRepository;
@GetMapping("/add")
public String addForm(@ModelAttribute("member") Member member) {
return "members/addMemberForm";
}
@PostMapping("/add")
public String save(@Valid @ModelAttribute Member member, BindingResult result) {
if (result.hasErrors()) {
return "members/addMemberForm";
}
memberRepository.save(member);
return "redirect:/";
}
- ๋ก๊ทธ์ธ ๊ธฐ๋ฅ
- LoginService ๋๋ฉ์ธ์ ์ถ๊ฐ, ๋น์ฆ๋์ค ๋ก์ง , ์์ด๋ ๋น๋ฐ๋ฒํธ ๋ง๋์ง ํ์ธ
@Service
@RequiredArgsConstructor
public class LoginService {
private final MemberRepository memberRepository;
public Member login(String loginId, String password) {
return memberRepository.findByLoginId(loginId)
.filter(m -> m.getPassword().equals(password))
.orElse(null);
}
}
- ๋ก๊ทธ์ธ ํผ html ์ถ๊ฐ
- ๋ก๊ทธ์ธ ํผ dto ์ถ๊ฐ
- ๋ก๊ทธ์ธ Controller
@Slf4j
@Controller
@RequiredArgsConstructor
public class LoginController {
private final LoginService loginService;
@GetMapping("/login")
public String loginForm(@ModelAttribute("loginForm") LoginForm form) {
return "login/loginForm";
}
@PostMapping("/login")
public String login(@Valid @ModelAttribute LoginForm form, BindingResult
bindingResult) {
if (bindingResult.hasErrors()) {
return "login/loginForm";
}
Member loginMember = loginService.login(form.getLoginId(),
form.getPassword());
log.info("login? {}", loginMember);
if (loginMember == null) {
bindingResult.reject("loginFail", "์์ด๋ ๋๋ ๋น๋ฐ๋ฒํธ๊ฐ ๋ง์ง ์์ต๋๋ค.");
return "login/loginForm";
}
//๋ก๊ทธ์ธ ์ฑ๊ณต ์ฒ๋ฆฌ TODO
return "redirect:/";
}
- ๋ก๊ทธ์ธ ์ฒ๋ฆฌํ๊ธฐ - ์ฟ ํค ์ฌ์ฉ
๋ก๊ทธ์ธ -> ๋ก๊ทธ์ธ ์ฑ๊ณต
<- ์ฟ ํค ๋ง๋ค์ด์ ๋ณด๋ด์ค
๋ชจ๋ ์์ฒญ์ ์ฟ ํค ์๋ ํฌํจ
์ฟ ํค์๋ ์์, ์ธ์ ์ฟ ํค๊ฐ ์๋๋ฐ ๋ก๊ทธ์ธ์ ๋ธ๋ผ์ฐ์ ์ข ๋ฃ ์๊น์ง๋ง ์ ์ง๋๋ฉด ๋๋ฏ๋ก ์ธ์ ๋ฐฉ์์ ์ ํํ๋ค.
@PostMapping("/login")
public String login(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult
, HttpServletResponse response) {
if (bindingResult.hasErrors()) {
return "login/loginForm";
}
Member loginMember = loginService.login(form.getLoginId(),
form.getPassword());
log.info("login? {}", loginMember);
if (loginMember == null) {
bindingResult.reject("loginFail", "์์ด๋ ๋๋ ๋น๋ฐ๋ฒํธ๊ฐ ๋ง์ง ์์ต๋๋ค.");
return "login/loginForm";
}
//๋ก๊ทธ์ธ ์ฑ๊ณต ์ฒ๋ฆฌ
Cookie idCookie = new Cookie("memberId",
String.valueOf(loginMember.getId()));
response.addCookie(idCookie);
return "redirect:/";
}
@PostMapping("/logout")
public String logout(HttpServletResponse response) {
expireCookie(response, "memberId");
return "redirect:/";
}
private void expireCookie(HttpServletResponse response, String cookieName) {
Cookie cookie = new Cookie(cookieName, null);
cookie.setMaxAge(0);
response.addCookie(cookie);
}
ํ ์คํธ ๊ฒฐ๊ณผ
home์์๋ ๋ก๊ทธ์ธ์ด ์ ์ง๋ ์ ์๋๋ก ๋ง๋ค๊ธฐ/ ๋ก๊ทธ์์ ๋ง๋ค๊ธฐ
@GetMapping("/")
public String homeLogin(@CookieValue(name = "memberId", required = false) Long memberId, Model model) {
if (memberId == null) {
return "home";
}
//๋ก๊ทธ์ธ
Member loginMember = memberRepository.findById(memberId);
if (loginMember == null) {
return "home";
}
model.addAttribute("member", loginMember);
return "loginHome";
}
๋ก๊ทธ์ธ ํ์๋ ํ ํ๋ฉด์ด ์ด๋ ๊ฒ ๋ฐ๋๋ฉฐ ๋ธ๋ผ์ฐ์ ๋ฅผ ๊ป๋ค๊ฐ ์ผ๋ฉด ์ฌ๋ผ์ง๋ค.
- ์ฟ ํค์ ๋ณด์ ๋ฌธ์
์ฟ ํค๋ ์ฌ์ค ํด๋ผ์ด์ธํธ์์ ์์๋ก ๋ณด๋ผ ์ ์๋ค -> ๋ค๋ฅธ ์ฌ์ฉ์๋ค์ ๊ฐ์ธ์ ๋ณด๊ฐ ๋ค ํธ๋ฆฐ๋ค
ํด์ปค๊ฐ ์ฟ ํค๋ฅผ ํ์ณ์ ์ ์ฉํ ์๋ ์๋ค
-> ๋์
์ฟ ํค์ ์๊ฐ์ ํ์ ์ค๋ค
์์ธก ๋ถ๊ฐ๋ฅํ ์์์ ํ ํฐ์ ์ค๋ค
ํดํน์ด ์์ฌ๋๋ ๊ฒฝ์ฐ ์ฟ ํค๋ฅผ ์ง์ ์ ๊ฑฐํ๋ค
- ๋ก๊ทธ์ธ ์ฒ๋ฆฌํ๊ธฐ - ์ธ์ ๋์ ๋ฐฉ์
์ฟ ํค์ ์ค์ํ ์ ๋ณด๋ฅผ ๋ณด๊ดํ๊ณ ์ฐ๊ฒฐ ์ ์งํ๋ ๋ฐฉ๋ฒ : ์ธ์
์ฟ ํค ๊ฐ์ ๋ณ์กฐ - ์์ ๋ถ๊ฐ๋ฅํ ๋ณต์กํ ์ธ์ id
ํธ๋ ค๋ ๋ณต์กํ๋ฏ๋ก ์๊ธฐ ์ด๋ ต๋ค
- ๋ก๊ทธ์ธ ์ฒ๋ฆฌํ๊ธฐ - ์ธ์ ์ง์ ๋ง๋ค๊ธฐ
@Component
public class SessionManager {
public static final String SESSION_COOKIE_NAME = "mySessionId";
private Map<String, Object> sessionStore = new ConcurrentHashMap<>();
/**
* ์ธ์
์์ฑ
*/
public void createSession(Object value, HttpServletResponse response) {
//์ธ์
id๋ฅผ ์์ฑํ๊ณ , ๊ฐ์ ์ธ์
์ ์ ์ฅ
String sessionId = UUID.randomUUID().toString();
sessionStore.put(sessionId, value);
//์ฟ ํค ์์ฑ
Cookie mySessionCookie = new Cookie(SESSION_COOKIE_NAME, sessionId);
response.addCookie(mySessionCookie);
}
/**
* ์ธ์
์กฐํ
*/
public Object getSession(HttpServletRequest request) {
Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
if (sessionCookie == null) {
return null;
}
return sessionStore.get(sessionCookie.getValue());
}
/**
* ์ธ์
๋ง๋ฃ
*/
public void expire(HttpServletRequest request) {
Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
if (sessionCookie != null) {
sessionStore.remove(sessionCookie.getValue());
}
}
private Cookie findCookie(HttpServletRequest request, String cookieName) {
if (request.getCookies() == null) {
return null;
}
return Arrays.stream(request.getCookies())
.filter(cookie -> cookie.getName().equals(cookieName))
.findAny()
.orElse(null);
}
class SessionManagerTest {
SessionManager sessionManager = new SessionManager();
@Test
void sessionTest() {
//์ธ์
์์ฑ
//ํ
์คํธ์ฉ ๊ฐ์ง Mock ์ ๊ณต
MockHttpServletResponse response = new MockHttpServletResponse();
Member member = new Member();
sessionManager.createSession(member, response);
//์์ฒญ์ ์๋ต ์ฟ ํค ์ ์ฅ
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(response.getCookies());
//์ธ์
์กฐํ
Object result = sessionManager.getSession(request);
assertThat(result).isEqualTo(member);
//์ธ์
๋ง๋ฃ
sessionManager.expire(request);
Object expired = sessionManager.getSession(request);
assertThat(expired).isNull();
}
Key : UUID
Value : member id๋ก
UUID๋ฅผ ํตํด member id์ ์ ๊ทผ
- ๋ก๊ทธ์ธ ์ฒ๋ฆฌํ๊ธฐ - ์ง์ ๋ง๋ ์ธ์ ์ ์ฉ
@PostMapping("/login")
public String loginV2(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult, HttpServletResponse response) {
if (bindingResult.hasErrors()) {
return "login/loginForm";
}
Member loginMember = loginService.login(form.getLoginId(),
form.getPassword());
log.info("login? {}", loginMember);
if (loginMember == null) {
bindingResult.reject("loginFail", "์์ด๋ ๋๋ ๋น๋ฐ๋ฒํธ๊ฐ ๋ง์ง ์์ต๋๋ค.");
return "login/loginForm";
}
//๋ก๊ทธ์ธ ์ฑ๊ณต ์ฒ๋ฆฌ
sessionManager.createSession(loginMember, response);
return "redirect:/";
}
@PostMapping("/logout")
public String logoutV2(HttpServletRequest request) {
sessionManager.expire(request);
return "redirect:/";
}
@GetMapping("/")
public String homeLoginV2(HttpServletRequest request, Model model) {
Member member = (Member)sessionManager.getSession(request);
if (member == null) {
return "home";
}
//๋ก๊ทธ์ธ
model.addAttribute("member", member);
return "loginHome";
}
sessionManager๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ์์ผ๋ก ๋ชจ๋ ์์ ํ๋ค.
์ด๋ ๊ฒ ์ผ์ผํ ๊ฐ๋ฐํด์ผ ๋๋ ์ธ์ ์ ์๋ธ๋ฆฟ์์ ์ง์ํด์ค๋ค.
- ๋ก๊ทธ์ธ ์ฒ๋ฆฌํ๊ธฐ - ์๋ธ๋ฆฟ HTTP ์ธ์ 1
๊ธฐ๋ณธ์ผ๋ก ๋ด์ฅ๋ ๊ธฐ๋ฅ ์ฌ์ฉ
@PostMapping("/login")
public String loginV3(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult, HttpServletRequest request) {
if (bindingResult.hasErrors()) {
return "login/loginForm";
}
Member loginMember = loginService.login(form.getLoginId(), form.getPassword());
log.info("login? {}", loginMember);
if (loginMember == null) {
bindingResult.reject("loginFail", "์์ด๋ ๋๋ ๋น๋ฐ๋ฒํธ๊ฐ ๋ง์ง ์์ต๋๋ค.");
return "login/loginForm";
}
//๋ก๊ทธ์ธ ์ฑ๊ณต ์ฒ๋ฆฌ
//์ธ์
์ด ์์ผ๋ฉด ์๋ ์ธ์
๋ฐํ, ์์ผ๋ฉด ์ ๊ท ์ธ์
์์ฑ
HttpSession session = request.getSession();
//์ธ์
์ ๋ก๊ทธ์ธ ํ์ ์ ๋ณด ๋ณด๊ด
session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
return "redirect:/";
}
request.getSession(), (true) -> ์ธ์ ์ด ์์ผ๋ฉด ์ธ์ ๋ฐํ, ์์ผ๋ฉด ์์ฑ
(false) -> ์์ผ๋ฉด ๋๊ฐ์ด, ์์ผ๋ฉด null
@PostMapping("/logout")
public String logoutV3(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
return "redirect:/";
}
@GetMapping("/")
public String homeLoginV3(HttpServletRequest request, Model model) {
HttpSession session = request.getSession(false);
if(session == null)
return "home";
Member loginMember = (Member)session.getAttribute(SessionConst.LOGIN_MEMBER);
if (loginMember == null) {
return "home";
}
//๋ก๊ทธ์ธ
model.addAttribute("member", loginMember);
return "loginHome";
}
JSESSIONID๋ผ๋ ์ด๋ฆ์ผ๋ก ์ฟ ํค๊ฐ ์ ์ ํ๊ฒ ์์ฑ๋๋ค
- ๋ก๊ทธ์ธ ์ฒ๋ฆฌํ๊ธฐ - ์๋ธ๋ฆฟ HTTP ์ธ์ 2
- @SessionAttribute
@GetMapping("/")
public String homeLoginV3Spring(
@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember, Model model) {
if (loginMember == null) {
return "home";
}
//๋ก๊ทธ์ธ
model.addAttribute("member", loginMember);
return "loginHome";
}
@SessionAttribute๋ก ์ฝ๋๋ฅผ ๊ฐ๋จํ๊ฒ ์ค์ผ ์ ์๋ค.
์ธ์ ์ ์ฐพ์์จ๋ค(์์ฑํ์ง๋ ์๋๋ค.)
- TrakingModes
์ฟ ํค๋ฅผ ์ง์ํ์ง ์๋ ๊ฒฝ์ฐ, URL์ ์ด์ฉํด์ ์ ์ง์ํจ๋ค. ์ฒ์์ ํ๋จ์ด ๋ถ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ
์ง์ ๋ถํ๋ค.
์ด๋ฅผ ๋๋ ๋ฐฉ๋ฒ์
server.servlet.session.tracking-modes=cookie
๋ฅผ ์ถ๊ฐํด์ฃผ๋ฉด ๋ ์ ์๋ค.
- ์ธ์ ์ ๋ณด์ ํ์์์ ์ค์
@Slf4j
@RestController
public class SessionInfoController {
@GetMapping("/session-info")
public String sessionInfo(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return "์ธ์
์ด ์์ต๋๋ค.";
}
//์ธ์
๋ฐ์ดํฐ ์ถ๋ ฅ
session.getAttributeNames().asIterator()
.forEachRemaining(name -> log.info("session name={}, value={}",
name, session.getAttribute(name)));
log.info("sessionId={}", session.getId());
log.info("maxInactiveInterval={}", session.getMaxInactiveInterval());
log.info("creationTime={}", new Date(session.getCreationTime()));
log.info("lastAccessedTime={}", new
Date(session.getLastAccessedTime()));
log.info("isNew={}", session.isNew());
return "์ธ์
์ถ๋ ฅ";
}
}
์ธ์ ์ ์ ๋ณด๋ค์ ๋ณธ๋ค.
์ธ์ ์ ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์์์ ์ง์ ํธ์ถํ ๋๋ง ์ญ์ ๋๋ค.
ํ์ง๋ง ๋๋ถ๋ถ์ ์ฌ์ฉ์๋ค์ ์ด๋ฅผ ์ ํํ์ง ์๊ณ , HTTP๋ ๋น์ฐ๊ฒฐ์ฑ์ด๋ฏ๋ก ์๋ฒ ์ ์ฅ์์๋ ์ฌ์ฉ์๊ฐ ์น ๋ธ๋ผ์ฐ์ ๋ฅผ ์ข ๋ฃํ ๊ฒ์ธ์ง ์๋์ง ์ธ์ํ ์ ์๋ค.
์๋ฒ์์ ์ธ์ ๋ฐ์ดํฐ๋ฅผ ์ธ์ ์ญ์ ํด์ผ๋ ๊น? - ์ผ๋จ ๋ฌดํ์ ์ ์๋จ, ๋ณด์์/ ๋ฉ๋ชจ๋ฆฌ์
๋ฉ๋ชจ๋ฆฌ์ ๋ฐ์ดํฐ๊ฐ ์ฐ๋๋ ์์ด๋ฉด ๋ฌธ์ ๊ฐ ๋ฐ์ํ ๊ฐ๋ฅ์ฑ์ด ๋งค์ฐ ๋๋ค.
-> ์ธ์ ์์ ์ต์ํ์ ๋ฐ์ดํฐ๋ง ๋ณด๊ดํด์ผํ๋ค.
-> ์ฌ์ฉ์๊ฐ ์๋ฒ์ ์ต๊ทผ์ ์์ฒญํ ์๊ฐ์ ๊ธฐ์ค์ผ๋ก 30๋ถ์ ์ ์งํด์ฃผ๋ฉด ์ด๋จ๊น?
- ๊ธ๋ก๋ฒ์ค์
server.servlet.session.timeout=60
- ์ธ์ ๋จ์๋ก ์ค์
session.setMaxInactiveInterval(1800);
- ์ธ์ ํ์์์ ๋ฐ์
์ธ์ ํ์์์ ์๊ฐ์ ํด๋น ์ธ์ ๊ณผ ๊ด๋ จ๋ JSESSIONID๋ฅผ ์ ๋ฌํ๋ HTTP ์์ฒญ์ด ์์ ๋๋ง๋ค ์ด๊ธฐํ์ํจ๋ค.
LastAccessedTime(session.getLastAccessedTime()) ์ดํ๋ก timeout ์๊ฐ์ด ์ง๋๋ฉด WAS๊ฐ ๋ด๋ถ์์ ํด๋น ์ธ์ ์ ์ ๊ฑฐ
7. ๋ก๊ทธ์ธ ์ฒ๋ฆฌ2 - ํํฐ, ์ธํฐ์ ํฐ
- ์๋ธ๋ฆฟ ํํฐ - ์๊ฐ
URL๋ก ๋ง ์ฌ๊ธฐ์ ๊ธฐ ๋ค์ด๊ฐ ์ ์์ผ๋ฉด ๋ก๊ทธ์ธ ๋์ง ์์ ์ฌ์ฉ์๊ฐ ๋ค์ด๊ฐ๋ฉด ์๋๋ ๊ณณ๊น์ง ๋ค์ด๊ฐ์ง๋ค = ๋ฌธ์
์ด๋ฅผ
ํํฐ : ์๋ธ๋ฆฟ์ด
์ธํฐ์ ํฐ : ์คํ๋ง์ด ์ ๊ณตํ๋ ๊ธฐ๋ฅ
๋ก๊ทธ์ธ ์ฌ๋ถ ์ฒดํฌ๋ฅผ ๋งค api ํธ์ถ ๋๋ง๋ค ํ๋๋ก ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ ๋ณ๋ก๋ค.
์ด๋ฐ ๊ณตํต๊ด์ฌ์ฌ๋ ์คํ๋ง AOP๋ก๋ ํด๊ฒฐํ ์ ์์ง๋ง, ์น๊ณผ ๊ด๋ จํ ๊ณตํต ๊ด์ฌ์ฌ๋ ์๋ธ๋ฆฟ ํํฐ, ์คํ๋ง ์ธํฐ์ ํฐ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค.(HttpServletRequest๋ฅผ ์ ๊ณตํ๊ธฐ ๋๋ฌธ)
์๋ธ๋ฆฟ ํํฐ : ์๋ธ๋ฆฟ์ด ์ง์ํ๋ ์๋ฌธ์ฅ
URL ํจํด๋ณ๋ก ์ ์ฉํ ์ ์์(/* = ๋ชจ๋ ์์ฒญ)
ํํฐ ํ๋ฆ
HTTP ์์ฒญ -> WAS -> ํํฐ -> ์๋ธ๋ฆฟ -> ์ปจํธ๋กค๋ฌ
ํํฐ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํ, ๋ฑ๋กํ๋ฉด
์๋ธ๋ฆฟ ์ปจํ ์ด๋๊ฐ ์ฑ๊ธํค ๊ฐ์ฒด๋ก ์์ฑํ๊ณ ๊ด๋ฆฌํจ.
init(): ํํฐ ์ด๊ธฐํ ๋ฉ์๋, ์๋ธ๋ฆฟ ์ปจํ
์ด๋๊ฐ ์์ฑ๋ ๋ ํธ์ถ๋๋ค.
doFilter(): ๊ณ ๊ฐ์ ์์ฒญ์ด ์ฌ ๋ ๋ง๋ค ํด๋น ๋ฉ์๋๊ฐ ํธ์ถ๋๋ค. ํํฐ์ ๋ก์ง์ ๊ตฌํํ๋ฉด ๋๋ค.
destroy(): ํํฐ ์ข
๋ฃ ๋ฉ์๋, ์๋ธ๋ฆฟ ์ปจํ
์ด๋๊ฐ ์ข
๋ฃ๋ ๋ ํธ์ถ๋๋ค.
- ์๋ธ๋ฆฟ ํํฐ - ์์ฒญ ๋ก๊ทธ
@Slf4j
public class LogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("log filter init");
}
//HTTP ์์ฒญ์ด ์ฌ ๋๋ง๋ค ํธ์ถ
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();
String uuid = UUID.randomUUID().toString();
try {
log.info("REQUEST [{}][{}]", uuid, requestURI);
chain.doFilter(request, response);
} catch (Exception e) {
throw e;
} finally {
log.info("RESPONSE [{}][{}]", uuid, requestURI);
}
}
@Override
public void destroy() {
log.info("log filter destroy");
}
}
ServletRequest request ๋ HTTP ์์ฒญ์ด ์๋ ๊ฒฝ์ฐ๊น์ง ๊ณ ๋ คํด์ ๋ง๋ ์ธํฐํ์ด์ค์ด๋ค. HTTP๋ฅผ
์ฌ์ฉํ๋ฉด HttpServletRequest httpRequest = (HttpServletRequest) request; ์ ๊ฐ์ด ๋ค์ด ์ผ์คํ
ํ๋ฉด ๋๋ค.
String uuid = UUID.randomUUID().toString();
HTTP ์์ฒญ์ ๊ตฌ๋ถํ๊ธฐ ์ํด ์์ฒญ๋น ์์์ uuid ๋ฅผ ์์ฑํด๋๋ค.
chain.doFilter(request, response);
๋ค์ ํํฐ๊ฐ ์์ผ๋ฉด ํํฐ๋ฅผ ํธ์ถํ๊ณ , ํํฐ๊ฐ ์์ผ๋ฉด ์๋ธ๋ฆฟ์ ํธ์ถํ๋ค.
๋ง์ฝ ์ด ๋ก์ง์ ํธ์ถํ์ง ์์ผ๋ฉด ๋ค์ ๋จ๊ณ๋ก ์งํ๋์ง ์๋๋ค.
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean logFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean
= new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LogFilter());
//ํํฐ๋ ์ฒด์ธ์ผ๋ก ๋์ํ๋ค. ๋ฐ๋ผ์ ์์๊ฐ ํ์ํ๋ค. ๋ฎ์ ์๋ก ๋จผ์ ๋์ํ๋ค.
filterRegistrationBean.setOrder(1);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
- ์๋ธ๋ฆฟ ํํฐ - ์ธ์ฆ ์ฒดํฌ
@Slf4j
public class LoginCheckFilter implements Filter {
//ํ์ด์ค url ๋ฆฌ์คํธ
private static final String[] whitelist = {"/", "/members/add", "/login", "/logout","/css/*"};
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();
HttpServletResponse httpResponse = (HttpServletResponse) response;
try {
log.info("์ธ์ฆ ์ฒดํฌ ํํฐ ์์ {}", requestURI);
if (isLoginCheckPath(requestURI)) {
log.info("์ธ์ฆ ์ฒดํฌ ๋ก์ง ์คํ {}", requestURI);
HttpSession session = httpRequest.getSession(false);
if (session == null ||
session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
log.info("๋ฏธ์ธ์ฆ ์ฌ์ฉ์ ์์ฒญ {}", requestURI);
//๋ก๊ทธ์ธ์ผ๋ก redirect
httpResponse.sendRedirect("/login?redirectURL=" +
requestURI);
return; //์ฌ๊ธฐ๊ฐ ์ค์, ๋ฏธ์ธ์ฆ ์ฌ์ฉ์๋ ๋ค์์ผ๋ก ์งํํ์ง ์๊ณ ๋!
}
}
chain.doFilter(request, response);
} catch (Exception e) {
throw e; //์์ธ ๋ก๊น
๊ฐ๋ฅ ํ์ง๋ง, ํฐ์บฃ๊น์ง ์์ธ๋ฅผ ๋ณด๋ด์ฃผ์ด์ผ ํจ
} finally {
log.info("์ธ์ฆ ์ฒดํฌ ํํฐ ์ข
๋ฃ {}", requestURI);
}
}
/**
* ํ์ดํธ ๋ฆฌ์คํธ์ ๊ฒฝ์ฐ ์ธ์ฆ ์ฒดํฌX
*/
private boolean isLoginCheckPath(String requestURI) {
return !PatternMatchUtils.simpleMatch(whitelist, requestURI);
}
}
webconfig์ ๋ฑ๋ก
@Bean
public FilterRegistrationBean loginCheckFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LoginCheckFilter());
filterRegistrationBean.setOrder(2);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
-> ์๋ฌด๋ฆฌ ๋ค๋ฅธ ํ์ด์ง๋ฅด ๋ค์ด๊ฐ๋ คํด๋ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฐ์ ๋ค์ด๊ฐ ์ ์๋ค.
@PostMapping("/login")
public String loginV4(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult,
@RequestParam(defaultValue = "/") String redirectURL,
HttpServletRequest request) {
/*์๋ต*/
return "redirect:" + redirectURL;
}
์ด๋ ๊ฒ ํ๋ฉด ์๋ ๊ฐ๋ ค๋ ํ์ด์ง๋ก ๋์๊ฐ ์ ์๋ค.
- ์คํ๋ง ์ธํฐ์ ํฐ - ์๊ฐ
HTTP ์์ฒญ -> WAS -> ํํฐ -> ์๋ธ๋ฆฟ -> ์คํ๋ง ์ธํฐ์ ํฐ -> ์ปจํธ๋กค๋ฌ
์๋ธ๋ฆฟ ํํฐ์ ๋นํด ์ ๋ฐํ ์ค์ ์ด ๊ฐ๋ฅํ๋ค
preHandle
์ปจํธ๋กค๋ฌ ํธ์ถ ์ ์ ํธ์ถ๋๋ค. (๋ ์ ํํ๋ ํธ๋ค๋ฌ ์ด๋ํฐ ํธ์ถ ์ ์ ํธ์ถ๋๋ค.)
์์ธ ๋ฐ์ ์์๋ ์ปจํธ๋กค๋ฌ ํธ์ถ ์ ์ ํธ์ถ๋๋ค.
preHandle ์ ์๋ต๊ฐ์ด true ์ด๋ฉด ๋ค์์ผ๋ก ์งํํ๊ณ , false ์ด๋ฉด ๋๋ ์งํํ์ง ์๋๋ค. false
์ธ ๊ฒฝ์ฐ ๋๋จธ์ง ์ธํฐ์
ํฐ๋ ๋ฌผ๋ก ์ด๊ณ , ํธ๋ค๋ฌ ์ด๋ํฐ๋ ํธ์ถ๋์ง ์๋๋ค. ๊ทธ๋ฆผ์์ 1๋ฒ์์ ๋์ด
๋๋ฒ๋ฆฐ๋ค.
postHandle
์ปจํธ๋กค๋ฌ ํธ์ถ ํ์ ํธ์ถ๋๋ค. (๋ ์ ํํ๋ ํธ๋ค๋ฌ ์ด๋ํฐ ํธ์ถ ํ์ ํธ์ถ๋๋ค.)
์ปจํธ๋กค๋ฌ์์ ์์ธ๊ฐ ๋ฐ์ํ๋ฉด ํธ์ถ๋์ง ์๋๋ค.
afterCompletion
๋ทฐ๊ฐ ๋ ๋๋ง ๋ ์ดํ์ ํธ์ถ๋๋ค.
์์ธ๊ฐ ๋ฐ์ํด๋ ํธ์ถ๋๋ค.
- ์คํ๋ง ์ธํฐ์ ํฐ - ์์ฒญ ๋ก๊ทธ
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
public static final String LOG_ID = "logId";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
String uuid = UUID.randomUUID().toString();
request.setAttribute(LOG_ID, uuid);
//@RequestMapping: HandlerMethod
//์ ์ ๋ฆฌ์์ค: ResourceHttpRequestHandler
if (handler instanceof HandlerMethod) {
//ํธ์ถํ ์ปจํธ๋กค๋ฌ ๋ฉ์๋์๋ชจ๋ ์ ๋ณด๊ฐ ํฌํจ๋์ด ์๋ค.
HandlerMethod hm = (HandlerMethod) handler;
}
log.info("REQUEST [{}][{}][{}]", uuid, requestURI, handler);
return true; //false ์งํX
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle [{}]", modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
String requestURI = request.getRequestURI();
String logId = (String)request.getAttribute(LOG_ID);
log.info("RESPONSE [{}][{}]", logId, requestURI);
if (ex != null) {
log.error("afterCompletion error!!", ex);
}
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/*.ico", "/error");
}
registry.addInterceptor(new LogInterceptor()) : ์ธํฐ์
ํฐ๋ฅผ ๋ฑ๋กํ๋ค.
order(1) : ์ธํฐ์
ํฐ์ ํธ์ถ ์์๋ฅผ ์ง์ ํ๋ค. ๋ฎ์ ์๋ก ๋จผ์ ํธ์ถ๋๋ค.
addPathPatterns("/**") : ์ธํฐ์
ํฐ๋ฅผ ์ ์ฉํ URL ํจํด์ ์ง์ ํ๋ค.
excludePathPatterns("/css/**", "/*.ico", "/error") : ์ธํฐ์
ํฐ์์ ์ ์ธํ ํจํด์ ์ง์ ํ๋ค.
HandlerMethod
ํธ๋ค๋ฌ ์ ๋ณด๋ ์ด๋ค ํธ๋ค๋ฌ ๋งคํ์ ์ฌ์ฉํ๋๊ฐ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋ค. ์คํ๋ง์ ์ฌ์ฉํ๋ฉด ์ผ๋ฐ์ ์ผ๋ก
@Controller , @RequestMapping ์ ํ์ฉํ ํธ๋ค๋ฌ ๋งคํ์ ์ฌ์ฉํ๋๋ฐ, ์ด ๊ฒฝ์ฐ ํธ๋ค๋ฌ ์ ๋ณด๋ก
HandlerMethod ๊ฐ ๋์ด์จ๋ค.
ResourceHttpRequestHandler
@Controller ๊ฐ ์๋๋ผ /resources/static ์ ๊ฐ์ ์ ์ ๋ฆฌ์์ค๊ฐ ํธ์ถ ๋๋ ๊ฒฝ์ฐ
ResourceHttpRequestHandler ๊ฐ ํธ๋ค๋ฌ ์ ๋ณด๋ก ๋์ด์ค๊ธฐ ๋๋ฌธ์ ํ์
์ ๋ฐ๋ผ์ ์ฒ๋ฆฌ๊ฐ ํ์ํ๋ค.
postHandle, afterCompletion
์ข
๋ฃ ๋ก๊ทธ๋ฅผ postHandle ์ด ์๋๋ผ afterCompletion ์์ ์คํํ ์ด์ ๋, ์์ธ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ
postHandle ๊ฐ ํธ์ถ๋์ง ์๊ธฐ ๋๋ฌธ์ด๋ค. afterCompletion ์ ์์ธ๊ฐ ๋ฐ์ํด๋ ํธ์ถ ๋๋ ๊ฒ์ ๋ณด์ฅํ๋ค.
PathPattern (Spring Framework 6.0.4 API)
Compare this pattern with a supplied pattern: return -1,0,+1 if this pattern is more specific, the same or less specific than the supplied pattern.
docs.spring.io
- ์คํ๋ง ์ธํฐ์ ํฐ - ์ธ์ฆ ์ฒดํฌ
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
log.info("์ธ์ฆ ์ฒดํฌ ์ธํฐ์
ํฐ ์คํ {}", requestURI);
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
log.info("๋ฏธ์ธ์ฆ ์ฌ์ฉ์ ์์ฒญ");
//๋ก๊ทธ์ธ์ผ๋ก redirect
response.sendRedirect("/login?redirectURL=" + requestURI);
return false;
}
return true;
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/*.ico", "/error");
registry.addInterceptor(new LoginCheckInterceptor())
.order(2)
.addPathPatterns("/**") //๋ชจ๋ ๊ณณ์ ์ ์ฉ
.excludePathPatterns(
"/", "/members/add", "/login", "/logout",
"/css/**", "/*.ico", "/error" //ํ์ง๋ง ์ด๊ฑด ๋นผ
);
}
์ฝ๋๊ฐ ์๋ธ๋ฆฟ์ ๋นํด์ ๋งค์ฐ ๊ฐ๋จํ๋ค
ํน๋ณํ ์ด์ ๊ฐ ์๋ค๋ฉด ์ด๊ฒ์ ์ฌ์ฉํ์
- ArgumentResolver ํ์ฉ
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Login {
}
@Target(ElementType.PARAMETER) : ํ๋ผ๋ฏธํฐ์๋ง ์ฌ์ฉ
@Retention(RetentionPolicy.RUNTIME) : ๋ฆฌํ๋ ์
๋ฑ์ ํ์ฉํ ์ ์๋๋ก ๋ฐํ์๊น์ง ์ ๋
ธํ
์ด์
์ ๋ณด๊ฐ ๋จ์์์
@GetMapping("/")
public String homeLoginV3ArgumentResolver(@Login Member loginMember, Model model) {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new LoginMemberArgumentResolver());
}
@Slf4j
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
log.info("supportsParameter ์คํ");
boolean hasLoginAnnotation =
parameter.hasParameterAnnotation(Login.class);
boolean hasMemberType =
Member.class.isAssignableFrom(parameter.getParameterType());
return hasLoginAnnotation && hasMemberType;
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
log.info("resolveArgument ์คํ");
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
HttpSession session = request.getSession(false);
if (session == null) {
return null;
}
return session.getAttribute(SessionConst.LOGIN_MEMBER);
}
}
๊ฒฐ๊ณผ๋ ๋์ผํ๊ฒ, ์ ๋ ธํ ์ด์ ์ผ๋ก ๊ฐ๋จํ๊ฒ ํด๊ฒฐํ ์ ์์