✔️ 로그인 및 쿠키 생성
//로그인 성공 시, 쿠키를 생성하고 저장한다.
@PostMapping("/login")
public String login(@Validated @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:/";
}
✔️ 기본 홈 화면 및 로그인 한 사용자에 대한 로그인 홈 화면 처리
@GetMapping("/")
public String homeLogin(@CookieValue(name = "memberId", required = false) Long memberId, Model model) {
//로그인 쿠키가 없으면 home으로 보냄
if (memberId == null) {
return "home";
}
//로그인 쿠키가 있어도 회원이 아니면 home으로 보냄
Member loginMebmer = memberRepository.findById(memberId);
if (loginMebmer == null) {
return "home";
}
//로그인 쿠키가 있는 사용자는 로그인 사용자 전용 홈 화면인 loginHome으로 보낸다.
//추가로 홈 화면에서 회원 관련 정보(member)도 출력해야 하므로 모델에 담아서 전달한다.
model.addAttribute("member", loginMebmer);
return "loginHome";
}
✔️ 로그아웃 처리
//LoginController
@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);
}
✔️ 목표
✔️ 세션 구현 시, 아래의 중요 3가지 기능을 구현한다.
@Component
public class SessionManager {
public static final String SESSION_COOKIE_NAME = "mySessionId";
private Map<String, Object> sessionStore = new ConcurrentHashMap<>();
/**
* 세션 생성
* @param value {@link Object}
* @param response {@link HttpServletResponse}
*/
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);
}
/**
* 세션 조회
* @param request {@link HttpServletRequest}
* @return Object {@link Object}
*/
public Object getSession(HttpServletRequest request) {
Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
if (sessionCookie == null) {
return null;
}
return sessionStore.get(sessionCookie.getValue());
}
/**
* 세션 만료
* @param request {@link HttpServletRequest}
*/
public void expire(HttpServletRequest request) {
Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
if (sessionCookie != null) {
sessionStore.remove(sessionCookie.getValue());
}
}
/**
* 쿠키 조회
* @param request {@link HttpServletRequest}
* @param cookieName {@link String}
* @return
*/
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);
}
}
✔️ 구현한 세션 테스트
public class SessionManagerTest {
SessionManager sessionManager = new SessionManager();
@Test
void sessionTest() {
//세션 생성
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();
}
}
✔️ 서블릿에서는 세션을 위해 HttpSession
이라는 기능을 제공하는데, 위에서 직접 만든 SessionManager와 같은 방식으로 동작한다.
✔️ 서블릿을 통해 HttpSession을 생성하면 아래와 같은 쿠키를 생성한다.
JSESSIONID
@PostMapping("/login")
public String loginV3(@Validated @ModelAttribute LoginForm form, BindingResult bindingResult,
HttpServletRequest request) {
...
//세션이 있으면 기존 세션 반환, 없으면 신규 세션 생성
HttpSession session = request.getSession();
//세션에 로그인 회원 정보 보관
session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
//로그인 성공 처리
return "redirect:/";
}
request.getSession(true)
request.getSession(false)
session.setAttribute(세션 명, 값)
, 하나의 세션에 여러 값을 저장할 수 있다.✔️ 세션 제거
@PostMapping("/logout")
public String logoutV3(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
//세션을 제거한다.
session.invalidate();
}
return "redirect:/";
}
✔️ 세션을 통한 Home 처리
@GetMapping("/")
public String homeLoginV3(HttpServletRequest request, Model model) {
//세션이 없으면 home
HttpSession session = request.getSession(false);
if (session == null) {
return "home";
}
Member loginMember = (Member) session.getAttribute(SessionConst.LOGIN_MEMBER);
//세션에 회원 데이터가 없으면 home
if (loginMember == null) {
return "home";
}
//세션이 유지되면 로그인으로 이동
model.addAttribute("member", loginMember);
return "loginHome";
}
session.getAttribute(SessionConst.LOGIN_MEMBER);
: 로그인 시점에 세션에 보관된 회원 객체를 찾는다.✔️ 서블릿에서는 세션을 더 편리하게 사용하도록 @SessionAttribute
기능을 지원한다.
//HomeController
@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(name = SessionConst.LOGIN_MEMBER, required = false) Member loginmember
와 같이 사용하며 해당 기능은 세션을 새로 생성하지 않는다.http://localhost:8080/;jsessionid=F59911518B921DF62D09F0DF8F83F872
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 "세션 출력";
}
}
sessionId
: 세션 Id, JSESSIONID
값이다.maxInactiveInterval
: 세션의 유효 시간, 예) 1800초creationTiem
: 세션 생성일시lastAccessedTime
: 세션과 연결된 사용자가 최근에 서버에 접근한 시간, 클라이언트에서 서버로 sessionId(JSESSIONID)를 요청한 경우 갱신isNew
: 새로 생성된 세션인지, 아니면 이미 과거에 만들어졌고, 클라이언트 서버로 sessionId(JSESSIONID)를 요청해서 조회된 세션인지 여부session.invalidate()
가 호출되는 경우에 삭제된다.✔️ 세션의 종료 시점
HttpSession
기능도 위 방식을 사용한다.✔️ 세션 타임아웃 설정
#application.yaml
server:
servlet:
session:
timeout: 60
session.setMaxInactiveInterval(1800); //1800초
✔️ 세션 타임아웃 발생
session.getLastAccessedTime()
: 최근 세션 접근 시간