[Spring Security] 스프링 시큐리티 기초 2 - 아키텍쳐

Junho Bae·2021년 3월 12일
4

Spring Security

목록 보기
2/2

인프런 백기선님의 스프링 시큐리티 기초 강의 정리 내용입니다. 제가 보려고 해놨어요 😀

어렵다..

chapter 2. 스프링 시큐리티 : 아키텍쳐

10. SecurityContextHolder와 Authentication

Principal : 어떤 어플리케이션에서 인증을 거치고 나면, 인증된 사용자 정보를 스프링에서 의미.

  • 이걸 authentic ation에다가 넣어서 관리하고 이걸 SecurityContext에다가 넣은걸 다시 SecurityContextHolder에다가 두번 감싸서 사용.
  • 왜그런지는 모르겠어요.

SecurityContextHolder

  • SecurityContext를 제공. 기본적으로 ThreadLocal을 사용

    Thread Local 이란 한 쓰레드 내에서 쉐어하는 저장소. 메서드 파라미터를 쓰지 않더라고, 한 쓰레드에서 공용으로 저장하는 객체이기 때문에, 파라미터를 넘기지 않더라도 데이터에 접근이 가능. 즉, authentication을 한 쓰레드 내에서 공유할 수 있음.

  • 결론적으로 SecurityContextHolder가 authentication을 담고 있는데, 얘는 ThreadLocal을 사용하기 때문에 이 정보는 어플리케이션 어디에서나 접근 할 수 있지. 만약 쓰레드가 달라진다면, 접이 불가능하겠지?

  • 그렇다면, 시큐리티컨텍스트홀더가 제공하는 다른 전략을 써야해

  • 보통은 웹 어플리케이션을 만들고, 서블릿 기반이라면, 요청이 들어왔을 때 요청이 처리되는 쓰레드는 명시적으로 async한 기능을 쓰지 않는 이상 동일한 쓰레드에서 처리하게 되니까. Thread per Request. 서블릿 컨텐이너 관련. 톰캣이 가지고 있는 앞단의 커넥터가 처리하는 거고. 최종적으로 어플리케이션에 요청이 들어와서 처리할 때는 대부분의 경우 하나의 쓰레드가 처리한다.

  • 무튼, authenticartion을 넘겨주지 않아도 시큐리티컨텍스트홀더를 통해서 접근이 가능하다.

  • authentication의 경우도 여러 구현체가 있는데 폼인증의 경우, 유저네임패스워트어썬티케이션토큰이라는 객체가 최종적으로 시큐리티컨텍스트 홀더 안에 authentication 으로 담김

  • 어썬티케이션에서 principal을 꺼낼 수 있는데 지금 보면 유저 타입임. 우리가 유저 디테일즈라는 패키지에서 제공하는 유저라는 객체 유저 디테일즈 서비스의 구현체에서 리턴한 타입이 곧 Principal.

  • 이 롤 정보 역시 유저 디테일즈 서비스 구현할 때 넘겨준 거. 심플그랜티트어써리티에서 principal이 가지고 있는 권한을 나타냄.

  • User.build().roles(u.getRole()) 여기서 _를 붙여준거임.

principal

  • 누구냐에 관한. 우리가 유저 디테일 서비스에서 리턴한 객체
  • 선언상 오브젝트이지만, 우리는 유저타입으로 했기 때문에 유저 디테일 타입, 유저타입일 것임.

GrantAuthority

  • 사용자가 어떠한 권한을 가지고 있는가. 우리가 리턴한 유저타입 (유저는 유저디테일을 상속)

UserDeytailsService

  • 디비에서 유저 정보를 스프링 시큐리티한테 제공하는 역할. 인증을 하지는 않음
  • 그럼 인증은 누가하냐. authentication manager가 함.
  • 그저, 우리는 loadUserByUsername 메서드를 오버라이드 해서 지금 유저 정보를 디비로부터 가져오고 있는 거임.

11. AuthenticationManager와 Authentication

  • SecurityContextHolder는 Authentication을 담고 있는 거고, 이제 이걸 만들고 인증하는건 AuthenticationManager임.
    AuthenticationManager는 딱 하나밖에 메서드가 없음. authenticate.
public interface AuthenticationManager {
	//폼인증의 경우, 유저 네임, 유저 비밀번호를 받는걸로 이해.
	Authentication authenticate(Authentication authentication) throws AuthenticationException
	//프린서펄을 담고 있는 Authentication 객체를 리턴. 		
}

이거의 구현체로는 ProviderManager를 가장 많이 씀. 여기서 구현이 이루어짐.

