[Spring] CH20 스프링부트 인증 - 세션과 쿠키

jaegeunsong97·2023년 3월 31일
0

[Fast Campus] Spring

목록 보기
37/44
post-thumbnail

📕 1교시


JPQL

무조건 댓글 나온다, left outer join

그리고

댓글이 있으면 뽑아라 없으면 null

댓글을 사용한 user가 있을 것이다. 그거는 defalut size를 걸면 된다.

그리고 Nativequery

별칭을 주는 이유는 BoardDetailDto에서 알 수 없기 떄문에 별칭을 붙인다.

결과를 실행하면 한방쿼리

그리고 응답받은 데이터는 List로 무조건 받는다

이떄 List< Object > Object는 1개의 row이다.

그리고 직접 돌면서 OM을 한다. ORM은 아니다.

오늘은 스프링 부트 인증 세션과 쿠키를 할 것이다.

여기 까지는 프로토콜이다. 자동으로 이렇게 돌아간다.

가장 처음에는 퉵의 탄생은 논문을 읽기 위해서 이기 때문에, 서버는 Client의 상태를 기억할 필요가 없었다.

지금까지 Stateless 서버를 배웠고, 이제는 Stateful 서버를 배워보자

그림으로 이해를 해보자

최초의 HTTP 는 요청 Get 밖에 없었다.

이렇게 되면 1대 1로만 가능하다. 따라서 스레드가 붙어야한다.

이 상태를 세션이라고 한다.

자원에 접근할 수 있는 상태가 되었다. 이것을 세션이 생성되었다라고 한다.

이렇게 Get요청을 하고 논문을 받으면, 선을 끊어야한다. 왜냐하면 목적인 논문을 가져왔기 떄문에 선을 종료해줘야한다.

이런 서버를 stateless 서버라고 한다. client 상태를 기억하지 않는다. 그래서 트래픽이 줄어들고 더 많은 사용자를 받을 수 있따.

근데 최근 웹서버는 이렇게 똑같이 돌지만 다른점이 있다.

지금 까지는 '구버전'이다.

최근에는 stateful하게 돌아간다.

client는 1명만 있다고 가정을 하겠다(물론 여러명이 있지만)

일단 둘이 연결되는 것은 똑같다(세션이 연결되지 않으면 데이터를 못 받기 떄문에) 그리고 멀티 스레드 기반이다.

이제는 Get 뿐만 아니라 다양한 요청을 할 수 있다.

Get, Post, Put(전체), Delete, patch(부분))

Client는 이제 서버 쪽으로 데이터를 줄 수도 있다.

Get 요청으로 main page 달라고 요청(최초 요청)을 하면, 톰켓은 세션이라는 저장소를 1개 들고 있다.

서버가 만들어질떄 자동으로 만들어진다. 세션은 서랍처럼 되어있다. 1개만 있다. request는 client마다 만들어지기 떄문에 많다.

세션을 보면 다음과 같이 되어있다.

그리고 데이터 영역은 key, value로 나눠져 되어있다. hashmap

나머지도 똑같이 생겼다.

가장 처음에 get 요청이 들어오면 서버는

1) 클라이언트의 requestHeader에 Cookie라는 값을 검사
2) JsessoinId 값이 있는지 확인(최초 요청시 없음)
3) 임의의 중복되지 않는 JsessoinId를 생성(= ABC5)
4) JsessionId(ABC5) 세션 메모리 영역에 저장

이 과정은 알아서 진행이 된다. Client도 쿠키에 값을 넣어 전달하는 것도 프로토콜이다.

최조 요청이니까 세션에는 ABC5라는 값이 들어가게 된다.

그리고나서 logic이 한번 실행이 될 것이다.

5) Controller - service - repository 일한다
6) 최종적으로 ResponseHeader에 Set-Cookie에 ABC5를 넣어서 'JsessionId= ABC5' 이런 형태로 담아서 응답한다.

이게 최초에 서버가 하는 일이다.

그러면 브라우저는 어떤 일을 할까? 브라우저는 최초에 Cookie라는 저장소가 있다.

그리고 Key Value로 되어있다.

최초에는 JsessionId 값인 쿠키가 없었을 것이다.

요청하고 나서 응답이 오면, 브라우저는 Set-Cookie라는 값이 있으면 저장을 한다.

Client가 2번쨰 요청을 한다고 해보자

이번에는 RequestHeader에 쿠기 값: ABC5를 가지고 갈 것이다.

