수업정리 쿠팡#3

최동민·2022년 6월 30일
0

Spring 수업정리

목록 보기
33/47

지난 내용 수정.

두 컨트롤러에 SessionAttributes 추가

index.html

register.html

form태그 안에 name은 entity와 꼭 같게 해주자.

login.html

header.html

로그인 된 상태이면 로그인과 회원가입을 없앤다.
로그아웃 상태이면 로그인과 회원가입을 보여준다.

이때 userEntity의 getName이 값을 받아오지 못한다.
왜 null일까?
userEntity가 어디서부터 최초로 객체화 되었나? Service.
UserController에서 UserEntity라는 이름으로 LoginVo를 집어넣었고, LoginVo는 Service의 Login을 거쳐오는데, 보아하니 LoginVo에 set~이 아무것도 안되어있었다.
DB에서 받아오는 UserEntity 객체가 가진 값을 LoginVo에 다 넣어주어야 정상적으로 작동이 될 것.

실제로 Controller가 html 파일로 하여금 넘겨주는건 LoginVo.
LoginVo객체가 값들을 가지고 있어야 html에서 정상적으로 값들을 가져다 쓸 것.

UserService
세션 처리도 한다.

브라우저 사파리와 크롬에서 로그인을 진행했을 때, 한쪽에서 로그인을 하면 로그인이 되어있던 다른 쪽이 로그아웃이 되도록 할 것.
이제는 톰캣이나 스프링이 제공해주는 세션에만 전적으로 의존하지 말자.

로그인 한 사람에게 키1 을 주고, 이 키1 을 가진 사람이 우리 사이트에 들어왔을 때, 우리는 그 키를 DB에 저장되어있는 키랑 비교를 해서 그 키가 올바른 키라는 생각이 들면 '넌 로그인 된 사용자다' 라고 해준다. 그리고 만약 동일한 계정으로 누군가 로그인을 했을 때, 마찬가지로 키를 줄 것이다. 다만 다른 키를 준다. 키2. 그런데 두 로그인한 계정이 동일한 계정이라는 것. 그래서 계정이 가진 키1을 DB에서 다 날린다. 그 다음 새로운 키인 키2만 집어넣는 것. 그럼 키1은 DB에 없기 때문에 로그아웃 된 것처럼 작동을 한다.

이 사용자를 별도로 인식하기 위해 키를 만들 것.
SessionKey.
절대 추측할 수 없는 문자열로 만들고, 해싱처리로 마무리.
보통 이런 조치를 취해주기 위해서 사용자를 기억하기 위해 Session을 어디 집어넣던가 하는 조치를 취하기 위해서는 Redis라는 것을 쓰는데 일단 생략.

사용자의 세션 정보를 담기 위한 테이블 생성

사용자가 언제 로그인했나?
이 세션이 언제 마지막으로 활동했나? 페이지를 옮겼다던가, 이런저런 요청을 보낸 마지막 시간.
이 세션이 언제 만료되는가? 로그인 후 1시간 동안 아무것도 하지 않았다면 로그아웃 시켜주는 것.
만료되기 전에 로그아웃을 했다거나 다른 사용자가 로그인을 해서 더 이상 유효하지 않게 만들기 위해 사용.
user_email = 세션

SessionEntity

UserService

request를 받아오기 위해 HttpServletRequest 매개변수 추가.
ua작성

IUserMapper
SessionEntity를 세션 테이블에 INSERT하자

xml

UserController에 request 추가

누가 요청을 했을 때 이메일과 비밀번호가 맞다면 진행시켜주면 될 것. 아니면 튕겨내면 되고.

UserService

누가 로그인 요청하면 맞다면 세션키 만들어서 INSERT

세션키 만들기 전 동일한 이메일을 가진 모든 세션 만료시키기.
기존에 그 이메일로 로그인된게 다 로그아웃 될 것.

세션 만료처리 후 세션을 INSERT 시켜야 저 이메일로 유일하게 한 명만 로그인 된 상황을 만들 수 있다.

로그인 한 사람에게 키를 만들어 우리 DB에 집어넣었고. 이제 그걸 돌려주어야 한다. 모든 reqest와 response에 우리가 명시하지 않아도 항상 뒤에 같이 따라다니는 것들이 있는데. 그 중에 하나가 Header이다.
톰캣이 클라이언트를 구분짓기 위해 사용하는게 JSESSIONID인 것처럼 우리가 이 키를 쿠키에다 실어 보낼 것. 한번 실어보내면 요청과 응답에 그 쿠키가 계속 같이 따라다닐 것.

