spring/spring security

Spring Security의 기본개념 공부 1.

대기업 가고 싶은 공돌이 2024. 3. 24. 20:36

Section 1.

spring security를 써야하는 이유는 무엇일까?

  1. 보안 전문가들이 개발한 spring security를 사용함으로서 우리는 비즈니스 로직에만 집중할 수 있다.
  2. 최적화된 코드이기 때문에 최소한의 구성으로 웹 어플리케이션을 보호할 수 있다.
  3. 발전해가는 CSRF,CORS등의 취약점에 대비해 spring security팀은 항상 보안 코드를 업데이트 한다.
  4. 권한을 부여하여 접근을 제한할 수 있다.
  5. 다양한 방법으로 인증 및 인가를 구현할 수 있다. (JWT, OpenId등,, 추후에 배움)

서블릿과 필터

  • 모든 웹사이트는 http 프로토콜을 사용하여 소통한다.
  • 그러나 우리의 자바코드는 그러한 http 요청을 이해할 수 없기에 중재자가 필요하다.
  • 이 중개자를 우리는 서블릿 컨테이너라고 부른다.
  • 서블릿의 컨테이너의 역할: http 요청 메세지를 자바 코드가 이해할 수 있는 서블릿 request object로 변환한다. 그리고 우리의 자바코드가 다시 response 메세지를 보낼 때 http 프로토콜로 감싸 호스트에게 보낸다.
    • 그러나 서블릿은 너무 복잡해서 이러한 서블릿을 활용하는 spring이 등장했고, spring에 Controller, MVC path등을 정의하면 spring 내부에서 서블릿을 생성하도록 설계되어 있다.
  • 필터란 서블릿의 특별한 종류 중 하나로서 클라이언트화 서블릿 사이에서 오고가는 요청을 가로채 특정 작업을 선행할 수 있게 해주는 거름망을 의미한다.
    • 추후에 이 필터를 활용하여 권한을 확인하고 권한이 없다면 접근을 막는 등의 역할을 수행할 수 있다.

Spring Security의 내부 흐름

  • post 요청으로 유저가 아이디와 비밀번호를 보낸다.
  • 서블릿 컨테이너로 들어가기 전 Spring Security에 포함된 필터에 요청이 들어간다.(필터는 URL을 확인 후 누구나 접근할 수 있는 URL인지 아닌지를 판별한다.) 20개 정도의 필터가 존재하며, 필터는 각각의 고유한 역할을 지니고 있다.
  • 필터에서 추출한 유저네임과 비밀번호를 가지고 Authentication 객체를 생성한다.
    • Principal: Principal은 현재 사용자를 나타내는 객체다. 주로 사용자의 식별 정보(예: 사용자 이름, 이메일 등)를 포함한다.
    • Credentials: Credentials는 사용자의 자격 증명(예: 비밀번호)을 나타낸다. 주로 암호화된 형태로 저장되며, 일반적으로 보안상의 이유로 실제 값을 포함하지 않는다.
    • Authorities: Authorities는 사용자에게 부여된 권한을 나타내는 컬렉션이다. 각각의 권한은 문자열 혹은 객체로 표현될 수 있다. 이를 통해 애플리케이션에서 사용자의 권한을 확인하여 접근 제어를 할 수 있다.
    • Authenticated Flag: 인증된 사용자인지 여부를 나타내는 플래그이다. 사용자가 인증되었으면 true를, 그렇지 않으면 false를 가진다.
      현재 단계에서는로 객체를 전달하여 인증을 수행한다.
    • 만약 모든 provider에서 인증이 실패했을 경우 사용자에게 인증에 실패했다고 응답을 반환한다.
  • UserDetails Manager/ Service 인터페이스를 통해 데이터베이스에 저장된 유저 정보를 가져와 엔드 포인트에서 전송된 정보를 비교하여 인증을 수행할수 있다.
  • UserDetails Manager/Service를 이용하지 않고 Provider에서 바로 구현을 할 수도 있다.
  • 데이터 베이스에 저장된 비밀번호는 Password Encoder를 활용하여 암호화하여 저장해야 하고 불러올 때도 Password Encoder를 활용하여 해싱해야 한다.
  • 인증에 성공했다면 응답은 다시 필터로 돌아가게 된다.
  • 필터에 돌아간 응답은 Security Context에 저장되게 되며 다음과 같은 내용을 포함한다.
    • 인증이 성공적이었는지 아닌지.
    • 세션 ID가 무엇인지
      • 나중에 다시 로그인을 시도하면 Security Context에 저장된 내용을 바탕으로 재 로그인을 요청하지 않게 된다.

Spring Security Filter

  • 인증 권한을 관리하고, 로그인 페이지를 만들며 HDT 포지션 내부에 인증 정보를 저장하는 것을 도와주는 여러가지 필터가 존재한다.

Authorization Filter

  • 엔드 유저가 접근하고자 하는 URL에 접근을 제한하는 역할을 한다.
    • 공개된 URL이라면 자격증명 없이 바로 표시될 것이고, 그렇지 않다면 다음 필터로 보낸다.