무조건 브라우저는 서버쪽으로 갈때 쿠키 값(JsessionId)을 전부들고 간다. 항상

그러면 서버쪽에서는 다시

1) Cookie 값이 있는지 확인 -> 있다.
2) 있으니까 JsessionId 확인 -> 있다.
3) 생략
4) 생략

서버 입장에서는 그냥 아~ 이전에 왔던 Client 구나 라고 인식만 하고 끝이난다. 그냥 이전에 왔던 Client다!라고 하기만하고 아무일도 일어나지 않는다.

그리고 세션키가 있는지 없는지 찾을때는 HashMap으로 되어있기 때문에 바로바로 접근을 해서 찾아낸다. 100개 1000개여도 빠르게 가능

이렇게 Client가 이전에 왔다는 것을 알고 있다는 것 자체가 stateful 서버라는 것이다.

내가 만약 상태를 저장하지 않는 서버를 만들고 싶으면, JsessionId를 사용하지 않으면 된다. 톰켓에게 알려주면 된다.

우리는 프로젝트할때는 stateless서버를 만들것이다. 이유는 default가 stateful서버로 되어있기 때문이다.

그럼 쿠키는 어디에 저장이 될까? -> Client, 브라우저 측에 저장이 된다.

세션은 서버측에 저장이 된다.

JsessionId를 통해서 이전에 왔던 사람인지 아닌지를 구별할 수 있다.

JsessionId는 나를 인식 시킬수 있는 정보이기 때문에 매우 중요하다.

근데 이게 브라우저쪽에 저장이 되어있다. 따라서 누가 강탈할 수 있기 때문에 매우 위험하다.

요즘 최근 정책은

아무것도 나오지 않는다. JS의 접근이 거부했기 때문이다.

쿠키에 접근이 가능하다.

Http Only 라고 적혀있는 것은 오직 브라우저만이 접근 가능하다.

그럼 JsessionId는 HttpOnly를 걸어둘까? 걸어둬야한다. 그렇지 않으면 JS로부터 해킹을 당할 수 있기 떄문이다. 훔쳐올 수 있다.

요청헤더의 쿠키, 일반인이 요청을 할때마다 자동 전달된다.

만약에 서버쪽에 쿠키를 저장해달라고 하면 ResponseHeader에 Set-Cookie에 저장을 해서 Client에게 보내면 된다.

그러면 브라우저가 아래 사진의 영역(쿠키영역)에 바로 저장을 한다.

JsessionId: 식별자, 하지만 아직까지는 이전에 왔던 사람이다. 까지만 가능하다.

그럼 사용자를 구분하기 위해서는 세션 서랍의 데이터 영역에 Key와 Value에 값을 넣어주면 된다.

이때 key는 String 타입이고, Value는 obj타입이다.

그러면 user object는 login할 떄 "아까전에 왔던 사람이다" + "자기 세션키 영역의 데이터가 있어서 누구인지 구분도 가능햐다"

언제 구분할 수 있냐면 -> 내가 session의 값을 임의로 저장을 해야한다.

Client의 정보를 저장하면 된다. 즉, join과 loing을 통해서 세션 서랍에 데이터 영역에 저장이 가능하다.

이렇게 저장을 하지 못하면 사용자 구분을 하지 못한다. 그냥 '아 이전에 왔던 사람이다'까지만 하는 것이다. 하지만 이전에 왔다라는 것만 알아도 StateFul 서버이다.

이 JsessionId를 쿠키로 전달하면 사용자의 민감한 정보에 접근이 가능하다.

안 넣었으면 중요하지 않다. 이유는 Client의 민감한 정보를 저장했기 때문에

이렇게 넣기 전까지는 중요하지가 않다. 넣고나서 부터가 문제다.

만약 다른 Client가 ABC5를 담아서 연결하면?

서버는 민감한 정보에 접근하게 해준다.

이전까지 즉, 아까 왔던 사람이다. 까지는 좋았지만, 민감한 정보를 저장(로그인, 세션 영역에 client정보를 저장하는 것) 이후 부터는 JseesionId를 해킹당하면 안된다

그래서 톰켓이 JsessoinId를 defalut로 걸어서 전달해준다. 이것을 걸지 않으면 JS로 해킹이 가능하다!




📕 2교시


https://github.com/codingspecialist/Springboot-Session-Cookie.git

서버와 포스트맨을 켜보자

가장 먼저 의존성을 확인해보자

model 확인

Repository, JpaRepository해서, Import 할 필요 없다