인증의 과정!

  • authenticationmanager의 구현체인 providermanager로 인증이 된다.
  • 프로바이더 안의 authenticate(Authentication authentication //id,password)
  • 인증을 또 다른 여러 AuthenticationProvider들에 위임을 함.
    for(AuthenticationProvider provider : getproviders() ) {
		if(!provider.supports(toTest)) {
			continue;
		}.
	}

-현재 우리가 넘겨주는 authentication 객체는 우저네임패스워드어썬티케이션토큰 타입임…

  • 근데 여기 있는 provuider는 anonymous어쩌구임
  • 그래서 여기가 처리가 안되기 때문에, parent로 가서 처리를 함.
  • 여기서 parent는 유저네임패스워드어썬티케이션토큰을 처리할 수 있음. 그니까 다오어썬티케이션 프로바이더 안으로 들어옴
  • 타고 타고 들어가다보면, 유저디테일즈 서비스를 호출해서 유저를 호출해옴.
  • 이 지점이 우리가 구현한 유저 디테일즈 서비스 (어카운트 서비스)가 사용됨.
  • 이후 여러가지 추가 확인. lock이 되어 있는지, 뭐 등등.
  • 결론적으로 이 result를 리턴하게 되는데………
  • 이 리절트는 똑같이 유저네임패스워드어썬티케이셔ㅑㄴ 토큰인데
  • principal은 “유저”라는 객체를 들고 있고, 이 “유저”라는 객체는 우리가 구현한 유저디테일즈 서비스의 구현체인 어카운트서비스에서의 로드유저네임에서 리턴하는 것임.
  • 아 이렇게 authentication이 결국 시큐리티컨텍스트 홀더에 들어가는 겁니다.

12. ThreadLocal

  • Java.lang 패키지에서 제공하는 쓰레드 범위의 변수. 즉, 쓰레드 수준의 데이터 저장소임.
    스프링으로 트랜잭션 처리를 하고 있다면 이미 쓰레드 로컬을 사용하고 있음.

13. Authentication과 SecurityContextHolder

그래서, Authentication에서 만든 authentication이 언제 SecurityContextHolder에 ThreadLocal로 들어가는데? 에 관한

총 두개의 친구, UsernamePasswordAuthenticationFileter, SecurityContextPersistenceFileter가 관리함.

UsernamePasswordAuthenticationFileter, SecurityContextPersistenceFileter

  • 이전에 로그인한 걸 알고 있을 수 있음! 정확하게 일치하고 있는. authentication이 여러 요청에 걸쳐서 유지가 됨.

  • 먼저 SecurityContextPersistenceFilter에 먼저 걸림
    -> 어딘가에서 걸려 있는, 캐싱하고 있는 시큐리티 컨텍스트 홀더를 복구하려고 요청함.
    -> 요청이 끝나면 시큐리티 컨텍스트 홀더를 지워줌
    ->근데 없어서 다 지나가면, UsernamePasswordAuthenticationFilter에 걸림

  • 여기서, authenticationmanager를 호출해서, 프로바이더 하고 인증을 함.

결론) 유저네임패스워드어썬티케이션필터에서 인증을 하고 이걸 넣는다.

시큐리티컨텍스트펄시스턴스필터에서 http그 어쩌구에 담아놓은것을 해서 세션으로 읽어옴. http session이 바뀐다? 그러면 인증이 날라간다는 거.

  • SecurityContextPersistenceFilter덕분에 약간 stateful하게 해주는.

사실 이 두 필터만 쓰이는게 아니라 다른 15여가지의 필터가 있는데 이게 이제 어떻게 관리되고 언제 사용되는 걸까? 필터 개많네

14. 스프링 시큐리티 필터와 필터 체인 프록시

  • 체인에 맞는 필터 목록을 가져와서, 가져 왔다면 필터들을 하나씩 순회하면서 순차적으로 하나씩 실행. additionalFilters 15개의 필터.
  • 이중 우리가 보던건 시큐리티컨텍스트펄시스턴스, 유저네임패스워드 어쩌ㅏ구.
  • FilterChauinProxy가 하나씩 쭉 다 호출을 함.
  • 필터 목록을 선택할 때,
  • 각각의 필터들에 대한 학습은 다음에 기회가 있을 것 같구요..

우리가 만드는 SecurityConifg에서 오버라이드 하는 configure 여기서 프록시 체인을 설정하는 거임. *

  • 상충되는 두 설정을 쓰면, 컴파일에서 에러가 남. 상충되는 경우 순서가 중요함..
  • 필터체인 프록시가 여러개의 필터들을 관리하고.
  • 이러한 목록은 우리의 시큐리티 설정에 따라 달라진다.

그럼 이제 내가 요청을 보냈을 때, 그게 어떻게 필터 체인 프록시까지 가지?

15. DelegatingFilterProxy와 FilterChainProxy

  • 우리의 요청은 서블릿으로 받음
  • 서블릿 컨테이너는 servelet filter 스펙이 존재 : 요청의 앞 뒤 처리
  • DelegatingFilterProxy : 그런 서블릿 필터의 스프링 구현체. 자기가 처리하는게 아닌 누군가에게 위임함. 자기가 해야할 일을. 스프링 ioc 컨테이너 내의 빈으로 위임.
  • 스프링 부트를 쓸 때는 자동으로 빈 설정이 됨.

