[Springboot] Springboot Security

@Configuration // IoC 빈(bean)을 등록
@EnableWebSecurity // 필터 체인 관리 시작 어노테이션
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) // 특정 주소 접근시 권한 및 인증을 위한 어노테이션 활성화
public class SecurityConfig extends WebSecurityConfigurerAdapter{



@GetMapping("/info")
public @ResponseBody String info() {
  return "개인정보";
}

20210719_224653

로 그냥 /info로 들어가면 아무나 들어가 졌지면 위의 securedEnabled = true) // 특정 주소 접근시 권한 및 인증을 위한 어노테이션 활성화를 시키면 이제 아무나 못 들어 가게 막는다.

20210719_225529

20210719_230227

이제 admin이여야만 들어가진다.

@PreAuthorize 도 있는데 이건 해당 어노테이션의 메서드가 실행 되기 전에 실행된다

//	@PreAuthorize("ROLE_USER")	//이렇게 하면 안먹는다.
	@PreAuthorize("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
	//하나만 걸고싶으면 hasROle하나만 걸면된다(근데 하나 걸바엔 Secured)쓰는게
//	,근데 여러개는 hasrole로 가능하다.
	//이건 메서드가 실행 되기 전에 실행된다.
	@GetMapping("/data")
	public @ResponseBody String data() {
		return "데이터 정보";
	}


@PostAuthorize 이것도 있는데 이건 반대로 메서드 종료 하고 나서 실행된다.


스프링 시큐리티는 시큐리티 세션이 있는데 원래 세션에 시큐리티가 관리하는 세션이 따로 있다. 그리고 이 안에 들어가는 타입은 Authentication 객체밖에 없다.

이 객체가 안에 들어오고 그걸 꺼내올 수 있다.

그리고 이걸 필요할 때마다 DI할 수 있다. 20210720_012645

첫번쨰는 userDetails타입, 그리고 두번쨰는 OAuth2User타입이 들어갈 수 있다.

20210720_012558

일반 로그인 떄는 얘를 적고

20210720_012606

OAuth 떄는 얘를 쓰면 컨트롤러엔 뭘 써야하나?

우리가 세션 정보 찾을때 처리가 복잡하니까 PrincipalDetails로 이 두가지를 묶는다 그래서 우리는 이 PrincipalDetails로 묶게 프로그램을 짠다.

20210720_012645

20210720_013633


특정 메서드에 간단하게 걸고 싶으면 이렇게 걸면 된다.

20210715_184923

20210715_185819

Oauth 로 로그인 하면 authentication의 객체로 들어오게 됨.

스프링 시큐리티는 자기만의 세션을 들고 있음. 시큐리티인데 타입은 Authentication 객체밖에 없는데 쏙 들어와야됨.

저 안에는 userDetails 타입과 Oauth2User타입이 들어갈 수 있다.

userDetails -> 일반 로그인 OAuth2User -> OAuth 로그인

OAuth2User와 userDetails 타입 세션에 저장돼었는데 떙겨옴 근데 user를 찾을 수가 없음. 그래서 PrincipalDetails 에 userDetails를 구현해서 user를 안에 넣는다.

구글, 페이스북, 네이버 로그인


네이버 로그인

일반적으로 스프링부트는 구글, 페이스북 트위터 등은 스프링부트가 oauth로 일반 제공해주는데 이건 잘 제공 안해줌. 예를들어 네이버는 한국꺼인데 대한민국에서만 유명, 그래서 Provider로 제공 안해준다.

20210720_225925

네이버는 기본적으로 provider가 아님.(다른거 하기엔 막 다른 나라들 포털 대표사이트 있을텐데 이걸 다 제공하면 스프링부트 디펜던시는 정신 나갈거다)


스프링 부트 시큐리트는 방식이 몇개 있는데

Authorization Code Grant Type방식

20210720_231943

Client Credentials Grant Type 방식

이렇게 있다.

출처: https://blog.naver.com/getinthere/222064999924

우리가 사용하는 방식은 코드를 부여받음.(Authorization Code Grant Type방식)

코드를 부여받고 이 코드로 액세스 토큰을 응답받고 요청한 데이터를 응답한다.

security:
  oauth2:
    client:
      registration:
        google: # /oauth2/authorization/google 이 주소를 동작하게 한다.
          client-id: 391208636966-ep51cg8drdfra574ql9scga7qnctcs17.apps.googleusercontent.com
          client-secret: S-NWrO4VpBTulsjStErAXFy3
          scope:
          - email
          - profile

        facebook:
          client-id: 210771624270218
          client-secret: 3658a4ba647970d620b9a0fed65f06f1
          scope:
          - email
          - public_profile #https://developers.facebook.com/docs/facebook-login/web 여기서 제공해주는 스코프 이름을 정확하게 적어야 한다.

        naver:
          client-id:
          client-secret:
          scope:
          - name
          - email
          client-name: Naver
          authorization-grant-type: authorization_code
          redirect-uri: http://localhost:8080/login/oauth2/code/naver #코드를 받는 콜백 주소. 근데 이거 안적어줘도 됨(그렇게 설정 되어있어서)
          #근데 네이버는 프로바이더가 설정이 안되서 적어줘야한다.
          #네이버는 주소가 고정이 안되어서 맘대로 만들기 가능
          #그래도 이 규칙에 맞춰서 적어주는게 편하다.

어플리케이션 프로퍼티에 시큐리티 Oauth2 부분 보자

네이버 개발자 센터에 가서 애플리케이션 등록에 간다음에

20210721_000416

20210721_000423

아이디, 이름만 체크하고

윗 부분은 8080포트 , 아래는 콜백 주소를 적어주고 등록한다.

그럼 클라이언트와 시크릿이 나올건데

20210721_001358

20210721_001458

이걸 id,secret부분에 넣어주자

20210721_001622

그리고 provider가 아니라 저장하면 오류날 거 EntityManager가 close 됐는데 authorizedClient에 네이버를 저장할 수 없음(provider가 아니라서)

네이버라는 레지스트레이션이 없어서 이걸 등록해줘야한다.

20210721_005630

이 주소로 요청하면 네이버 로그인이 되게 된다.

20210721_005709

프로필 주소 또한 마찬가지

네이버는 프로바이더를 등록해줘야한다가 중요한 점.

20210721_012734

네이버 로그인이 생겼고

20210721_012801

아직 컨트롤러 설정을 안해서 에러가 났지만 이 콘솔에 찍힌 정보들이 네이버 로그인 했을 때 response로 들어가서 받는 거다.

20210721_013012

20210721_013626

컨트롤러에 추가하자

20210721_013012

그리고 여기 response 부분을 받기 위 getAttribute안에 있는 response값을 넘겨주면 된다.

getAttribute를 안이 맵이 되므로 Map으로 잡자.

20210721_015350

네이버가 이렇게 리턴을 해준다.

20210721_015425

리턴 된건 여기 들어가고.

그 다음에 response안에 저 정보가 있는게 아닌 response안에 정보가 있으면

20210721_015638

이렇게 적으면 되지만(뒤에 get(“response”))가 필요 없음) 뒤에 붙이는 이유는