@Query: 안에 JPQL쿼리를 적을 수 있다. 그리고 :username, :password 는 @Param("password") 이런식으로 해야 바인딩이 된다. 만약 @Param을 적지 않으면 바인딩이 되지않는다.

JPA 에는 named query가 있다. 따라서 @Query를 적을 필요도 없다.

적지 않아도 다음과 같이 적으면 되기는 하다. 아래의 코드를 보자

여기까지 findBy가 select u from User u 를 만들어 낸다.

그다음에 내가 where절에 적고 싶은 것

그다음에 And를 붙이면 and를 붙여주고

password를 붙이면

자동으로 만들어준다.

이렇게 만들면 @Query(JPQL)을 할 필요가 없기는 하다. 이런 쿼리를 자동으로 만들어주는 namedQuery가 있기는 하다.

사용하지 말자! 공부 더해야 하니까

그리고 보면 Optional은 username과 password로 DB에 조회를 했는데, null 일 수도 있으니까, 일단 내가 Optional로 감싸는 것이다.

즉, nullpointEx을 안터지게 도와주는 라이브러리이다.

기본 리턴: bad request -> 100% 유효성 검사 실패

그외의 리턴: 값 전달

@RestControllerAdvice -> 모든 Ex를 1군대에서 처리!

보니까 ResponseEntity에 e.getMessage가 걸려있다. + 상태코드!

내부 클래스 방식

강사님의 Dto 방식

Data transfer Obj는 나중에 매우 많이 필요하다. 파일이 정말 늘어나서 보기가 싫어진다. 따라서 UserRequest 를 만들고 내부로 각각의 Dto를 전부 모아두면 편하다.(static 붙여야된다.!)

나중에 사용하려면 Request.loginDto 이렇게 사용하면 된다.

응답의 Dto는 사용하지 않지만, 그냥 만듬

session -> DI, IocContainer로 부터!

session은 IocContainner로부터 받을수 있다. 이유는 session영역은 spring에서 1개만 떠있기 때문에! -> session은 싱글톤 객체다

Request는 DI가 될까?

안된다!! -> Client가 여러명이기 떄문에(스레드 별로 존재) -> 따라서 IocContainner에 존재할 수 없다.

2번쨰로 session을 다음과 같이 받을 수 있을까?

받을 수 있다! -> 이유는 DS는 Request, Resposne를 들고 있다. 따라서 DS가 "어! session이 필요하네?" 하면서 리플렉션을 한다.

그렇게 request.getSession()이렇게 해서 주입을 해준다.

그러면 DI라는 것은 생성자 주입도 있지만, 메소드 주입도 있다.

즉, Spring에서의 생성자 주입은 Ioc로부터 오는 것이고, 메소드 주입은 controller를 호출한 Dispathcer servlet으로부터 오는 것이다.

그럼 테스트를 해보자

매개변수를 보면 이름에서부터 요청받는 DTO라는 것을 알 수 있다.

@RequesetBody가 있으니까 Json으로 보내라는 것

userPS 영속화를 하고, 리턴할 때 영속화된 객체를 MessageConverter가 Json으로 직렬화를 한다. 그때 getter가 떄려진다. 나중에 잘못해서 getter를 때리면 lazyLoading을 하다가, 무한 참조가 일어나서 터질수도 있다.

getter를 떄린다. Json을 만든다. 중요하다!!

그리고 보면 joinDto.toEntity()가 있다.

원래는 다음과 같이 되어있는 것이다.

이렇게 만들면 편하다. 값을 받아서 비영속 객체에 값을넣어서 리턴을 해준다.

만약 UserRequest에다가 toEntity를 만들지 않으면 다음 코드와 같은 것이다.

코드가 매우 지저분해진다.

따라서 다음과 같이 만들어주자

어느 Dto에서만 toEntity를 만들까?

insert하거나 update를 할 때 필요하다

보면 loginDto는 만들지 않는다. 왜냐하면 select를 할 것이기 때문이다.

실행을 해보면

PW만 뺴고 받았다. 왜냐하면 @JsonIgnore를 걸었기 떄문이다.

이제 login을 보자

이렇게 만들자

userOP.isPresent() 뜻은 값이 있으면!! 값이 있으면userOP.get()으로 꺼내서 session에 넣자

만약 값이 없으면 throw를 날리면 된다.

Optional코드를 처리하는 정석코드이다.

근데 이렇게 잘 사용하지는 않는다.

아마 없을때 꺼내면 nullex가 뜰 것이다.

