도메인이 가장 중요하다.
도메인 → 화면, UI, 기술 인프라 영역은 제외한 시스템이 구현해야 하는 핵심 비즈니스 업무 영역을 말한다.
→ 의존 관계 설정이 중요하다.
WEB 패키지는 domain 패키지에 의존하지만, domain 패키지는 WEB 패키지에 의존하면 안된다.
유지 보수 및 추가 개발 시 더 복잡해진다.
애노테이션 @RequiredArgsConstructor
@RequiredArgsConstructor는 Lombok 라이브러리에서 제공하는 애노테이션입니다. 이 애노테이션의 주요 기능은 다음과 같습니다:
애노테이션 @PostConstruct
스프링 프레임워크에서 제공하는 기능으로, 해당 빈의 의존관계가 모두 주입된 후에 초기화 용도로 호출되는 메서드를 지정하는 데 사용됩니다. 주요 특징은 다음과 같습니다:
예를 들어, 다음과 같이 사용할 수 있습니다:
@PostConstruct
public void init() {
itemRepository.save(new Item("testA", 10000, 10));
itemRepository.save(new Item("testB", 20000, 20));
}
이 메서드는 빈이 완전히 초기화된 후 자동으로 호출되어, 초기 데이터를 저장하는 역할을 수행합니다.
LoginController - login()
@Slf4j
@Controller
@RequiredArgsConstructor
public class LoginController {
private final LoginService loginService;
private final SessionManager sessionManager;
@GetMapping("/login")
public String loginForm(@ModelAttribute("loginForm") LoginForm form) {
return "login/loginForm";
}
// @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());
if (loginMember == null) {
bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다");
return "login/loginForm";
}
/**
* 로그인 처리 로직 (쿠키)
* 로그인 처리 된 쿠키를 보내주고
* 이후 로그인 이후 접근할 시 쿠키를 함께 보내준다.
* 영속 쿠키 vs 세션 쿠키
* 세션 쿠키 -> 브라우저 종료시 까지만 유지한다 -> 이걸 사용한다.
*/
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:/";
}
}
쿠키 생성 로직
Cookie idCookie = new Cookie("memberId", String.valueOf(loginMember.getId()));
response.addCookie(idCookie);
로그인에 성공하면, 쿠키를 생성하고, HttpServletResponse 에 담는다.
쿠키 이름은 memberId 이고, 값은 회원의 id를 담는다. 그리고 웹 브라우저는 종료 전까지 회원의 id를 서버에 계속 보내준다.
→ 홈에서는 로그인 된 User는 쿠키를 들고 있을 것이니, 쿠키 값이 있는지 확인하고
null 이면 → home
null 이 아니면 → loginHome
보내주면 된다.
HomeController - homeLogin()
@Slf4j
@Controller
@RequiredArgsConstructor
public class HomeController {
private final MemberRepository memberRepository;
private final SessionManager sessionManager;
@GetMapping("/")
public String homeLogin(@CookieValue(name = "memberId", required = false) Long memberId, Model model) {
if (memberId == null) {
return "home";
} else {
// 로그인 성공 시
Member loginMember = memberRepository.findById(memberId);
if (loginMember == null) {
return "home";
}
model.addAttribute("member", loginMember);
return "loginHome";
}
}
}
HomeController - logout()
@PostMapping("/logout")
public String logout(HttpServletResponse response) {
expireCookie(response, "memberId");
return "redirect:/";
}
private static void expireCookie(HttpServletResponse response, String cookieName) {
Cookie cookie = new Cookie(cookieName, null);
cookie.setMaxAge(0);
response.addCookie(cookie);
}
로그아웃 시, memberId 라는 쿠키를 새로 만들고, 해당 쿠키의 종료 날짜를 0으로 설정하고 response 에 만든 쿠키를 추가한다. 그리고 이를 redirect:/ 홈 화면으로 리다이렉트 하며 해당 쿠키를 전송한다.
→ 브라우저에서 쿠키의 값은 임의로 바꿀 수 있고, 다른 사람의 쿠키 값인 “memberId” 값을 알면, 내가 다른 사람으로 로그인도 가능하다.
대안