response안에 response안에 있기 떄문(한번 더 들어가기 때문)

20210721_015620

20210721_015942

이제 네이버 로그인이 잘 들어간 게 보인다.

20210721_021208 이제 /user로 가면 콘솔에 로그인 한 경로가 보인다.

구글 로그인도 마찬가지.


JWT를 이해하기전 세션에 대해 알아보자

중요: 왜 사용되고 어디에 쓰는지.

20210805_170735

웹에서 겟요청으로 주소를 요청하고

서버는 그 주소를 넘겨준다(html) 이떄 http헤더에 넣고 해주는데 헤더에 뭘 담아주냐면 쿠키라는 걸 담아준다. 쿠키에 세션 아이디를 준다. 이걸 담아서 주는데 웹 브라우저는 세션 브라우저를 받아서 자동으로 준다.

최초요청으로 세션이 만들어짐. 2번쨰로 리퀘스트 할 때는 세션 ID를 달고 감. 헤더는 세션아이디 다시 또 넘겨준다.

만약 처음오면 카드를 가지고 와야되는데 처음에 오면 카드가 없다. 그래서 서버는 카드를 만들때 마다 목록을 가지고 있어야 한다.

처음 만든게 겟인데어고 다른건 만든 적이 없다.

두번쨰로 요청할 때 카드 등록하고 목록에 넣음. 없으면 만들고 있으면 카드 보는거.