LoginVo에 sessionEntity만들고 게터세터 추가.

UserController

결과가 SUCCESS일 때, LoginVo가 가지고 있는 SessionEntity가 가진 키를 쿠키에 집어넣을 것.

요청,응답을 할 때 쿠키를 추가해줄테니 클라이언트(브라우저)한테 나한테 요청을 보낼 때 쿠키를 보내세요 하고 알려줘야 함. 그래서 response에서 처리를 함.

addCookie는 쿠키 객체를 받는다.
쿠키 객체를 만든다.

실제 중대형 서비스에서는 레거시 세션을 쓰지않음. 레거시라는 건 위에서부터 계속 내려오는, 스프링이나 톰캣이 기본적으로 제공하는 레거시 세션을 쓰지 않는다는 뜻. JSESSIONID, SessionAttributes를 쓰지 않고, redis와 통합해서 직접 세션 시스템을 구축해서 쓴다. UserEntity를 때려넣는게 아니고.

이 쿠키에 setPath를 하지 않으면 이 주소에만 쓸 수 있는 것이고, 그래서 우리가 setPath를 /로 하게 되면 우리 도메인의 모든 주소에서 쓸 수 있다.

다시 로그인하면, 방금 만들어진 세션 키sk.
sk가 우리가 쿠키 만들 때 지정했던 그 이름이 된다.
테이블에 들어가있는 세션키 값이랑 동일한 값이 이 세션 쿠키에 들어가있다. 쿠키가 만들어지고 그 자리에 가만히 있는게 아니고, 우리 서버로, 그 어떤 요청이 들어올때더라도 이 쿠키가 함께 실려서 같이 오는 것. JSESSIONID도 마찬가지.

sk는 세션키를 줄인 것.
우리는 이제 JSESSIONID에 의존하는게 아니라, 이걸 가지고 이 사람이 로그인 한 사용자인지 아닌지를 구분하는 것.

유저서비스에 세션 만료처리하는 파트에서, 로그인을 시도하는, 얘가 가진 이메일 기준으로 세션테이블에 그 이메일과 동일한 모든 세션키의 expired를 true로 만드는게 목적.

이메일로 사용자 세션을 만료되게끔 업데이트 하겠다.
그래서 INSERT가 발생하기 전 UPDATE부터 하겠다. 만약 INSERT부터 하게 되면 우리가 방금 INSERT한 세션도 만료가 되어버리니까.