만약 다음과 같은 코드이면? 위험한 코드다. 왜냐하면 없을 수도 있기 때문이다.

즉, get()은 null 처리가 되고 사용해야한다.

가장 많이 사용하는 것은 orElseThrow()이다.

userPS를 찾으면 세션에 접근해서 loginuser에 userPS를 저장한다.

이게 그림에서 어느 부분일까?

키값이 바로 "loginㅕser"이고 거기에 user 객체를 저장한다. 이게 login process이다.

먼저 회원가입을하고

Optional이 잘 처리되는 것을 알 수 있다.

User에 조건을 걸어주자

not null 과 50자로 제한되어있는 것을 알 수 있다.

테이블을 만들때는 @Column이라는 것으로 제약조건을 줄 수 있다.

다시 해보자(방금 것은 터진것)

회원가입을 하고

로그인을 하면

코드를 보면 login이 되면 ok만 되어있다. 무엇으로 로그인이 되어있는지 알 수 없다.

이제 인증 체크를 해보자

MyLoginArgument 전체 주석

Usercontroller 해당 부분 주석

webMvcconfig 전체 주석

usercontroller

Unauthorized는 401이다. 401은 로그인이 안됬을 떄 (인증에 실패했을 떄 발생)

서버 재실행

바로 getmapping의 /users를 실행

왜이렇게 뜰까? session에 값이 없기 때문이다.

그리고 회원가입을 먼저 해보자

로그인 하고 -> /users를 하면!

결과




📕 3교시


send를 하면 쿠키에 JsessionId가 있다. 이름이 03F4이다.

그러면 서버는 쿠키를 받아서 세션에 있는지 체크를 한다.

지금 서버는 "아 이전에 왔었구나 까지만 알고 있다."

이제 JsessionId를 보안적으로 중요하게 만들것이다. Login을 하는 순간 JseesionId는 세션서랍에 데이터 영역으로 key, value로 저장이 된다.

만약 세션키를 탈취당하면 데이터영역에있는 key, value가 노출이된다.

왜냐하면 세션키 ABC5는 사용자 식별자기 떄문이다.

로그인 전까지는 민감한 정보가 아니다. 왜냐하면 데이터 영역에 key, value가 없었기 때문이다.

하지만 login을 하는 순간 데이터 영역에 key, value가 채워지고 탈취당하면 안된다.

따라서 login 후 Responseheader를 보면

HttpOnly에 true로 되어있는 것을 알 수 있다. 그래서 오직 브라우저만 접근이 가능하다.

JS에서 해킹을 못하는 것이다.

예전 이야기

탈취해서 가능!

진화

지금

물리적 접근 불가!

그래서 login을 하면 접근 가능!

해당 부분뺴고 삭제!

webmvcconfig 주석 해제

usercontroller

userdetailv2 삭제

보면 인증이 필요한 부분이면 계속 적혀있다.

controller마다 반복적으로 적혀있다.

그래서 삭제를 해보자

그러면 해당 페이지로 접근할 때, 인증 체크를 할까? 하지 않는다.

확인을 위해 webmvcconfig를 주석 처리하자

서버 재실행 하고 접근을 해보면, 빈 배열이 리턴된다.

그림으로 이해해보자

컨트롤러마다 메소드를 들고 있을 것이다.

Filter는 Db접근이 가능할까? 접근 가능하다. 별로 사용하는 것을 추천하지 않는다.

Flilter에서 막는 것은 전역적인 체킹이다.

우리는 특정 메소드의 전, 후를 제어해보자

인터셉터를 통해서 controller가 실행되기 전후를 관리할 수 있다. 즉 특정 메소드가 인증이 필요한지 안 필요한지 알 수 있는 것이다.

즉, 해당 인터셉터하는 부분에서 HttpOnly를 체크하면 된는 것이다.

prehandle이기 떄문에 메소드 실행 직전에 호출이 된다.

true -> 메소드 탐
false - > 메소드 안탐(튕겨나감)

false면, controller로 진입도 못한다. 바로 튕긴다.

이 Interceptor도 등록을 해야한다. webmvcconfig에서 하자

다음과 같이 등록하면 된다.

보면 주소를 걸었다. 저 뜻은 /users, /users/1 ... 모든 주소는 interceptor를 무조건 타는 것이다.

로그인을 안한 상태에서 접근을 하면

이번에는 이쪽으로 가보자

이렇게 한번 설정을 하면 부가적인 코드를 적을 필요가 없다.

