[Spring Security] 익명사용자 인증 필터

식빵·2022년 8월 7일
6
post-thumbnail

이 시리즈에 나오는 모든 내용은 인프런 인터넷 강의 - [ 스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security ] - 에서 기반된 것입니다. 그리고 여기서 인용되는 PPT 이미지 또한 모두 해당 강의에서 가져왔음을 알립니다.




🥝 익명사용자


🥥 익명사용자란?

우리가 만약 스프링 시큐리티 없이 로그인 기능을 개발하면,
Session 에서 사용자 정보를 읽어서 정보가 있다면 로그인 상태이고
정보가 null 로 반환되면 로그인 안한 사용자라고
딱 이분법적으로 생각하고 개발하게 될 것이다.

그런데 스프링 시큐리티는 그러지 않고, 비록 로그인 사용자가 아니더라도
"익명 사용자(Anonymous User)"라는 개념이 적용될 수 있도록 시스템을 갖췄다.


🥥 왜 필요한가?

그런데 대체 익명사용자는 왜 필요할까?
이건 강의 QNA 에 있는 글을 그대로 복붙해온 아래 글을 참고하자.


Q: 익명사용자를 왜 사용하는지 모르겠습니다.
A:
스프링 시큐리티는 익명 사용자 즉 로그인을 하지 않은 상태의 사용자도 별도의 역할 즉 인증정보, 권한정보를 가지도록 설계되었습니다.

물론 인증정보와 권한정보 자체가 정식 인증을 받은 상태가 아닌 익명 사용자용 인증, 권한 정보라고 볼 수 있습니다.

그리고 스프링 시큐리티에서 인증받지 않은 상태를 판별하는 기준은 user 객체의 존재 여부가 아닙니다.

즉 user 가 null 이라 할지라도 Authentication 이 null 이 아니면 인증 받은 것으로 간주하기도 합니다.

if(SecurityContextHolder.getContext().getAuthentication() == null){
....
}

과 같은 구문이 스프링 시큐리티 전반에 걸쳐 여러 군데에서 나오고 있습니다.
즉 세션이 만료되거나 무효화 되어서 SecurityContext 안에 Authentication
객체가 존재하지 않을 경우 어떠한 처리를 하라는 의미입니다.

위의 구문은 세션이 무효화 되어서 인증이 필요한 상태는 맞지만 익명사용자 즉 "anonymousUser" 는 아닙니다.

익명사용자는 Authentication 이 null 이 아니고 문자열의 "anonymousUser" 이 저장되어 있는 principal 과 ROLE_ANONYMOUS 권한 정보를 가지고 있는 객체입니다.

그렇기 때문에 스프링 시큐리티에서는 익명사용자라 할지라도 Authentication 객체를 별도로 할당함으로써 인증사용자와 익명사용자를 구분해서 적절한 분기를 타고 있습니다.

어떻게 보면 익명사용자 즉 로그인을 하지 않은 사용자는 궁극적으로 user 가 null 인 상태라 가정하고 코드를 작성하는게 당연할 수있습니다.

다만 스프링 시큐리티는 익명사용자가 인증을 받은 것은 아니지만 단순히 null 체크 기능을 넘어서 인증 및 인가 영역 전반에 걸쳐 특정한 목적과 사용을 위해서 고안한 설계물이며 AnonymousAuthenticationFilter 가 중심이 되어서 익명사용자용 인증객체와 권한 정보등을 생성하는 처리를 하고 있다고 보시면 되겠습니다.

익명사용자 필터 강의에서도 설명하고 있으니 참고해 주시면 감사하겠습니다.

이런 처리를 해주는게 익명사용자 인증 필터이다.


위 글을 읽고도 이해가 안되서 또 물어보는 QnA 가 있었는데, 해당 답변도 복붙하겠다.

스프링 시큐리티 설계자가 왜 익명사용자라는 개념을 도입했는지 생각해보면 될 것 같습니다.

