로그인 처리 - 쿠키 사용
웹에서 사용자의 로그인 상태를 관리하려면 상태를 유지할 수 있는 방법이 필요하다. HTTP 프로토콜은 상태가 없는 프로토콜이기 때문에 쿠키를 사용하여 상태 정보를 저장하고 관리할 수 있다.
쿠키의 기본 개념쿠키는 작은 텍스트 파일로, 사용자의 브라우저에 저장되는 정보이다. 서버는 HTTP 응답 헤더를 통해 쿠키를 클라리언트에 전달하며, 클라이언트는 이후 요청에서 해당 쿠키를 포함하여 서버에 다시 전송한다.
쿠키에는 크게 두 가지 유형이 있다.
- 영속 쿠키 : 만료 날짜가 지정된 쿠키로, 해당 날짜까지 유지된다.
- 세션 쿠키 : 만료 날짜가 지정되지 않는 쿠키로, 브라우저 종료시 삭제된다.
로그인 구현
로그인에 성공하면 서버는 해당 사용자의 고유 ID나 다른 식별 정보를 쿠키로 생성하여 클라이언트에게 전달한다.
@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";
}
// 로그인 성공 시 쿠키 생성
Cookie idCookie = new Cookie("memberId", String.valueOf(loginMember.getId()));
response.addCookie(idCookie);
return "redirect:/";
}
위 로그인 컨트롤러에서 로그인 성공 시, 세션 쿠키를 생성하여 클라이언트에 전달한다.
쿠키와 보안 문제
쿠키는 웹 애플리케이션에서 상태를 유지하기 위해 자주 사용되는 도구 중 하나이다. 로그인 상태 유지, 사용자의 환경 설정 정보 등 다양한 경우에 활용된다. 그러나 쿠키를 사용할 때 주의해야 할 보안 문제가 있다.
보안 문제
쿠키 값의 변경 가능성
- 쿠키의 값은 클라이언트 측에서 임의로 변경할 수 있다.
- 사용자는 웹 브라우저의 개발 도구를 통해 쿠키의 값을 수정할 수 있기 때문에, 잘못 사용될 경우 다른 사용자의 권한으로 동작할 위험이 있다.
쿠키 값의 노출 위험
- 쿠키에 저정된 정보는 웹 브라우저에 보관되며, HTTP 요청과 응답을 통해 클라이언트와 서버 간에 전달된다.개인 정보나 신용카드 정보와 같은 중요한 정보를 쿠키에 저장한다면, 이러한 정보는 로컬PC에서나 네트워크 전송 중에 탈취될 수 있다.
영구적인 쿠키
- 위험쿠키의 만료 기간을 설정하지 않으면 해당 쿠키는 사용자의 웹 브라우저가 종료될 때까지 유지된다. 이런 쿠키를 해커가 훔쳐간다면, 그 쿠키를 통해 악의적인 요청을 계속 시도할 수 있다.
대안 : 보안 강화
임의 토큰 사용
- 쿠키에 중요한 값을 직접 저장하는 대신, 각 사용자에 대해 예측이 불가능한 임의의 토근을 생성하여 이를 쿠키에 저장한다.서버 측에서 이 토근과 사용자의 ID를 매핑하여 관리한다. 이렇게 하면 실제 중요한 사용자 정보는 노출되지 않는다.
토큰의 보안성 강화
- 생성된 토큰은 예측이 불가능하도록 복잡한 알고리즘을 통해 생성되어야 한다.만약 해커가 토큰을 탈취한다 해도, 해당 토큰의 만료 시간을 짧게 설정(예: 30분)하여 그 효력을 제한할 수 있다.
- 서버 측에서는 의심스러운 활동이 감지될 경우 해당 토큰을 강제로 만료시킬 수 있다.
쿠키의 보안 대안 - 세션 동작 방식
세션은 웹 애플리케이션에서 사용자의 상태를 유지하는 중요한 도구이다. 여기에는 로그인 정보, 사용자의 환경 설정, 장바구니 상태 등 다양한 정보가 포함될 수 있다.
세션을 통한 로그인 과정
1. 로그인 과정
- 사용자는 'loginID' 와 'password' 정보를 서버에 전달한다.서버는 이 정보를 기반을 사용자 인증을 진행한다.
2. 세션 생성
- 사용자가 성공적으로 인정되면, 서버는 세션 ID를 생성한다. 이 세션 ID는 예측하기 힘든 값이어야 한다. (예: UUID)
- 생성된 세션 ID와 함께 필요한 사용자 정보(memberA)를 서버의 세션 저장소에 보관한다.
3. 세션 ID의 쿠키 전달
- 서버는 클라이언트에게 생성된 세션 ID를 쿠키 형태로 전달한다. 예를 들어, 'Cookie : mySessionId=zz0101xx' 같은 형태로 전달될 수 있다.
- 클라이언트는 이 쿠키를 자신의 저장소에 보관하게 된다.
4. 세션의 ID 사용
- 클라이언트가 서버에 요청할 때 마다 저장된 세션 ID 쿠키(MySessionId)를 함께 전달한다.
- 서버는 이 쿠키를 통해 세션 저장소를 조회하고, 해당 세션 ID에 연결된 사용자 정보를 찾아서 사용한다.
보안의 중요성
세션을 통해 서버에서 중요한 정보를 관리하여 다음과 같은 보안 문제를 해결할 수 있다.
- 쿠키 값의 변조 방지 : 복잡하고 예측 불가능한 세션 ID를 사용하여, 클라이언트에서 쿠키 값을 임의로 변조하는 것을 방지한다.
- 정보의 보안 : 중요한 사용자 정보는 서버의 세션 저장소에만 보관되며, 클라이언트에는 오직 세션 ID만 전달된다. 이렇게 함으로써 클라이언트에서 정보가 노출될 위험을 줄일 수 있다.
- 세션 탈취의 대응 : 세션 ID가 탈취당해도, 서버에서는 해당 세션의 만료시간을 짧게 설정(30분)하여 사용할 수 있는 기간을 제한한다. 추가로 의심스러운 활동이 감지되면, 서버는 해당 세션을 강제로 종료시킬 수 있다.
웹 애플리케이션과 세션
웹 애플리케이션에서 사용자의 상태를 유지하기 위해 세션을 사용한다. 서블릿에서는 'HttpSession' 이라는 객체를 통해 세션을 관리한다.
HttpSession이란?
서블릿에서 제공하는 'HttpSession'은 세션을 관리하는 객체이다.이 객체를 사용해 세션을 생성하면, 이름이 'JSESSIONID' 이라는 랜덤 값 쿠키가 만들어진다.
HttpSession 사용법
public class SessionConst {
public static final String LOGIN_MEMBER = "loginMember";
}
@PostMapping("login")
public String loginV3(@Validated @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:/";
}
세션 생성 및 조회
- request.getSession(ture) : 세션이 있으면 기본 세션 반환, 없으면 새 세션 생성
- request.getSession(false) : 세션이 있으면 기존 세션 반환, 없으면 null 반환.
- request.getSession() == request.getSession(ture)
세션에 데이터 저장
- 세션에 데이터를 저장할 때는 'session.setAttribute(key, value)' 메서드를 사용한다.
@PostMapping("/logout")
public String logoutV3(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
return "redirect:/";
}
세션에서 데이터 제거
- 로그아웃이나 다른 상황에서 세션을 제거하고 싶을 때, 'session.invalidate()' 메서드를 사용한다.
Spring에서의 세션사용
@SessionAttribute
@GetMapping("/")
public String homeLoginV3Spring(
@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember, Model model) {
//세션에 회원 데이터가 없으면 home
if (loginMember == null) {
return "home";
}
//세션이 유지되면 로그인으로 이동
model.addAttribute("member", loginMember);
return "loginHome";
}
- Spring에서는 세션을 더욱 간편하게 사용할 수 있게 해주는 '@SessionAttribute' 어노테이션을 제공한다.
- 이 어노테이션을 사용하면 메서드 파라미터에서 바로 세션에 저장된 데이터를 받아올 수 있다.
@SessionAttribute(name = "loginMember", required = false) Member loginMember
- 이미 로그인 된 사용자를 찾을 때는 다음과 같이 사용해, 세션 생성을 막아준다.
TrackingModes
- 웹 브라우저가 쿠키를 지원하지 않을 때, 'jsessionid'를 URL에 포함시켜 세션을 유지하는 방법이다. 이것은 URL 전달 방식이다.
- 서버 입장에서 웹 브라우저가 쿠키를 지원하는지 하지 않는지 판단하지 못하므로, 쿠키 값도 전달하고, URL에 Jsession도 함께 전달한다.
- URL 전달 방식을 끄고 항상 쿠키를 통해서만 세션을 유지하고 싶다면, 'application.properties에 'server.servlet.session.tracking-modes=cookie' 설정을 추가하면 URL에 'jsessionid' 가 노출되지 않는다.
세션 정보와 타임아웃 설정
세션의 특징
1. 비연결성의 문제 : HTTP는 ConnectionLess 특성 때문에 사용자가 웹 브라우저를 종료했는지 판단하기 어렵다.
2. 메모리 사용 : 세션은 주로 메모리에 저장되므로 많은 사용자가 동시에 접속하면 많은 메모리가 사용될 수 있다
3. 보안 문제 : 세션과 관련된 쿠키(JSESSIONID)가 탈취되면 악의적인 요청이 가능해진다.
세션의 종료 시점
@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={}", session.getAttributeNames()));
log.info("sessionId={}", session.getId());
log.info("getMaxInactiveInterval={}", 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초)
- creationTime : 세션 생성일시
- lastAccessedTime : 세션과 연결된 사용자가 최근에 서버에 접근한 시간, 클라이언트에서 서버로 sessionId(JSESSIONID)를 요청한 경우에 갱신된다.
- isNew : 새로 생성된 세션인지, 아니면 이미 생성된 세션인지 'sessionId(JSESSIONID)' 체크
세션 타임아웃 설정
- 스프링 부트의 글로벌 설정 : 'application.properties' 에서 'server.servlet.session.timeout' 값을 설정하여 세션 타임아웃 시간을 조절할 수 있다.
- 개별 세션 설정 : 'session.setMaxInactiveInterval()' 를 사용하여 개별 세션의 타임아웃 시간을 설정할 수 있다.
세션 주의점
- 세션에는 최소한의 데이터만 저장해야 한다. 많은 데이터를 저장하면 메모리 부족으로 인해 서버에 문제가 발생할 수 있다.
- 세션의 유지 시간을 너무 길게 설정하면 메모리 사용이 계속 누적될 수 있다. 따라서 적절한 시간을 설정하는 것이 중요하다.
출처 : 인프런 - 🔗 스프링 MVC 2 - 백엔드 개발 활용 기술by 우아한형제 김영한님
'Spring > Spring MVC - 활용' 카테고리의 다른 글
[Spring] ExceptionHandler , ErrorPage(예외 처리, 오류 페이지) (0) | 2023.09.13 |
---|---|
[Spring] Servlet Filter, Spring Interceptor(서블릿 필터, 스프링 인터셉터) (0) | 2023.09.11 |
[Spirng] Bean Vaildation(빈 밸리데이션) - 검증 (0) | 2023.09.07 |
[Spring] Validation(밸리데이션) - 검증 (0) | 2023.09.06 |
메시지, 국제화 (0) | 2023.09.03 |