여기서 부가적인 코드는 체크를 하는 부분을 의미한다.

따라서 Controller가 심플해진다. 핵심로직에만 집중을 할 수 있다.

이번에는 밑에꺼를 해보자

보면 session체크를 한다고 생각하는데, 그게 아니라 id를 비교해서 동일하면 해당 id의 객체를 넘겨주고 아니면 에러를 터트리낟.

따라서 권한이 없으니까 추가를 해주자

해당 페이지는 인증이 안되면 들어갈 수도 없는 페이지다.

이번에는 회원가입후 해보자

회원가입 2개

login

정보 접근

login하지 않은 2번으로 할경우

인증이 안되면 401, 권한이 없으면 403

여기서 중요한 정보가 나온다.

주소로 들어오는 데이터는 신뢰할 수 없다. 따라서 무조건 인증을 해야한다.

마지막으로 Cookie를 알아보자

쿠키는 login을 하지 않아도 할 수 있다.

보면 request, response를 주입 받ㅇ았다.

이거는 DS로부터 전달 받은 것이다.

일단 Controller는 메모리에 1번 생성된다. 싱글톤이니까

그럼 num은 힙 객체에 떴다.

Client 3명이 Controller에 동시 접근이 가능할까? 가능하다! 동시에 접근이 가능하다. 스레드 별로3명이 들어온다.

스레드는 num이라는 변수를 공유할까 아니면 공유하지 않을까?

공유한다.

왜냐하면 객체가 하나이기 때문이다.

지역변수 n

만약 3개의 스레드가 join메소드요청을 했으면?

ssar, cos, love 동시 join 메소드 호출!

ssar -> num 20으로 변경, 그럼 cos, love도 num을 20으로 본다

만약 이렇게 되어있다고 해보자

ssar이 num -> 30으로 변경

이렇게 되면 cos, love는 num을 20으로 보고 ssar은 30으로 바라본다.

왜냐하면 메모리 영역이 다르기 떄문이다.

결론은 멀테스레드 환경에서는 상태변수를 만들면 안된다. 왜냐하면 공유를 하기 때문이다.

반대로 지역변수는 만들어도 된다. 왜냐하면 메소드는 호출될때마다 스택을 형성을 하기 때문에 ssar, cos,love 면 join 스택이 3번 생기기 떄문이다.

즉, 분리된 메모리 영역을 가지고 있는 것이다.

하지만

이 부분은 싱글톤 패턴에서는 공유하기 때문에 매우 위험하다.

이게 멀티스레드 환경에서 전역변수를 잘 못 사용하면 매우 위험하다.

그냥 상태변수를 두지 말자.

다시 cookie로 돌아가자

request와 resposne는 DS가 주입을 해준다

즉, 메소드를 호출할 때마다 지역변수가 따로 관리가 된다.

왜냐하면 스택이 따로 관리되기 때문이다.

request.getCookie() -> 자기가 들고있는 쿠키가 cookies에 전부 담기게 된다.

그리고 쿠키의 사이즈만큼 돈다

만약 쿠키가 없으면 돌지않는다.

쿠키를 세팅해서 응답하는 장바구니를 만든다.

처음 이 코드를 보면 어렵기 때문에 간단한 코드를 먼저 보자

responseHeader에 Set-Cookie에 ssar을 넣는다. 그러면 브라우저는 쿠키의 Key, value 형태로 저장을 한다.

다시 커스텀 해보자

그러면 브라우저는 responseHeader에 setCookie가 있구나 하면서 자기의 쿠키 영역에 저장을 한다.

Set-Cookie가 아니면 저장을 하지 않는다.

쿠키 만드는 것은 응답의 헤더에 걸어주면 된다.

간단하게 만드는 방법

이전의 방법과 동일한 방법이다

이제부터 항상 requestHeader에 cookie를 들고 간다.(프로토콜)

근데 로그인 이후 보안을 해줘야하니까 HttpOnly를 설정해주는 것이다.

한번 httpOnly가 false일때를 해보자

따라서 탈취 가능

본 코드

쿠시 시간 설정 + HttpOnly 설정 + addCookie + cart 대신 box로 바꾸자

실행

박스쿠키가 있으면 기존값 100에다가 /를 걸으라는 것 -> 100/200/300

그리고 실행을 하면

여기서 %2F -> /

예전 장바구니 - 세션, 쿠키

최근 DB에다가 담는다!

수업 끝

profile
블로그 이전 : https://medium.com/@jaegeunsong97

0개의 댓글