세션아이디는 최초 요청에만 만들고 계속 지속적으로 요청시마다 들고옴.

세션은 2가지 경우 사라지는 데,

  1. 세션을 서버에서 날릴 때

  2. 사용자가 브라우저를 다 닫는 경 그럼 세션값이 날아감.

서버에 있는 값은 살아있지만 서버에선 카드 들고가지 않으므로

  1. 시간 주고 지나면 사라지게함.

세션이라는 건 로그인 요청(인증)을 하면 클라이언트가 최초 리퀘스트 하고 서버는 세션이라는 저장소에 뭘 만드냐면 세션 아이디를 하나 만든다.

20210806_175201

  1. 클라이언트가 서버에 리퀘스트 함.

  2. 세션이라는 저장소에 세션아이디를 만듬.그리고 얘가 들고있는 자그마한 공간이 생김(세션 저장소는 되게 큼)

  3. 그리고 세션 아이디를 서버에서 클라이언트에 돌려줌.

  4. 클라이언트쪽 웹 브라우저에 세션아이디 1234가 저장된다.

  5. 그 다음에 로그인 요청을 한다.

  6. 서버는 확인해서 데이터베이스에 넣는다. 정상이면 그 사람의 유저정보를 저장한다.

  7. 저장하고 로그인이 성공하면 보통 메인페이지로 리턴한다.(html).

  8. 그 다음부터 다시 인증이 필요한 걸 요청 유저정보 요청.

유저 정보에 요청하니까 세션이 있는지를 확인한다. (세션 아이디 1234가 있는지 찾음)

  1. 유저정보가 있으면 로그인 한 사람인걸 확인하고

  2. 데이터 베이스에 응답을 받음(사용자 정보를)

  3. 사용자 정보 응답받고 돌려줌

세션은 이 과정을 계속 반복함.

세션을 통해 민감한 정보를 있는지 확인하고 응답하고 돌려준다.


세션의 단점?

20210806_175239

클라이언트가 서버에 요청할떄 정보를 리턴하는데 클라이언트가 만명이거나 너무 많아지면? 100만명이면/

동접자 수가 천명씩 되는데 서버가 동시접속자를 100명 처리하면 나머지 900명이 기다려야 한다.

그럼 서버 여러개 해서 이 서버가 너무 바쁘면 다른 서버에 요청하고 이런게 로드 밸런싱이라 하고 부하를 분산시킴.

리퀘스트 요청할 때 로그인 요청함. 세션이라는 곳 만들고 로그인 응답하는 그 다음 다시 그 서버에 요청하면 아무 상관이 없음. 그럼 세션이 있어서 그 응답자인걸 알 수 있지만 만약 다른 서버로 가면 그 클라이언트는 그 서버에서 세션에 없으니까 그 세션값이 없으므로 처음 온 것으로 인식.

그래서 스티키 서버 만들어서 로드밸런싱익 뭐고 2번쨰 요청에 달라붙게 해서 인식하거나(그럼 세션을 무조건 거치게 하거나) 혹은 세션을 다 복제하거나.

세번째로 좋은 방법은 세션에 정보 저장하는게 아니라 서버들이 db에 세션값을 넣어놓고 그 세션값을 공유해서 쓰는것.

세션은 서버가 들고있는데 메모리에 접근해서 값을 가져와서 되게 빠르다. 근데 서버에 접근해서 찾으면 하드디스크에서 찾아야되는데 그럼 엄청나게 드려진다.

데이터 베이스에 데이터 베이스를 넣으면 되게 느려진다(io가 일어남)

그래서 메모리 공유 서버를 쓰면 io가 일어나지 않지만 얘는 램만 있고 하드는 아님.

그럼 세션값 공유하면 아까와 같은 문제가 일어나지 않음. 그래서 Redis 쓰고 이러는데