@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 Cookie findCookie(HttpServletRequest request, String cookieName) {
Cookie[] cookies = request.getCookies();
if (cookies == null) {
return null;
}
return Arrays.stream(cookies).filter(cookie -> cookie.getName().equals(cookieName)).findAny().orElse(null);
}
/**
* 세션 만료
*/
public void expire(HttpServletRequest request) {
Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
if (sessionCookie != null) {
sessionStore.remove(sessionCookie.getValue());
}
}
}
ConcurrentHashMap<> 사용함.LoginController - loginV2()
@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());
if (loginMember == null) {
bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다");
return "login/loginForm";
}
/**
* 로그인 처리 로직 (쿠키)
* 로그인 처리 된 쿠키를 보내주고
* 이후 로그인 이후 접근할 시 쿠키를 함께 보내준다.
* 영속 쿠키 vs 세션 쿠키
* 세션 쿠키 -> 브라우저 종료시 까지만 유지한다 -> 이걸 사용한다.
*/
/**
*
* 세션 관리자를 통해 세션을 생성하고, 회원 데이터를 보관
*/
sessionManager.createSession(loginMember, response);
return "redirect:/";
}
LoginService - login()
@Service
@RequiredArgsConstructor
public class LoginService {
private final MemberRepository memberRepository;
/**
* return 이 null 이면 로그인 실패
*/
public Member login(String loginId, String password) {
// Optional<Member> findMemberOptional = memberRepository.findByLoginId(loginId);
// Member member = findMemberOptional.get();
// if (member.getPassword().equals(password)) {
// return member;
// } else {
// return null;
// }
// Optional<Member> findMemberLoginId = memberRepository.findByLoginId(loginId);
// return findMemberLoginId.filter(member -> member.getPassword().equals(password)).orElse(null);
return memberRepository.findByLoginId(loginId).filter(member -> member.getPassword().equals(password)).orElse(null);
}
}
LoginController - logoutV2()
@PostMapping("/logout")
public String logoutV2(HttpServletRequest request) {
sessionManager.expire(request);
return "redirect:/";
}
HomeController - homeLoginV2()
@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";
}
우리가 구현한 세션은 서블릿에서 HttpSession 이라는 기능으로 제공한다.
이를 적용해보자.
SessionConst
public interface SessionConst {
String LOGIN_MEMBER = "loginMember";
}
LoginController - loginV3()
@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());
if (loginMember == null) {
bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다");
return "login/loginForm";
}
HttpSession session = request.getSession();
session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
return "redirect:/";
}
LoginController - logoutV3()
@PostMapping("/logout")
public String logoutV3(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
return "redirect:/";
}
session.invalidate(); 를 사용하면, 해당 세션을 제거할 수 있다.
스프링에서 세션을 더 편리하게 사용할 수 있도록 제공하는 애노테이션.
HomeController - homeLoginSpring()
@GetMapping("/")
public String homeLoginSpring(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember, Model model) {
if (loginMember == null) {
return "home";
}
model.addAttribute("member", loginMember);
return "loginHome";
}
→ 단, 세션을 생성하는 기능은 아니다. 따라서, 쿠키 값을 통해 세션에서 value 를 찾아올 때 유용함.
→ 실행 결과 : 웹 브라우저가 처음 접속하여 쿠키를 지원하지 않을 때(쿠키 값이 없을 때) URL 을 통해 세션을 유지하기 위해, URL 에 jsessionid= 값이 붙어 나온다. 이를 끄고 항상 쿠키만 통해서 세션을 유지하고 싶다면,
application.properties
server.servlet.session.tracking-modes=cookie
추가하자.