[JAVA/SPRING] Spring Security 구조 및 역할

SangHyun-Park·2022년 3월 12일
3

😐 Spring Security?

Spring Security 란 spring application 의 사용자 인증(Authentication), 권한 부여(Authorize) 및 기타 보안 기능을 제공하는 Java / Java EE 프레임 워크이다.

보안 분야는 아무래도 개발에 있어서 시간이 많이 소요되는 것들 중 하나이다. Spring Security 는 미리 짜여진 내부 로직을 통해 인증, 권한 확인에 필요한 옵션을 제공한다.

Authentication / Authorization

특정 자원에 대한 접근을 제어하기위해 객체는 "권한" 을 가진다.

특정 권한을 얻기 위해 사용자는 인증정보(Authentication) 을 제출해야하고 관리자는 해당 정보를 참고해서 권한을 부여(Authorization) 한다.

보편적으로 username - password 패턴의 인증방식을 거친다면, spring security 는 principal - credential 패턴을 가진다.

간단히 정리하면
Authetication 제출 - 검증 - 통과된다면 특정 권한 부여 과정을 거친다고 볼 수 있다.

구조

AuthenticationFilter

모든 요청에 대하여 AuthenticationFilter (Spring Filter 와 유사한 역할) 가 인증 및 권한 부여 과정을 선제적으로 거친 후 Dispatcher Servlet 으로 요청을 넘긴다.

마치 Filter 는 인증 전반의 과정을 총괄하는 듯한 느낌이 드는데, 객체지향에서 객체간 협력의 과정을 잘 보여주는 구조이다.

최초 요청에 대해서 spring 은 filter 의 attempAuthentication 메소드를 요청하는데, 다시 filter 는 Token 을 생성해서 저장되어있는 authenticationManager 에게 검증 요청을 한다

AuthenticationFilter 는 Authentication(주민등록증) 인터페이스를 구현한 Token 객체를 통해 검증과정을 거친다.

Authentication

UsernamePasswordAuthenticationToken 은 Token 중에서 Principal-Credential 패턴을 반영한 객체이며 가장 기본적인 Authentication 구현체다.

또한 Token 은 기본적으로 AbstractAuthenticationToken 을 상속받아 내부에 권한을 저장하는 authorities, 검증이 완료되었는지 판단하는 authenticated 상태를 저장하고있다.

흐름은 최초에 검증되지않은 Token 을 AuthenticationManager 에게 넘기면 검증 결과에 따른 새로운 Token 을 반환해준다.

AuthenticationManager

Spring Security 를 조사하다보면 한번 흠칫하게 되는 순간이 이AuthenticationManager 를 봤을 때 라고 생각한다.

왜냐하면 interface 에서 요구하는 메소드가 authenticate 뿐이고 구현체는 또 다시 AuthenticationProvider 에게 검증을 위임하는데,

그렇다면 Manager 를 두지 않고 바로 Provider 에게 검증을 요청하면 되지않나? 라고 의문이 들었기 때문이다.

자료조사가 부족한 탓인지 명확한 답을 얻을 순 없었지만, 대표적으로 사용되는 ProviderManager 내부 메소드를 참고하여 아래와 같이 결론을 내렸다.

  1. Provider 는 하나가 아니다.
  2. 때문에 다수의 Provider 를 관리할 객체가 필요하다.
  3. Filter 가 Provider 를 관리하는건 어색하다.
  4. 따라서 Manager 를 두고 Provider 를 관리한다

더 알아보기 전에 AuthenticationProvider 가 무엇인지 살펴보자

AuthenticationProvider

정말정말정말 실제 검증을 수행하는 객체이다.

그렇다면 검증을 하기위해서는 무엇이 필요할까?

우선 클라이언트가 요청한 principal-credential 에서 principal 을 바탕으로 서버에 해당 유저 정보를 불러와야 할 것이다.

뒤에서 살펴보겠지만

유저 정보를 불러오기 위해서는 다시 UserDetailsService 구현체를 사용하고 해당 구현체는 loadUserByUsername 메소드를 통해 UserDetails 객체를 반환하도록 강제하고있다.

아무튼 유저 정보를 불러왔다면 credential 검증을 수행하면 될텐데, 보안상 데이터베이스에 저장된 비밀번호는 암호화 되어있기 때문에 클라이언트로 부터 받은 credential 또한 passwordEncoding 과정을 거친 후에 비교해야할 것이다.

주의할 점
당연하다고 생각할 수 있지만 데이터베이스 내 데이터의 encode 방식과 새로 들어온 데이터의 encode 방식은 일치해야한다.

PasswordEncoder 를 bean 으로 등록해야하는 이유

구현한다면 위와 같은 구조다.

UserdetailsService / UserDetails

UserdetailsService 은 데이터베이스에서 유저 정보를 불러와서 UserDetails 객체를 만들어 돌려주는 역할을 한다

사실 UserDetailsService 보다는 UserDetails 가 핵심인 듯 한데, 왜냐하면 UserDetails 객체를 만드는 이유가 권한(GrantedAuthority)이 부여된 객체를 생성하도록 강제하기 때문이다.

즉, Provider 는 UserDetails 구현체(User) 를 사용함으로써 해당 유저의 권한을 참조가능하게 된다.

구현체는 아래와 같다

UserDetailsVO - authorities 를 무조건 포함해야만 생성가능하다.

위 코드는 시스템에서 email 을 principal 로 사용하기 위해 별도의 필드로 저장한다.


검증 후

이로써 모든 검증 과정이 끝났다 !

호출한 순서의 반대로 Authentication 객체 (여기서는 UsernamePasswordAuthenticationToken) 를 차례로 반환해줌으로써 인증 - 검증 - 권한부여 과정을 종료한다.

잠깐 그래서 이 정보를 어떻게 사용하지? 라고 의문이 들 수 있다.

spring security 는 내부적으로 SecurityContext 에 Authentication 객체를 저장하는데

filter 가 최종적으로 반납한 Authentication 객체를 저장하고, 인증이 필요한 순간에 꺼내서 권한을 확인하고 인가를 할지 말지를 결정한다.

그럼 또 다시 이 역할은 누가 수행하는 것일까?

바로 WebSecurityConfigurerAdapter 객체이다.

WebSecurityConfigurerAdapter

위에서 말한 역할 외에도 해당 객체는 security 시스템의 전반적인 설정(configuration) 을 담당한다.

개발자는 이 객체의 수많은 오버로딩된 configure 메소드를 이용해 기본적으로

  1. Filter를 추가
  2. URI 별 접근권한 설정
  3. AuthenticationManager 가 검증을 요청할 Provider 추가

이로써 간단한(?) spring security 를 통한 인증 체계를 구현해 보았다.

조금 더 세부적인 로직은 다음에 한번 알아보자🦆

profile
https://ppaksang.tistory.com/ 옮겼습니다 !!

0개의 댓글