반응형

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 대시보드 - 인프런 | 강의 (inflearn.com)

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

쿠키정보

항상 서버에 전송됨

- 네트워크 트래픽 추가 유발

- 최소한의 정보만 사용 (세션 id, 인증 토큰)

- 보안에 민감한 데이터는 저장하면 안됨

 

쿠키 생명주기 (expires, max-age)

세션 쿠키: 만료 날짜 생략 -> 브라우저 종료시 까지만 유지

영속 쿠키: 만료 날짜 입력 -> 해당 날짜까지 유지

 

쿠키 도메인

생성한 쿠키가 아무 사이트에 전부 전송되면 안됨

도메인 명시: 명시한 문서 기준 도메인 + 서브 도메인

도메인 생략: 현재 문서 기준 도메인만 적용

 

쿠키 생성

1
2
Cookie idCookie = new Cookie("memberId"String.valueOf(loginMember.getId()));
response.addCookie(idCookie);
cs

쿠키 조회

1
2
3
4
5
6
7
8
9
10
11
12
13
@GetMapping("/")
public String homeLogin(@CookieValue(name = "memberId", required = false) Long memberId, Model model) {
    if(memberId == null){
        return "home";
    }
 
    Member loginMember = memberRepository.findById(memberId);
    if(loginMember==null){
        return "home";
    }
    model.addAttribute("member", loginMember);
    return "loginHome";
}
cs

쿠키 제거

1
2
3
4
5
6
7
8
9
10
11
@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);
}
cs

쿠키 문제점

1. 쿠키 값 임의로 변경 가능

2. 쿠키에 보관된 정보 훔쳐갈 수 있음

3. 해커가 쿠키 한 번 훔쳐가면 평생 사용가능

 

대안 -> 세션으로 해결 가능

1. 쿠키에 중요한 값 노출 x, 예측 불가능한 임의의 토큰을 노출하고

   서버에서 토큰 <-> 사용자 id 맵핑해서 인식. 서버에서 토큰 관리

2. 토큰은 해커가 임의의 값을 넣어도 찾을 수 없도록 예상 불가능 해야 함

3. 토큰 만료 시간을 짧게 설정

 

세션

중요한 정보를 서버에 보관하고 연결을 유지하는 방법

쿠키 문제 해결 -> 중요한 정보를 모두 서버에 저장.

추정 불가능한 임의의 식별자 값으로 클라이언트 <-> 서버 연결

 

세션ID: 추정 불가능해야함 -> UUID는 추정 불가

서버의 세션 저장소에 {세션 ID, 보관할 값(memberA)} 보관

 

회원과 관련된 정보는 전혀 클라이언트에 전달하지 않음

추정 불가능한 세션 ID만 쿠키를 통해 클라이언트에 전달

 

클라이언트: 요청 시 항상 mySessionId 쿠키를 전달

서버: mySessionId 쿠키 정보로 세션 저장소를 조회 -> 로그인 시 보관한 세션 정보 사용

세션 id는 털려도 여기에는 중요한 정보 없음

토큰 털려도 만료시간 짧게 하거나 세션 강제로 제거

 

세션 관리

1. 생성

  ① 세션 id 생성

  ② 세션 저장소에 {세션 id, 보관할 값} 저장

  ③ 세션 id로 응답 쿠키 생성해서 클라이언트에 전달

 

2. 조회

요청한 세션 id 쿠키 값으로 세션 저장소에 보관한 값 조회

 

3. 만료

요청한 세션 id 쿠키 값으로 세션 저장소에 보관한 세션 id, 값 제거

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
 * 세션 생성
 * 1. 세션 id 생성
 * 2. 세션 저장소에 보관할 값 저장
 * 3. 세션 id로 응답 쿠키 생성 -> 클라이언트에게 전달
 */
public void createSession(Object value, HttpServletResponse response) {
    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());
    }
}
 
public Cookie findCookie(HttpServletRequest request, String cookieName) {
    if(request.getCookies()==null){
        return null;
    }
    return Arrays.stream(request.getCookies())
            .filter(cookie->cookie.getName().equals(cookieName))
            .findFirst()
            .orElse(null);
}
cs

호출

1
2
sessionManager.createSession(loginMember, response);
sessionManager.expire(request);
cs

 

세션 id 임의로 변경 -> getSession()에서 못찾고 null 반환

HttpSesion

로그인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    @PostMapping("/login")
    public String loginV4(@Valid @ModelAttribute("loginForm") LoginForm form,
                          BindingResult bindingResult,
                          @RequestParam(defaultValue = "/"String redirectURL,
                          HttpServletRequest request){
        if(bindingResult.hasErrors()){
            return "login/loginForm";
        }
        Member member = new Member();
 
        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:"+redirectURL;
    }
cs

로그아웃

1
2
3
4
5
6
7
8
9
    @PostMapping("/logout")
    public String logoutV3(HttpServletRequest request) {
        // 세션 없으면 만들면 안됨
        HttpSession session = request.getSession(false);
        if(session != null){
            session.invalidate();
        }
        return "redirect:/";
    }
cs

홈 화면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    @GetMapping("/")
    public String homeLoginV3(HttpServletRequest request, Model model) {
        // 처음 사용자 (로그인 안한) 세션 만들면 안됨
        HttpSession session = request.getSession(false);
 
        if(session==null){
            return "home";
        }
        Member loginMember = (Member)session.getAttribute(SessionConst.LOGIN_MEMBER);
 
        // 세션에 회원 데이터 없으면 home으로
        if(loginMember==null){
            return "home";
        }
 
        // 세션이 유지되면 로그인 한 home으로 이동
        model.addAttribute("member", loginMember);
        return "loginHome";
    }
cs

@SessionAttribute (세션 생성 안함)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    @GetMapping("/")
    public String homeLoginV3Spring(
            @SessionAttribute(name=SessionConst.LOGIN_MEMBER, required = false) Member loginMember,
            Model model) {
 
        // 세션에 회원 데이터 없으면 home으로
        if(loginMember==null){
            return "home";
        }
 
        // 세션이 유지되면 로그인 한 home으로 이동
        model.addAttribute("member", loginMember);
        return "loginHome";
    }
cs

 

세션 타임아웃 설정

세션 삭제: 로그아웃 클릭 시 session.invalidate() 호출됨

but, 대부분의 사용자 -> 로그아웃 안하고 웹 브라우저 종료

http는 비연결성(ConnectionLess)이므로, 서버 입장에서는 해당 사용자가 웹 브라우저를 종료한 것인지 아닌지 알 수 없음.

  -> 서버는 메모리에 계속 저장 중. 메모리 터질 수 있음

  -> 쿠키 탈취당하면 오랜 시간이 지나도 해당 쿠키로 요청 가능

 

세션 종료 시점: 생성 시점 기준 (x), 최근 요청 시간 기준으로 30분 정도 유지

LastAccessedTime 이후로 timeout 시간이 지나면, WAS가 내부에서 해당 세션 제거

 

세션에는 최소한의 데이터만 보관해야 함

보관한 데이터 용량 * 사용자 수로 세션의 메모리 사용량이 급격하게 늘어나서 장애로 이어질 수 있음

세션 시간 너무 길면 메모리 사용 계속 누적됨 (기본 30분)

 

문제점

URL만 알면 로그아웃한 상태라도 상품관리 접근 가능 -> 막아야 함

 

반응형

+ Recent posts