Do Filter

  • Do Filter는 클래스가 아니라 메소드이다. 클래스 내부에 정의되어 해당 클래스의 역할을 수행한 후 다음 필터를 호출하는 역할을 맡고 있다.

DefaultLoginPageGenerating Filter

  • 보안 페이지에 접속하려 하면 로그인 화면을 띄워주는 역할을 맡고있다. 밑에 코드를 보면 로그인 페이지 코드가 포함된 것을 확인할 수 있다. 

UsernamePasswordAuthentication Filter

  • 유저가 자신의 아이디와 비밀번호를 입력하면 실행된다.
  • attemptAutentication 이라는 메소드가 있다
    • 이 메소드 내부엔 수신하는 http 요청으로부터 아이디와 비밀번호를 추출하는 로직이 짜여져있다. 추출한 정보를 바탕으로 UsernamePasswordAuthentication Token객체를 생성한다.
      • UsernamePasswordAuthentication Token 클래스는 AbstractAuthentication Token을 상속하며 이것은 Authentication 인터페이스를 implements 한다.
      • 따라서 UsernamePasswordAuthenticationToken은 Authentication 인터페이스의 실제 구현체라고 할 수 있다.
      • 이 UsernamePasswordAuthenticationToken을 AuthenticationManager라는 인터페이스 내부의 authenticate메소드에 인자로 집어넣어 메소드를 호출한다.
      • AuthenticationManager도 인터페이스 이므로 이를 구현한 클래스가 필요하다 그것은 ProviderManager클래스 이고 이 ProviderManager는 반복문을 돌리며 모든 가능한 AuthenticationProvider를 수행한다. 만약 인증에 성공한 AuthenticationProvider가 하나라도 나온다면 반복문은 즉시 중단되며 성공한 인증값을 반환하게된다. 
    • 즉 모든 인증 로직은 Authentication Provider에 구현되어 있따는 것이며 우리는 이 Authentication Provider를 구현해야할 것이다.
  • 다음은 실제 Authentication Provider의 예시이다.

  • 내부 코드에 user가 null이라면 retrieveUser 메소드를 통해 UserDetatils객체를 가져오는 것을 볼 수 있는데, retireveUser 메소드는 UserDetailsService를 통해 유저 정보를 받아오는 걸 알 수 있다. 위의 흐름도에서 봤던것과 동일한 흐름이다. 
  • UserDetailsService는 정보를 어떻게 불러오느냐? 하면 UserDetailsService 내부에는 loadUserByUsername이라는 메소드가 정의되어 있는데 이 메소드에서 보통 jpaRepository의 findByUsername 메소드를 통해 User 객체를 찾아서 반환한다고 한다.
    • 그렇게 반환된 User 객체를 additionalAuthenticationChecks 메소드에 넘기는데 이 메소드는 passwordEncoder를 활용하여 데이터베이스 내부에 저장된 비밀번호와 입력받은 비밀번호가 일치한지 확인하는 역할을 수행한다.
    • 일치하는지 여부가 다시 Authentication Provider에게 반환되고 나면 이는 Spring Security 프레임워크에 전달되며 성공이 전달됐다면 URL의 접근이 허용될 것이다.

정리

  1. REST 요청이 들어온다.
  2. Authorization 필터 실행(보안 URL인지 아닌지 확인)
  3. DefaultLoginPageGenerating 필터 실행 (로그인 페이지를 만든다)
  4. 아이디 비밀번호 입력
  5. UsernamePasswordAuthentication 필터 실행 (사용자에게 입력받은 아이디와 비밀번호를 바탕으로 UsernamePAsswordAuthenticationToken타입 객체를 생성한다.)
  6. 이후 Authentication 의 구현체인 Provider Manager 내부의 Authenticate 메소드를 호출한다. (인자는 UsernamePasswordAuthentication)
  7. 내부에서 모든 가능한 Authentication Provider 객체를 실행한다.
  8. retireveUser 메소드가 실행되고, UserDetatilsManager 내부에 구현된 LoaduserByUSername 메소드가 호출된다.
  9. 사용자에게 입력받은 아이디를 바탕으로 동일한 아이디가 있는지 데이터베이스에서 findByUsername 메소드로 찾아내고 해당 객체를 반환한다.
  10. Authentication Provider에 반환된 객체는 additionalAuthenticationChecks 메소드에 들어가고 비밀번호를 passwordEncoder를 통해 해독한뒤 서로 일치하는지를 확인한 후에 일치 여부가 프레임워크로 반환된다.
  11. Security Context에 인증 정보가 저장되며 자격증명이 자동으로 이뤄지게 된다.

로그인 없이 인증이 수행되는 과정

  • 로그인을 수행하면 백엔드에서 세션 아이디를 쿠키형식으로 브라우저에 전달해준다.
  • 사용자의 컴퓨터 메모리에 쿠키정보가 저장되며 서버에 어떠한 요청을 할 때마다 이 쿠키가 http 요청의 헤더에 붙어서 서버로 전달된다.
  • 서버는 쿠키 정보를 바탕으로 해당 유저의 요청을 처리한다.

 

본 게시글은 Spring Security 6 초보에서 마스터 되기 최신강의! (JWT , OAUTH2 포함) 을 공부한 후 정리한 글 입니다.