본문 바로가기
SpringBoot

쿠키, 세션 구현, 동작 원리

by 오렌지마끼야또 2024. 5. 17.
728x90
반응형

 

 

 

 

 

이해를 위한 쿠키, 세션 구현. 실제로는 이미 다 구현이 되어있음.

 

 

 

 

쿠키 만들기

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 을 종료 전까지 서버에 계속 보내준다.

로그인 요청에 대한 response의 set cookie
이후 request의 cookie

 

 

 

쿠키 꺼내기

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 내부에서 해당 세션을 제거한다.

 

 

728x90
반응형

댓글