인증을 하지 않은 사용자를 단지 user 객체가 null 이라는 단순한 개념이 아닌 AnonymouAuthenticationToken 객체에 익명사용자의 정보를 저장하고(사용자명, 권한, 인증여부 등..) 이를 SecuirtyContext 객체에 저장하여 어플리케이션 전역적으로 사용할 수있도록 도입했을 뿐입니다.

즉, 익명사용자일 경우

String user = SecurityContextHolder.getContext().getAuthentication() 하면 user 에 "anonymousUser" 가 저장되고 이 user 변수는 principal 에 저장이 되며 principal 은 AnonymousAuthenticationToken 저장이 되고 최종적으로 AnonymusAuthenticationToken 은 SecurityContext 에 저장이 되는 계층적 구조로 되어 있습니다.

이건 인증사용자도 마찬가지 구조입니다.

이러한 전반적인 처리를 하는 필터가 AnonymousAuthenticationFilter 이구요

어떻게 보면 객체지향적인 설계사상에 근거하여 익명사용자도 인증사용자처럼 일관성, 통일성 있게 스프링 시큐리티의 인증 프로세스를 따르도록 구현한 것일 수도 있습니다.

굳이 스프링 시큐리티가 아니라도 만약 어떤 시스템에서 인증을 하지 않는 사용자를 어떤 특정한 클래스를 만들어 거기에 보관하여 관리하도록 설계를 하고 구현 한다면 위와 비슷한 개념이 됩니다.

너무 어렵게 접근하실 필요는 없습니다

일반적으로 우리가 아는 로그인 하지 않은 사용자의 개념을 조금 더 구조적으로 설계하고 필터개념을 도입해서 관리하고자 함이라고 이해하시면 됩니다.




🥝 익명사용자 인증 필터(AnonymousAuthenticationFilter)


🥥 필터 내부 동작 방식

  • 필터는 인증 여부를 확인한다.
    • 인증 사용자면 다음 필터로 요청을 보낸다.
    • 무인증 사용자면...
      • 새로운 인증 토큰 생성
      • 인증 토큰을 SC 에 저장
      • 다음 필터 호출
      • 참고로 이 인증 객체는 세션에 저장되지 않는다! (당연하지만)


🥥 동작 코드 추적

AnonymousAuthenticationFilter.doFilter 메소드만 가볍게 보자.

정말 기능이 심플한 것을 느낄 수 있다.

SecurityContextHolder.getContext().getAuthentication() 을 통해서 현재 인증 객체가 있는지 확인한다.

없다면 다음 필터로 바로 요청을 보내버리고( chain.doFilter(req,res) ),
그렇지 않다면 createAuthentication 메소드를 통해서 AnonymousAuthenticationToken 을 생성하고, 이를 SC 에 저장한 후에 다음 필터로 요청을 위임한다.



참고로 저 인증 토큰에는 아래와 같이 세팅된다.

  • principal 에는 문자열로 "anonymousUser"
  • authorities 에는 "ROLE_ANONYMOUSE"




🥥 익명사용자 토큰 사용 예

익명사용자 토큰은 스프링 시큐리티 내부 로직에서 굉장히 많이 쓰이는데,
그중 하나가 바로 ExceptionTranslationFilter 필터이다.

이 필터는 AccessDeniedException , AuthenticationException 가 발생했을 때
이를 try-catch 로 잡아서 처리를 하는 필터이다. 나중에 더 자세히 배운다.

  • 인증 토큰의 타입을 먼저 확인하고
  • 익명사용자 토큰이면 sendStartAuthentication 메소드를 실행한다.
    • 해당 메소드는 내부적으로 로그인 FORM 이 존재하는 URL 로 redirect 시킨다.
  • 익명사용자 토큰이 아니면, 즉 인증 사용자 토큰이면
    • this.accessDeniedHandler.handle 메소드가 실행된다.
    • 메소드 이름을 봐서 알겠지만, 인증 사용자가 자원에 대한 접근 권한이 없어서 접근 거절을 당한 것이다.
profile
백엔드를 계속 배우고 있는 개발자입니다 😊

0개의 댓글