스프링 시큐리티와 jwt, Oauth2
스프링 시큐리티를 위한 사전 지식
- 세션
- 유저가 웹 브라우저를 키고 주소창에 www.naver.com처럼 입력(GET요청)
- 서버에서 메서드에 따라서 html파일을 반환해줌
- html파일에는 헤더와 바디가 있는데, 헤더에 쿠키라는 것을 넣어서 반환해줌(SESSION ID에 아이디가 담겨서 옴)
- 클라이언트의 두 번째 요청부터는 그 세션아이디가 요청에 담겨서 가게됨
- 서버는 돌대가리라서 세션아이디가 없으면 처음왔는지 두번왔는지 기억을 못함 ㅋㅋ
- 서버는 세션아이디를 발급할 때마다 그 목록을 생성해야함(누가 어떤 세션아이디를 가지고있는지 알기위함)
세션이 제거되는 때는 서버에서 제거하거나, 사용자 브라우저가 전부 종료되거나, 시간이 지나면 삭제된다.
- 단점
- 클라이언트가 엄청 많을때 로드밸런싱이 일어나는데, 이때 서버는 여러개가 된다.
- 여러개의 서버가 존재할 때, 요청이 어떤 서버에 가느냐에 따라서 세션도 달라진다.
- 여러개의 서버가 같은 DB를 공유하는 방법이 있지만, DB가 하드에 존재할 때는, 겁나게 느리다
- 이 단점을 해결하기 위해 메모리 서버라는 것을 쓴다.(Redis 등)
- 네트워크 7계층
- 물 데 네 트 세 프 용
- 응용계층
- 어떤 프로그램을 쓰는지 (ex:롤, 쇼핑페이지) 어떤 행동을 하는지 (ex:궁극기 사용, 로그인) - 어떤 때는 암호화가 필요하고, 어떤 때는 이미지를 압축해야함!
- 프레젠테이션
- 압축, 암호화가 일어나는 계층
- 세션
- 보내는 데이터가 인증이 되어있는지 체크하는 계층
- 트랜스포트
- TCP통신을 할지, UDP통신을 할지 결정하는 계층
- TCP통신
- TCP통신은 통신을 보내고, 통신에 대한 답변이 오는걸 기다린 뒤, 그 다음 데이터를 보내기 시작한다.
- 만약 답변이 오지 않는다면, 그 부분의 데이터를 재전송한다.
- 답변이 와야 데이터를 전송하고, 데이터가 오지 않으면 데이터를 재전송한다는 점에서, 신뢰성있는 통신방법임
- UDP통신
- 답변을 기다리지 않고 데이터를 전송
- TCP에 비해서 빠르지만 신뢰성 없는 통신
- 주로 실시간 통화등에서 사용됨
- 인증등에서는 사용되지 않음(데이터가 중간에 유실될 수 있기때문)
- TCP통신
- TCP통신을 할지, UDP통신을 할지 결정하는 계층
- 네트워크
- IP (WAN) - 상대방의 네트워크 위치를 특정하기 위한 IP
- 데이터링크
- LAN 공유기 내부에서의 통신을 담당함 공유기 내부에서 어느놈이 내가 데이터를 보내고 싶은 놈인가를 판가름해줌
- 물리계층
- 랜선
회사에서 다른 회사로 안녕이라는 정보를 전달할 때를 가정해보면,
사장은 안녕이라는 데이터를 다른 회사에 보내라고 과장한테 전달
과장은 아래 외국인들이 데이터를 알아들을 수 있도록 중국어로 번역해서 대리에게 전달
대리는 중국어로 번역된 데이터를 잘 포장해서 사원에게 전달
사원은 포장된 데이터를 데이터를 받을 회사의 사원에게 전달
데이터를 받는회사 B에서는
사원이 포장된 데이터를 받아서 대리에게 전달
대리는 포장을 풀어서 과장한테 전달
과장은 중국어로 된 데이터를 한국어로 다시 번역해서 사장에게 전달
사장은 이제 한국어로 된 안녕이라는 데이터를 받아볼 수 있음
- CIA
- 보 - 안
- 문서를 전달할 때 누군가 훔쳐가거나, 변조하여 전달할 위험이 있음
- 그래서 등장한 것이 CIA다
- C 기밀성 - 내가 원하는 사람이 아니라 다른 사람에게 문서가 공개되었을때 기밀성이 깨졌다고 표현한다.
- I 무결성 - 문서에 변경이 있을경우 무결성이 깨졌다고 표현한다.
- A 가용성 - 내가 원하는 문서에 제대로 접근이 가능한지 변조가 일어났거나 중간에 빼돌려질 경우, 내가 제대로 사용할 수 있는 상태가 아니기 때문에 가용성이 깨졌다고 표현한다.
- 만약 편지를 전달한다고 했을때, 그 편지가 빼돌려진다면 가용성이 깨지는 것이고, 편지를 누가 다시 쓴다면 무결성이 깨지는 것이고, 이미 다른 사람에게 넘어갔으므로 기밀성이 깨지는 것이다.
- 이 편지를 지키기 위해 여러가지 장치를 한다.
- 일단 편지를 지킬 호위대를 만든다. - 가용성을 지키기 위함
- 이 경우, 뺏는 사람이 호위대랑 같거나 높은 전투력을 가졌을 때, 편지가 빼앗길 수 있는 가능성 이 있음 - 아무리 준비해도, 가용성이 깨질 수 있음
- 편지를 지킬 금고를 만들어서, 그 금고에 편지를 보관한다.
- 호위대가 다 죽어도, 열쇠가 없으면 데이터를 볼 수 없기 때문에, 가용성이 깨져도 무결성은 지킬 수 있음
- 일단 편지를 지킬 호위대를 만든다. - 가용성을 지키기 위함
위와 같은 방법으로 편지를 지킬 수 있지만 아직 여러 문제가 남아있음
- 열쇠 전달 문제
- 문서 위조 전달 문제
- 데이터를 전달 받는 사람(편지를 받는사람) 이 내용을 보기 위해서는 열쇠를 전달 해야함 - 탈취위험있음
- 만약 중간에 데이터를 탈취한 사람이 나인척하고 데이터를 보낸다고 했을때 어떻게 내가 아닌 것을 알아챌 수 있나 - 인증문제
- 이 두개의 문제를 해결하기 위해서 나온것이 RSA라는 보안방법
- RSA(암호화)
- RSA에는 Public key(공용키) 와 Private key(개인키) 의 키쌍이 존재한다.
- 메세지를 전달할 때, 공용키 또는 개인키로 암호화해서 전달한다.
- 그 암호화된 키는 공용키로 암호화 했으면 개인키로 해제가 가능하고, 개인키로 암호화한 경우에는 공용키로 암호해제가 가능하다.
- 개인키는 자기만 가지고 있고, 공용키는 누구에게나 전달해도 괜찮다.
- 개인키를 전달할 경우, 내 공용키로 암호화된 모든 문서를 볼 수 있기 때문에, 개인키는 절대로 공유하면 안된다.
- B라는 사람에게 메세지를 전달할 때, B의 공용키로 암호화하면, B만 열어볼 수 있는 문서가 된다.
- A라는 사람이 B에게 메세지를 전달할 때, A의 개인키로 암호화하면, A가 메세지를 보냈다는 증거가 된다
- 그래서 메세지를 전달할 때, A 가 B 에게 메세지를 전달할 경우에는, 메세지를 B의 공용키로 암호화 한 뒤, A의 개인키로 다시 암호화(이때는 전자서명이라고 한다.) 하여 전달하게 된다.
- 이 방식이 RSA다
- 벨 연구소를 알아야 함
- 벨 연구소에서 내부망을 구성해서 자기들끼리 통신하고있었음
- 다른 연구소에서도 내부망을 구성해서 자기들끼리 통신하고있음
- 다른 연구소에 데이터를 보낼 때나 요청할 때 누구(IP와 포트번호)에게 보낼건지, 어떤 데이터를 어떤식으로(쿼리스트링)으로 받을건지를 정함
- 이런 방식을 정한 문서가 RFC방식임
- 다른 연구소와 연결 될 때마다 문서가 늘어나서, 겁내 많아짐
- 이 문서의 규칙이 작동되는것이 www 월드 와이드 웹임
JWT(JSON WEB TOKEN)
구조
- 헤더
- 암호화에 사용된 알고리즘과 토큰의 유형을 전달
{ "alg" : "HS256" "typ" : "JWT" }
- payload
- 유저 정보를 특정할 수 있는 정보를 넣어서 전달
- 서명
- 헤더랑, 정보랑, 나만 알고있는 키(개인키 또는 Secret)로 암호화를 함(서버만 알고 있는 키) - 개인키로 암호화하면 공용키로 풀 수 있음(RSA)
- HMAC → 시크릿 키를 포함한 암호화 방식
- SHA256 → 해쉬 암호화
인증이 완료 될 때, JWT를 클라이언트에 전달(웹 브라우저의 로컬스토리지, 세션스토리지 등에 저장하게 됨)
검증이 필요함
- 헤더와 payload 를 일단 base64 방식으로 인코딩함
- 인코딩한 값을 secret(서버가 지정한 비밀키)를 통해 암호화 한 값이 시그니쳐에 들어감
- 만약 RSA방식을 사용한다고 하면, 서버의 개인키로 암호화(서명) 하면 됨(공용키로 풀릴경우, 내 서버에서 쓴 데이터인것이 확실하기 때문)
요청의 순서
- 로그인을 함
- 서버에서 유저의 정보가 들어간 JWT를 발급함 (암호화 해서 다 넘김)
- 이후 유저가 유저의 개인정보가 들어간 페이지를 요청함 - 요청의 헤더에 JWT가 들어가 있음
- 서버는 JWT의 Header부분과, payload부분을 똑 떼서, 자기 서버의 시크릿 키로 암호화 해봄(또는 공용키로 풀어봄)
- 만약 암호화한 것이 JWT의 서명부분과 같다면, 서버에서 발급한 정보니까 믿고 정보를 주어도 됨
- payload에 들어있는 유저의 정보를 기반으로(findByUserId)를 해서 유저의 개인정보를 데이터베이스에서 꺼내옴
- 이후 유저에게 데이터가 전달됨