jwt 쓰면 위의 세션의 단점들을 다 해결이 가능하다.


JWT를 이해하기전 TCP에 대해서 알아보자

통신 : OSI 7 계층

물데네 전세표응

내가 메일을 보내기 위해서 롤이라는 게임을 하는데 야스오라는 캐릭터로 상대에게 궁을 쓰면 궁이라는 데이터가 전달이 되어야 한다.

응용 계층에게 얘가 롤 프로그램을 쓰는게 응용계층에게 넘어감.

tcp와 udp 통신의 차이는 tcp로 A가 B에게 안녕이라는 걸 보내면 ACK 를 돌려주고 드 다음 반가워를 보낸다.

만약 ack를 돌려받는 중 유실이나 낚아채가나 통신이 안되면? 계속 반가워를 보낼거. 그럼 ack라고 응답하고 또 응답하고 이런 tcp는 신뢰성 있는 방식(항상 확인함)

UDP

는 ACK 따위 신경쓰지 않고 계속 보냄(신뢰성 따위 없음)

도착 안해도 계속 요청 보냄.

만약 안녕 반가워에서 끊겨서 안녕 반워 이런식으로 가도 udp로 하면 사람이 받으면 안녕 반가워라고 인식은 할 수 있긴 하다.

20210806_182215

데이터 링크계층에서 ip로 어떤 집을 찾아갔는데 공유기가 하나 있고 컴퓨터가 4개가 물려있으면 내부망을 Lan이라 하고 바깥걸 Wan이라 한다.

20210806_182702

20210806_182831

tcp 통신할 떄 보안문제와 어떤 문제가 있는지 알아야 한다.


JWT를 이해하기전 CIA에 대해서 알아보자

A,B,C나라가 들고있는데 A,B나라가 동맹으로 C나라 안 들키게 문서 전달하려 한다.

어떤 문서를 그냥 들고가면 C나라에서 그냥 낚아채서 훔쳐서 어떤 데이터 보내는 지 알 수 있다.

이걸 문서의 CIA라고 한다.

C나라가 문서 획득하면 기밀성이 깨짐.

C나라가 문서 들고가서 새로운 문서를 B나라로 전송할 수 있는데 이건 무결성이 깨짐.(문서 변경을 막는게 무결성)

이때 문서 보낼떄 문서 지키는 최고의 병사들을 같이 보내면 쉽게 안 뻇길거(이게 가용성)

20210806_183301

근데 C나라에서 죽이면 무결성이 또 깨지므로 이 문서를 암호화 한다.

20210806_184122

문서를 암호화하면 기밀성은 유지가 됨.

금고를 A라는 열쇠로 잠글 수 있는데 금고 자체를 가져가면 무결성이 꺠짐. 그래서 벽에 딱 붙여놔야 금고 무결성을 가질 수 있다.

열쇠 있다고 무결성 지킬수 있는게 아님. A라는 열쇠를 가지고 금고 달라붙으면 가용성도 지킬 수 있다.

근데 문제가 있는데 A라는 열쇠로 잠궜는데 B나라 A열쇠가 필요한데 C나라가 가로챌 수도 있다.

20210806_191226

그래서

  1. 열쇠 전달 문제
  2. 문서가 누구로 부터 왔는지

이 두 부분이 해결되면 된다.


JWT를 이해하기전 RSA에 대해서 알아보자

  1. 열쇠 전달문제

  2. 인증문제(누구인지)

RSA(암호화)
  • public key: 공개키
  • private key :개인키

A가 B에게 사랑해라고 전달하고 싶은데 이 메시지를 공개키로 암호화 함.

그래서 얘를 보냄.

B에 공개키(사랑해)가 있고 이걸 개인키는 A만 들고 있어야 하고 각 공개키는 블로그 같은 데에 올려도 상관이 없다. 근데 개인키는 개인만 들고 있어야함.

키가 하나만 있으면(키 하나로 잠그고 열고 다하면) 시멘트릭키라 하고 (대칭키)라고 한다.

B의 공개키는 블로그에 공개되어 있어서 잠구고 하는데 해커가 이걸 가로챈다.

