스프링 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만 알면 로그아웃한 상태라도 상품관리 접근 가능 -> 막아야 함
'오늘 배운 것' 카테고리의 다른 글
[스프링 MVC 2] 예외 처리와 오류 페이지 (0) | 2021.07.23 |
---|---|
[스프링 MVC 2] 로그인 처리2 - 필터, 인터셉터 (0) | 2021.07.22 |
[스프링 MVC 2] 검증2 - Bean Validation (0) | 2021.07.19 |
2021.07.14 (수) (0) | 2021.07.14 |
[알고리즘] 다익스트라, 세그먼트 트리 (0) | 2021.07.13 |