[Spring] 로그인 처리 1 - 쿠키, 세션

윤짱·2026년 1월 28일

Spring

목록 보기
15/18
post-thumbnail

강의 정보 : 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술

로그인 처리 - 쿠키

서버에서 로그인에 성공하면 HTTP 응답에 쿠키를 담아서 브라우저에 전달하면 브라우저는 앞으로 해당 쿠키를 지속해서 보내준다.


쿠키에는 영속 쿠키와 세션 쿠키가 있다.

  • 영속 쿠키 : 만료 날짜 입력 시 해당 날짜까지 유지
  • 세션 쿠키 : 만료 날짜 생략 시 브라우저 종료까지만 유지

LoginController - login

@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:/"
}    

로그인에 성공하면 쿠키를 생성하고 HttpServletResponse 에 담는다. 쿠키 이름은 memberId 이고, 값은 회원의 id 를 담아둔다. 웹 브라우저는 종료 전까지 회원의 id 를 서버에 계속 보내줄 것이다.

LoginController - logout

@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);
}    

로그아웃도 응답 쿠키를 생성하며 해당 쿠키는 즉시 종료된다.

쿠키와 보안 문제

보안 문제

  • 쿠키 값은 임의로 변경할 수 있다.
    - 클라이언트가 쿠키를 강제로 변경하면 다른 사용자가 된다.
    - 실제 웹브라우저 개발자모드 Application Cookie 변경으로 확인
    - Cookie: memberId=1 Cookie: memberId=2 (다른 사용자의 이름이 보임)

  • 쿠키에 보관된 정보는 훔쳐갈 수 있다.
    - 만약 쿠키에 개인정보나, 신용카드 정보가 있다면?
    - 이 정보가 웹 브라우저에도 보관되고, 네트워크 요청마다 계속 클라이언트에서 서버로 전달된다.
    - 쿠키의 정보가 나의 로컬 PC에서 털릴 수도 있고, 네트워크 전송 구간에서 털릴 수도 있다.

  • 해커가 쿠키를 한번 훔쳐가면 평생 사용할 수 있다.
    - 해커가 쿠키를 훔쳐가서 그 쿠키로 악의적인 요청을 계속 시도할 수 있다.

대안

  • 쿠키에 중요한 값을 노출하지 않고, 사용자 별로 예측 불가능한 임의의 토큰(랜덤 값)을 노출하고, 서버에서 토큰과 사용자 id를 매핑해서 인식한다. 그리고 서버에서 토큰을 관리한다.
  • 토큰은 해커가 임의의 값을 넣어도 찾을 수 없도록 예상 불가능 해야 한다.
  • 해커가 토큰을 털어가도 시간이 지나면 사용할 수 없도록 서버에서 해당 토큰의 만료시간을 짧게(예: 30분) 유지한다. 또는 해킹이 의심되는 경우 서버에서 해당 토큰을 강제로 제거하면 된다.

로그인 처리 - 세션

보안 문제를 해결하기 위해서 중요한 정보는 결국 서버에 저장해야 하고 클라이언트와 서버는 추정 불가능한 임의의 식별자 값으로 연결해야 하는데, 이처럼 서버에 중요한 정보를 보관하고 연결을 유지하는 방법을 세션이라 한다.




세션을 사용해서 서버에서 중요한 정보를 관리하게 되면 다음과 같은 보안 문제를 해결할 수 있다.

  • 쿠키 값 변조 가능성 : 예상 불가능한 복잡한 세션 ID 사용
  • 쿠키에 보관하는 정보는 클라이언트 해킹 시 털릴 수 있다 : 세션 ID에는 중요한 정보가 없음
  • 쿠키 탈취 후 사용 : 토큰을 가져가도 세션의 만료 시간을 짧게 유지하거나 강제 제거하면 된다.

세션 관리

package hello.login.web.session;
import org.springframework.stereotype.Component;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
* 세션 관리
*/
@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);
	}

}
profile
바르게 살자...

0개의 댓글