B의 공개키로 잠겨있기 떄문. 해커는 B의 개인키로 열어야 되는데 해커는 개인키가 없으므로 열지 못한다.

20210806_191226

그럼 이거로 1. 열쇠 전달 문제가 해결된다.

공개키 -> 개인키(암호화)

개인키 -> 공개키(전자서명)

  1. 문서 받으면
  2. A의 공개키를 열어본다

20210806_195217


JWT를 이해하기전 RFC문서란

20210807_022519

public 과 private key가 있는 암호화 방식 사용.

RFC 7519 라는 문서에 정의되어 있는데 RFC에대해 좀 아록 가야한다.

프로그램 초보나 개발 몇년차가 되도 RFC 모르는 사람이 있는데 이런거 하나하나 쌓아야 한다.

프로그래밍을 수박 겉핥기로 기초 안하고 공부하면 실력이 빨리 늘 수 가 없다. 그리고 문서 읽을떄 이게 뭐고 RFC가 뭐냐 jwt가 뭐냐 이런 문서를 읽다보면

이런 개념이 안 잡혀서 영어를 잘한다고 읽을 수 있는게 아니다.

Http: 벨 연구소에서 www(world wide web)이 튀어나옴.

내부 망이 있는데 자기들 끼리 통신하려면 선이 연결되어 있어야 한다.

선이 연결되어 있으면 그 선으로 데이터를 송 수신하면서 잘 쓰고 있었다.

모 대학에서 똑같이 내부망을 구성하고 자기들 끼리 쓰고 있었다. 이 상황에서 A대학이 벨 연구소와 통신하고 싶었는데 두 곳에서 내부망이 약속을 하고 만든게 아니라 서로 달랐을건데 이 두 내부망이 통신하려면 약속된 규칙이 필요하다.

? 뒤는 데이터니까 그걸 파싱해서 처리하라고 약속된 규칙 만들어야 한다.

20210807_024650

그리고 약속된 규칙 하나가 RFC의 1번문서(최초 약속) 그리고 이 약속된 방식을 프로토콜이라고 한다.

그러다가 B대학이 생기고 얘들이랑 통신하고 싶은데 벨 연구소와는 통신 되는데 A대학엔 프로토콜이 없어서 통신이 안됨. 그래서 RFC 2번문서가 만들어지고 네트워크가 이렇게 연결되고 연결 될떄마다 문서가 쭉 생김.

20210807_025553

이런 문서들이 쭉 연결되서 만들어 진게 WWW이다. 이 약속이 Http 프로토콜이다.

예를 들어 K대학이 여기 참여하고 싶은데 규칙을 요청함(적용해 달라고) 근데 너무 비대해 지면 뚫고 들어가기 어려워진다.(내 규칙을 나머지 애들이 다 동의해야됨) 안 그러면 RFC 문서로 안 만들어진다.

RFC 문서가 처음에는 잘 만들어지다가 규약이 점점 많이 생겼다.

20210807_025856

처음엔 동의를 얻는거라 동등한 관계였는데 지금 만들어지는 문서는 동등하지 않음. 어마어마한 생태계에 추가르르 하려면 동등한 규약이 되진 않을 것.

20210807_025843 RFC문서에 대해 이해하면 된다. 그게 7519에 만들어 진게 JWT(Json Web Token)이다.


JWT 구조 이해

JSON 웹 토큰이란 무엇입니까?

JWT(JSON Web Token)는 당사자 간에 정보를 JSON 개체로 안전하게 전송하기 위한 간결하고 자체 포함된 방법을 정의 하는 개방형 표준( RFC 7519 )입니다. 이 정보는 디지털 서명되어 있으므로 확인하고 신뢰할 수 있습니다. JWT는 비밀( HMAC 알고리즘 사용) 또는 RSA 또는 ECDSA를 사용하는 공개/개인 키 쌍을 사용하여 서명할 수 있습니다 .

출처: https://jwt.io/introduction

  • JSON 웹 토큰은 언제 사용해야 합니까? -> 이건 실제 쓰면서 보자

구조는 xxxx부분이 헤더, yyyy가 유효 탑재 부분(payload), 그리고 zzzz가 시그니쳐 부분.