시큐리티를 설치하게 되면 모든 url이 잠겨버린다.
로그인을 할 경우 세션이 만들어지고, 저장이 되는데 그게 바로 principal이 된다.
인증이 안된 사용자들이 출입할 수 있는 경로를 /auth/**으로 허용
(만약 회원가입이 안된 사용자가 회원가입페이지가 가는데 로그인페이지가 뜨면 안되니까)
@EnableWebSecurity
어노테이션은 시큐리티 필터 동작을 이 설정파일을 통해 한다는 것이다. 이 파일이 없어도 접근제한이 되었지만, 이 어노테이션을 쓰는것으로써 config를 따로 해줄 수 있다.
시큐리티 로그인의 흐름
- 시큐리티가 적용된 서버에 http로 로그인 요청이 들어온다.
- 시큐리티가 그 요청을 가로챈다.(Filter이기 때문에, Dispatcher Servlet 에 도달하기 전임)
- username과 password로 로그인이 진행이 된다.
- 로그인이 성공한 경우 시큐리티 전용 세션에 유저 정보가 저장이 된다.
- 그 전용세션에 저장되는 타입은 UserDetails 라는 타입만 저장이 된다.
- 그래서 UserDetails라는 타입을 구현(implements) 해서 사용하던가, 다른방법이 또 있다.
비밀번호는 무조건 해쉬 암호로 암호화가 되어야 시큐리티에서 저장이 된다.(스프링 시큐리티 5부터 적용)
해쉬는 무조건 고정적인 길이로 변경이 됨 (asdf를 해쉬화 해도 8자리 300p짜리 책을 해쉬화해도 8자리가 나옴)
(5:07) UserDetails타입을 생성하는 장면 참고 - PrincipalDetail implements UserDetails
스프링 시큐리티가 로그인 요청을 가로채서 로그인을 진행하고, 완료가 되면 UserDetails타입의 오브젝트를 스프링시큐리티의 고유한 세션 저장소에 저장을 해준다.
시큐리티가 대신 로그인 해줄 때, 비밀번호가 해쉬화가 되는데, 그 해쉬화가 어떤걸로 되었는지 알아야 같은 해쉬로 암호화해서 DB에 있는 해쉬랑 비교할 수 있음
auth.userDetailsService(null).passwordEncoder(encodePWD())//encodePWD가 해쉬화한 암호방식임
이 해쉬 방식을 누구한테 알려줘야 하냐면, UserDetailsService한테 알려줘야 함. 그 객체를 나중에 위 코드의 null자리에 넣음 - UserDetailsService는 실제로 로그인을 진행하는 객체
loadUserByUsername(String username) 이라는 함수는
스프링이 로그인 요청을 가로챌 때, username, password 변수 두개를 가로채게 되는데, password는 알아서 처리하게됨
일단 용어정리
- principal : 접근자
- authorizeRequest()⇒ 요청이 들어오면
- .antMatchers(”/auth/**”)⇒/auth경로아래의 어떤 요청이 오면,
- .permitAll() ⇒ 모든 요청을 허용함
Uploaded by N2T