지금까지 본 모든 필터가 다 서블릿 필터임.

컨텍스트 홀더가 어썬티케이션 가지고 있고, 매니저가 어썬티케이션을 인증을 해주고, 필터들이 인증된걸 다시 홀더에다 넣어주고, 이러한 필터들은 필터체인 프록시가 호출을 해주고 있고, 어케 필터체인으로 들어온거는 DelegatingFIlterProxy가 넣어준다.

http.antMatcher()를 사용해서 구분해 주는 것이 좋음.

인증은 했는데 권한 확인은 어디서해?

16. AccessDecisionManager

  • 이미 인증을 거친 사용자가 특정한 서버 리소스에 접근을 할 때 접근을 허용할 것인가? 를 담당하는 인터페이스.
  • 인증은 authentication, 인가는 accessdecisionmanager
  • 구현체로는 affirmativeBased. voter중 한명이라도 허용하면 허용.
  • 보터를 커스터마이징 가능

15. 롤의 계층을 설정하는 방법

  • 핸들러에다가 roleHierachy를 설정하는 방법
  • 어드민은 유저를 포함한다. 이런 느낌으로. 

    -> 그럼 accessdecisionmanager는 누가 호출해주냐

16. FilterSecurityInterceptor

  • 인증을 다 거친 다음에 마지막으로
  • 허허
  • 얘도 사실 필터 체인 맨 마지막에 있는 친구,

웹 애플리케이션 시큐리티

1. 스프링 시큐리티 ignoring() 1부

지금까지 살펴본 모든 요청은 스프링 시큐리티가 필터들을 적용해서 처리를 해 왔지..
지금 보면 총 3개의 요청이 가고 있음

  • localhost, favicon -> anyRequest.authenticated에서 걸려서 login까지 같이 걸린거임. 인증된 사용자만 접근해야 하니까.
  • 요청이 들어오면 필터가 적용이 됨. 그래서 favicon 요청을 그 맨 마지막 필터가 잡아가지고.
  • 불필요한 일이 생기고 있는거임
    특정 필터를 적용하고 싶지 않은 요청이 있다?

@Override
public void configure(WebSecurity web) throw Exception {
	//스태틱 리로스를 이렇게 쓰는게 귀찮으면
    //web.ignoring().mvcMatchers("/favicon.io");
        
        web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
        

}
  • ignoring 방법은 매우 다양하기 때문에 알아서 잘 찾아서 하세요. 그걸 ignore시키면 filter의 목록이 비워진다. 가 중요함. 성능상 이점.

2. ignoring 2부

http.authorizeRequests()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitall 로 그냥 configure에서 설정을 한다면?

  • 결과는 같지만, 필터 체인 15가지를 다 거쳐야 하기 때문에.
  • 오히려 시간이 더 오래 걸림.
    동적으로 처리하는 리소스들은 필터를 타는게 좋고, permitall을 해주더라도, 정적인 리소스들 중에서 안 쓸 친구들은 필터를 처리하는게 좋다.

23. WebAsyncManagerIntegrationFilter

@GetMapping("/async-handler")
@ResponseBody
public Callable<String> asyncHandler() {
    SecurityLogger.log("mvc");
    //톰켓이 할당한 nio 쓰레
    return new Callable<String>() {
        @Override
        public String call() throws Exception {
            SecurityLogger.log("Callable");
            //다른 쓰레드

            /*
            서로 다른 쓰레드지만 서로 같은 시큐리티 컨텍스트를 공유할 수 있음. 동일한 principal이 찍히는 것 확인 가능.
             */

            return "Async Handler";
        }
    };
}

24. 스프링 시큐리티와 @Async

25. SecurityContextPersistenceFilter

여러 요청 간의 시큐리티 컨텍스트를 공유할 수 있는.

  • 새로 인증을 하지 않아도, 시큐리티 컨텍스트 정보가 지금 공유가 되고 있기 때문에 또 인증을 안해도 댔져? 한번 하면?
  • SecurityContextRepository를 사용해서 기존의 시큐리티 컨텍스트를 읽어옴.
  • 이 기본 구현체가 httpSession을 사용.
  • 다른 인증을 하기 전에, 실제 principal정보를 담고 있는 시큐리티 컨텍스트가 있다면 다른 인증을 굳이 할 필요 없으니깐.
  • 만약 비어있다면 새로 만듬. 이 때는 물론 다른 필터들을 다 탐
  • 그래서 이 필터가 반드시 다른 필터들보다 먼저 와야한다!
  • 만약 나중에 커스텀한 인증 필터를 쓰고 싶다 하면 이건 앞부분에 있어야 한다는것.

26. HeadWriterFilter

응답 헤더에 시큐리티 관련 헤더를 추가해주는 필터. 딱히 알 필요는 없지만 고마운 친구라는 걸 알 수 있게 해줌.
async -> persistence -> headwriter의 순서로 필터가 적용됨.

어렵네여

profile
SKKU Humanities & Computer Science

0개의 댓글