이해를 위한 쿠키, 세션 구현. 실제로는 이미 다 구현이 되어있음.
쿠키 만들기
public class MyComtroller {
public String login(... , HttpServletResponse response) {
// 쿠키에 시간정보를 안주면 세션쿠키(브라우저 종료 시 모두 삭제)
Cookie idCookie = new Cookie("memberId", String.valueOf(loginMember.getId()));
response.addCookie(idCookie);
}
}
HttpServletResponse 객체를 추가하고 new Cookie 로 쿠키를 만들어서 response 객체에 추가
쿠키 이름 : memberId / 값 : 회원의 id
Response Header에 Set-Cookie: memberId=1 이 추가되어 응답을 보냄
이걸 받은 웹브라우저는 이후 요청 시 Reqiest Header에 Cookie:memberId=1 을 종료 전까지 서버에 계속 보내준다.
쿠키 꺼내기
public class HomeController {
public String homeLogin(@CookieValue(name = "memberId", required = false) Long memberId) {
}
}
HttpServletRequest 객체를 통해서 받을 수도 있고 @CookieValue 어노테이션을 통해서 받을 수도 있다.
쿠키 삭제하기
public class logoutController {
public String logout(HttpServletResponse response) {
Cookie cookie = new Cookie("memberId", null);
cookie.setMaxAge(0); // 쿠키가 유지되는 시간(초)을 0으로 세팅
response.addCookie(cookie);
}
}
MaxAge를 0으로 세팅하고 보내면 브라우저는 쿠키를 받자마자 만료된다.
쿠키의 보안문제
- 쿠키 값은 F12 개발자모드에서 임의로 바꿔서 보낼 수 있다.
- 쿠키에 보관된 정보는 훔쳐갈 수 있다. 쿠키는 웹 브라우저에도 보관되고, 요청마다 네트워크를 통해 서버로 전달된다. 내 pc가 해킹될 수도 있고, 네트워크 해킹을 당할 수도 있다.
대안
- 쿠키에 중요한 정보를 넣지 않고, 사용자 별로 예측 불가능한 임의의 토큰(랜덤 값)을 만든다. 서버에서는 이 토큰과 사용자id를 매핑해서 관리한다.
- 해커가 쿠키를 해킹해서 토큰을 알게되어도 토큰의 만료시간을 짧게 주어(ex 30분) 시간이 지나면 사용할 수 없게 한다.
세션 생성, 조회, 만료
public class SessionManager {
private Map<String, Object> sessionStore = new ConcurrentHashMap<>(); // 동시성 문제 해결을 위해 ConcurrentHashMap 사용
// 세션 생성
public void createSession(Object value, HttpServletResponse response) {
String sessionId = UUID.randomUUID().toString(); // 자바가 기본 제공하는 기능. Universally Unique IDentifier
sessionStore.put(sessionId, value);
// 쿠키 생성
Cookie mySessionCookie = new Cookie("mySessionId", sessionId); // 쿠키 이름이 mySessionId
response.addCookie(mySessionCookie);
}
// 세션 조회
public Object getSession(HttpServletRequest request) {
Cookie[] cookies = request.getCookie();
if (cookies == null) return null;
for (Cookie cookie : cookies) {
if (cookie.getName.equals("mySessionId")) {
return sessionStore.get(cookie.getValue()); // map에서 value 가져오기
}
}
// 못찾으면 리턴 null
return null;
// stream 사용하면
return Arrays.stream(cookies)
.filter(cookie -> cookie.getName().equals("mySessionId")
.findAny()
.orElse(null);
}
// 세션 만료
public void expire(HttpServletRequest request) {
Cookie sessionCookie;
Cookie[] cookies = request.getCookie();
if (cookies == null) sessionCookie = null;
sessionCookie = Arrays.stream(cookies)
.filter(cookie -> cookie.getName().equals("mySessionId")
.findAny()
.orElse(null);
if (sessionCookie != null) sessionStore.remove(sessionCookie.getValue()); // map에서 지우기
}
}
실무 사용 @SessionAttribute
public class Controller {
public void login(@SessionAttribute(name = "loginMember", required = false) Member member, Model model) {
// 로그인 세션이 없는 사용자면 세션을 만들면 안되기 때문에 required = false
// @SessionAttribute 어노테이션을 통해
// 쿠키를 찾아서 "loginMember"에 해당하는 쿠키가 있으면
// 서버에 저장되어 있는 저장소에서 객체를 찾아 member에 넣어줌.
if (member == null) return "home";
return "loginHome";
}
}
세션의 종료 시점
- 사용자가 로그아웃을 누르면 서버에 저장된 세션 정보를 삭제한다. 하지만 사용자가 로그아웃을 누르지 않고 그냥 브라우저 창만 꺼버리면 서버 입장에서는 알 수가 없다. 세션은 서버의 메모리에 저장되므로 사용자의 정보를 무한정 저장하고 있을 순 없다. 그래서 타임아웃을 설정한다.
스프링 부트로 글로벌 설정
application.properties
server.servlet.session.timeout=60 : 60초, 기본은 1800(30분)
글로벌 설정은 분 단위로 설정해야 한다. 60(1분), 120(2분), ...
특정 세션 단위로 시간 설정
session.setMaxInactiveInterval(60); // 60초
@Slf4j
@RestController
public class SessionInfoController {
@GetMapping("/session-info")
public String sessionInfo(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return "세션이 없습니다.";
}
session.setMaxInactiveInterval(60); // 60초
//세션 데이터 출력
session.getAttributeNames().asIterator()
.forEachRemaining(name -> log.info("session name={}, value={}", name, session.getAttribute(name)));
log.info("sessionId={}", session.getId());
log.info("maxInactiveInterval={}", session.getMaxInactiveInterval()); // 세션의 유효 시간, 예) 60초, (1분)
log.info("creationTime={}", new Date(session.getCreationTime()));
log.info("lastAccessedTime={}", new Date(session.getLastAccessedTime())); // 세션과 연결된 사용자가 마지막으로 서버에 요청한 시간
log.info("isNew={}", session.isNew());
return "세션 출력";
}
}
로그인 시점부터 카운트하는게 아니라, 마지막 요청으로부터 새롭게 갱신해서 카운트한다.
시간이 초과되면 WAS 내부에서 해당 세션을 제거한다.
'SpringBoot' 카테고리의 다른 글
@Transactional 롤백 (0) | 2024.07.22 |
---|---|
[SpringBoot] 필터, 인터셉터 호출 흐름, 적용 방법 (0) | 2024.05.20 |
bindingResult 가 쌓이는 과정, @Valid (0) | 2024.05.16 |
Spring MVC 구조와 동작 원리 (0) | 2024.05.09 |
[SpringBoot] 웹서버, WAS, JSP, Servlet, MVC 패턴, MVC 프레임워크, Front Controller (0) | 2024.03.18 |
댓글