public interface IUserMapper {

xml
LIMIT은 두면 안됨. 싹 다 만료되게 해야하니까.

UserService 세션 만료 처리
로그인을 하게 되면 원래 있던 것들이 다 만료된 형태로 표시되고, 방금 새로 만든 세션은 잘 있게 된다.


지금부터 세션 의존성을 없애도록 해본다.

컨트롤러에 @SessionAttributes(value = "userEntity") 다 지우고.

UserController
modelAndView.addObject("userEntity", loginVo); 지운다.

RootController에도.


이제 로그인 처리가 아예 안되겠다.
이제 레거시 세션은 없다. 이제 어떻게 할까?

지금부터 우린 sk를 이용해서 UserEntity를 받아와야 한다.
세션키라는 문자열128자만을 이용해서.
이 일이 언제 발생? 말 그대로 모든 주소에서 발생해야 한다.
동일한 일이 모든 곳에서 발생하게 하기 위해 인터셉터가 필요.

WebMvcConfig

configurePathMatch
프로젝트 펼치면 이거부터 만드는게 좋다.
User/Login 들어가는 방식이 상대경로에서 봤을 때 완전히 다른 얘기. 이걸 작동안하게끔 하는 메서드.

user/login 로그인이라는 파일이 있는 것
user/login/ 로그인이라는 디렉토리가 있는 것
상대경로가 완전히 다름.

SessionInterceptor
이 SessionInterceptor라는 클래스가 Interceptor가 되기 위해서는 HandlerInterceptor라는 인터페이스를 구현.
컨트롤러 들어가기 전 조치사항을 취해주기 위한 메서드는 ?
prehandle.
여기서 false 반환하면 컨트롤러로 안들어간다.

여기서 해야될 일은 세션이 있는 것처럼 작동을 하게끔 조치를 취해줘야 한다.

// 쿠키에서 sk 값 가져오기
// sk를 이용하여 DB에서 SessiongEntity 긁어오기. 단, 만료되지 않은거만
// 돌아온 SessiongEntity 가 null 이거나 userEmail이 없거나 하면 로그인 안한거임
// 돌아온 SessiongEntity의 userEmail이 있고 이 email로 UserEntity를 긁어왔더니 정상이다 = 로그인된 상태
// 긁어온 UserEntity 를 request 객체에 Attribute로 추가하면 다 OK

Interceptor와 컨트롤러에서 사용한 request 객체 둘은 같은 객체이다.
== 해보면 true가 뜸. 주소값이 같음.

이 다섯개 단계를 다 밟아야 로그인처리가 된다.
레거시 세션에 의존하지 않고.

쿠키에서 sk 값부터 가져오자.
sessionKeyCookie 일단 null
우리가 쿠키를 추가할테니 앞으로 요청을 보낼 때마다 쿠키를 같이 보내주렴 하고 알려줄 때 response를 썼으니,
요청이 들어왔을 때 sk라는 쿠키가 있는지 확인하기 위해서는 당연히 request를 써야겠다.

만약 쿠키가 하나도 없는 상태에서 요청을 보내면 null이 들어오니 null검사를 해주자.
for를 돌려
만약 돌아가고 있는 쿠키에 sk 동일한게 있으면 sessionKeyCookie 를 그걸로.

람다식도
위 내용과 같음. Optional은 제네릭인데 가지고 있는 메서드 중 orElse 라는게 있다.
쿠키 타입의 객체를 가지고 있으면 쿠키를 돌려주고 아니면 뒤에 나오는 null을 돌려주는 것.

만약 sessionKeyCookie 가 null이 아니면 sessionKey 값을 지정. 여기까지가 1번. 쿠키에서 sk값 가져옴.

IUserMapper 에서 selectSessionByKey

xml
만료일자가 지금보다 미래이고 만료되지 않은 것만. 유효한 세션만 가져오겠다는 것.

UserService 에 getSessiong에서 유효한 세션만 돌려줌

SessionInterceptor
이제 인터셉터에서 UserService에 getSession이라는 메서드를 호출해서 SessionEntity를 가져와야 한다.
하지만 인터셉터는 생성자를 통한 멤버변수에 초기화가 안되니까 private으로 UserService를 불러 @Autiwired를 붙여놓는 방법 밖에 없겠다.

WebMvcConfig 에서 Bean어노테이션 등록
addInterceptors 메서드에 인터페이스 추가
InterceptorRegistration.
메서드 체인. addInterceptors라는 메서드가 반환하는 타입이 InterceptorRegistration 타입이고. 그 외 모든 메서드가 반환하는 타입이 똑같기 때문에 계속 . 으로 찍어가며 실질적으로는 계속 return this하는 것.
addPathPatterns, excludePathPatterns도 InterceptorRegistration 타입인 것.
같은 객체에 대해 메서드를 호출할 수 있는 것.
메서드체인에 입각하여 객체를 짜면 개발하기가 많이 수월해짐.

SessionInterceptor
sessionEntity 가 sessionkey를 받는다.

IUserMapper

xml

UserService

SessionInterceptor

sessionEntity가 null이 아니고 getUserEmail도 null이 아니라면 이제 우린 userEmail로 UserEntity를 끌고 올 수 있다는 것.

userEntity가 null이 아니고, getEmail이 null이 아니라면 로그인이 되어 있는 상태이겠다.
request.setAttribute("userEntity", userEntity); 해줌.

그런데 최종적으로 request.getAttribute가 null이면
클라이언트가 sk라는 쿠키를 가지고 있어도 되긴한데, 우리가 직접 DB를 긁어봐야 하기 때문에 성능 상 이점을 갖기 위해, null이면 우리가 직접 sk라는 쿠키를 지워주도록 한다.

쿠키는 수명이라는게 있음. remove가 없기에 수명을 0으로 만들어주면 죽게 된다.

일반 브라우저에서 로그인 하고
시크릿브라우저에서 로그인 하면
일반 브라우저 로그아웃 됨.

request는 하나이다.

UserService

업데이트를 해주지 않으면 로그인 한 순간부터 1시간 후 로그아웃 되어버린다. 시한부.
인터셉터 거칠 때마다 UserEntity가 null이 아닌 것을 확인할 때마다 업데이트 해주어야 한다.

중복로그인 방지를 위한 인터셉터

IUserMapper

xml

updated_at
이 세션이 언제 마지막으로 활동했는가에 대한 시간
expires_at
만료 일시를 늘려줌. 마지막으로 활동한 시간 + 60분으로 세션의 만료시간을 늘려줌.
expired_flag

profile
코드를 두드리면 문이 열린다

0개의 댓글