헤더는 정보,

헤더로 무엇을 사용했다 알고리즘을 무얼 썼다 이런걸 명시한다.

base64는 암호화 하고 복호화 할 수 있는 거.

스프링에서 암호화 하면 해싱이 되서 복호화가 안됨. 암호를 찾기 위해선 비밀번호 초기화를 해야 한다.

그게 해쉬다.

base64는 암호화 하고 디코딩이 가능하다.

정보는 클레임을 가지고 있는데 등록된 클레임과 개인 클레임으로 이뤄지는데 등록된 클레임은 꼭 필수조건은 아니지만 정의된 클레임.

개인 클레임은 json 웹 토큰에 유저정보에 필요한 공개되도 되지만 특정 될수 있는 개인키를 넣어줄 것.

헤더 부분은 어떤 알고리즘 써서 서명하는지, 페이로드 부분은 정보들 담을수 있다. 마지막 시그니쳐(서명)부분은 만든 헤더와 페이로드 정보와 나만 알고있는 개인키를 HMAC으로 암호화를 한다.(Hs256) HMAC Sha 256으로 암호화를 한다.

클라이언트랑 서버가 있는데 클라이언트가 id, pw를 전송한다 그럼 서버가 원래 세션을 만들었는데 이제 json web token을 만들어 줄 것. 그럼 서버가 헤더를 만들고 페이로드를 만들고 시그니쳐를 만든다.

헤더에는 HS256으로 서명을 했다는 거고 페이로드에는 유저 네임, 패스워드 (유저정보) 시그니쳐는 HS헤더와 페이로드를 더해서 이 두개에다가 서버만 알고있는 키값을 (서버만 아는 키값을) cos라는 정보를 HS256으로 암호화한다.

HS256은 HMAC이라는 방식으로 암호화하는데 SHA256-> 해쉬화해서 복호화 하는 암호를 만들고

HMAC은 시크릿키를 포함해서 암호화한다. 그 다음 뒤에 있는게 SHa 256 암호고 이걸 base64로 인코딩을 한다.

20210807_151451

보통 클라이언트에 보면 로컬스토리지가 있다.

key : value 에 대해 저장 해놨다가 요청을 하면 ( 내 개인정보를 달라고) 그럼 서버가 세션이 없으니까 jwt를 준다. 어떻게 보내냐면 로컬스토리지에 저장한 걸 들고 요청한다 서버가 jwt를 받으면 신뢰할 수 있는지 검증이 필요하다.

이 시그니쳐에 hs256으로 암호화 된 내용을(ABC5)라는 정보가 있으면 이걸 받아서 이걸 뭘해야 되냐면 헤더, 페이로드, 시그니쳐 이런식으로 안에 데이터가 뭔지 아는게 중요한게 아니라 이 제이슨 웹 토큰이 유효한 토큰인지 아는게 중요하다.

20210807_151903

만약 jwt시그니쳐 부분에 abc5로 같으면 이전에 왔던게 맞다고 인식한다.

서버에 개인정보가 맞는지 확인은 payload에 유저네임이 있다.

json web token에 헤더랑 페이로드랑 시그니쳐가 있었는데 이 시그니쳐를 봤을 떄 헤더랑 페이로드랑 시크릿 값으로 이 3 값을 HMAC (HS256)으로 암호화 함(secret) 포함해서 암호화 )

근데 RSA 256 을 사용하면 헤더 만들고 RSA 만들고 헤더에 유저네임 (SSAR) 이라 적어주고 RSA에선 공개키와 개인키가 있는데 서버가 자기 개인키로 잠구고 이 파란색 부분의 토큰을 돌려주고 클라이언트가 받아서 서버로 요청할 떄 검증시에 공개키로 시그니쳐를 열어보기만 하면 된다.

20210807_152239

이 RSA로 해도 되고 SHA256으로 해도 되는(공개키로 서명 검증) 보통 SHA256으로 더 많이 쓰다고 한다.

20210807_152627

json 웹토큰이 생긴건 빨간색 헤더, 보라색 페이로드 마지막 시그니쳐인데 각각은 base64로 암호화가 되어있다.

base64로 암호화 되어있으면 디코딩이 가능하고 디코딩 하면 저 값이 튀어나옴. 이건 암호화에 목적이 아니라 서명에 목적이 있음(유효한지 확인이 목적)

데이터가 유효한지 아닌지에 대한 데이터지 이게 비밀성을 보장하나 이런거에 대한 데이터가 아니다.

이걸 풀면 만들어서 클라이언트에 던지고 이 값을 요청시 서버는 base64로 다시 요청해서

20210807_152727

이런식으로 만들어진다.

이걸 들고와서 이게 헤더고 페이로드 , 시그니쳐로 나눈다.

만약 비밀키를 들고와서 똑같이 HMAC Sha256으로 암호화 해봤더니 똑같은 값이 나오면 검증이 끝난다. 이거로 서버들이 여러개 있으면 세션 안쓰고 토큰만 쓰면 되서 클라이언트가 서버 어느곳을 들어가도 유효하다(jwt 토큰만 검증하면 되서 서버들이 jwt 토큰값만 알고 있으면 되므로) 이 jwt가 검증되면 걔가 들고 있는 payload값으로 (유저 정보로) select 하거나 이런식으로 사용 되는데 이건 아무나 못 만들고 서버가 만들어서 유효한 값을 인증한다.


JWT 프로젝트 세팅

20210807_154011

문자열이 되서 (자바에서) +

20210807_154153

이런 부분들을 넣는다.

raw 시그니쳐 부분을 hs256으로 암호화를 해서

그걸 다시 base64로 인코딩 해도 되고 안해도 되고 근데 이걸 암호화 한다. 아무튼 ㅇㅇ

payload가 본문이고 base64부분은 하나하나 만들어도 되지만 라이브러리 쓰면 굉장히 쉽게만들 수 있다.

20210807_154332


jwt를 위한 security 설정

user

@Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String username;
    private String password;
    private String roles;		//user, admin
//    private Role roles;		//이런식으로 Role을 만들어서 처리하기도 가능하다(Role 이라는 클래스 만들고)


    // ENUM으로 안하고 ,로 해서 구분해서 ROLE을 입력 -> 그걸 파싱!!
    public List<String> getRoleList(){
        if(this.roles.length() > 0){
            return Arrays.asList(this.roles.split(","));
        }
        return new ArrayList<>();
    }
}

JPA 자동생성 잔짜잔

20210807_162654

User 테이블 잘 만들어졌다. security 세팅하기 위해 config 만들고 클래스 하나 만들자.

이렇게 되면 내가 시큐리티 쓰지만 세션 안 쓰므로 모든 페이지에 접근이 가능해진다.

이떄까지 만든 시큐리티와 다른건

.sessionManagement().sessionCreationPolicy(SessionC
  reationPolicy.STATELESS) // 세션을 사용하지 않겠다는 뜻

이렇게 세션 없이 쓰(스테이트 리스 서버)

cors정책 안씀(모든 접근 허용) 그리고 폼 로그인을 안쓰는게 가장 큰 차이다.


jwt Bearer 인증 방식

20210808_121747

기본적으로 stateless방식은 세션 안 쓰겠다는 거

기본적으로 웹은 stateless인데 statefull처럼 쓰기 위해 쿠키 만들고 세션을 만듬

그거 안 쓰겠다고 설정 하는거고 http 베이직을 disable하는 게 뭔지 이해해야 한다

기본적으로 클라이언트가 있으면 id패스워드 날리고

최초 요청이 일어나면 서버는 뭘하냐면 서버한테 세션이라는 메모리 영역이 있고 여기에 세션 아이디를 하나 만든다.

id pw 가 정상이면 (홍길동이라면) 이 아이디에 대한 세션 아이디를 만들고 그 홍길동만의 영역을 만들어둔다.

이렇게 만들고 나서 응답을 어떻게 해주냐면 idpw가 정상일 시 유저 오브젝트를 저장하고 세션 아이디를 리턴하는데

클라이언트에 돌려줄 떄 웹 브라우저로 요청하는데 요청 했기 떄문에 돌려 줄 떄 특별한거 안해도 웹 브라우저는 자기 웹 브라우저 프로그램에

쿠키라는 영역에 (json) 세션 아이디를 저장한다. 그리고 그 다음 요청부터 새로운 요청이 일어나면 새로운 요청이 일어날 때 이걸 들고 간다. 그럼 서버는 들고온 서버를 통해 인증을 확인하는게 세션방식.

근데 이 방식이 안 좋은 건 서버가 하나이면 괜찮은데 서버가 여러개가 생기면 안 좋아진다.(서버마다 세션 영역, 메모리가 따로 있기 떄문0

우리가 ajax 쓰면 클라이언트가 js로 요청하는데 (서버에 js로 요청)

자바 스크립트로 요청하면 쿠키 세션 정책은 기본 정책은 동일 도메인에서만 요청일 때 방동을 한다.

어떤 서버가 있는데 a,b,c, 있다고 하면 최초 요청시 자바스크립트로 요청하면 요청에 대해 ip주소는 210.10.10.5로 요청이 들어가면 거부를 함

쿠키가 안 날아감.

쿠키가 안날아가니까 자바스크립트에서 쿠키를 강제로 코드로 담아서 요청이 가능하다.

요새 서버는 http only라 해서 http가 아닌 자바스크립트 이런건 쿠키 아예 못 건드리게 만든다.

웹 브라우저로 요청시 정상적이면 요청을 받아 주겠지만

만약 js 파일에서 fetch로 뭐 이상하게 요청하면 쿠키를 담을 수 있는데

headers라는 영역에 쿠키 담을 수 있는데 이런식으로 요청하면 다 거부한다(서버쪽에서)

이런 요청이 오면 거절하도록 false로 하면 요청이 오긴 오지만 일단은 동일 출처 정책을 써서 다른 도메인에서 요청오면 서버가 이걸 풀어주면 자바스크립트로 장난 칠수 있어서 보안적으로 좋지 않다.

두번쨰로는 쿠키 방식 쓰면 서버가 많아질 수 록 확장성이 굉장히 떨어진다(관리하기가)

Header에 Authorization이라는 키값에 넣고가는 방식이 있는데 여기 아이디랑 배스워드를 헤더에 담아서 요청하는 방식이 있는데 이게 http의 Basic 방식임(인증의 )

이러면 인증을 매번 달고 요청함.

요청 할 때마다 게속 인증을 함 (요청할 때 마다) 이 아이디랑 패스워드가 암호화가 안되서 노출이 될 수 있다. 서버를 https서버를 써야 되는데 s가 secure고 아이디 패스워드가 암호화가 되서 날아간다.

Authorization: 토큰 이건 노출 되면 안됨. 노출이 된다해도 이 자체가 아이디와 패스워드가 아니라 위험부담이 적음

아이디와 패스워드를 통해서 토큰을 만드는데 이 토큰을 달고 요청하는게 Bearer인증 방식이다(Basic 방식이 아니라)

20210808_122740

만약 이게 노출되는데 아이디와 패스워드 로그인 할때마다 서버쪽에서 다시 만들어주기떄문에 한번 노출됐다고 위험하진 않지만 본인이 안 바꾸면 계속 뚫린다. 얘는 어떤 토큰의 유효시간이 있어서 특정 시간이 지나면 노출 되도 유효시간이 10분이라하면 10분 지나면 로그인 토큰 들고있다고 해서 로그인 못함.

이 토큰이 노출되면 다른사람이 로그인 요청을 할 수 있다.

20210808_122718

이 토큰 방식으로 jwt 방식을 만듬.

우리가 이 방식을 쓸거기 때문에 세션방식도 안 쓸거고 기본 인증방식도 안 쓸거고 내가 쓸 방식은 Bearer방식을 쓸거라면 이런걸 다 비활성화 시켜야 한다.


jwt FIlter 등록 테스트

20210808_184627

출처: https://velog.io/@sa833591/Spring-Security-5-Spring-Security-Filter-%EC%A0%81%EC%9A%A9

https://blog.naver.com/getinthere/222094919059





© 2021.03. by yacho

Powered by github