본문 바로가기

Backend 개발/Spring Security 6

Spring Security 6 익명 사용자 anonymous()

반응형

익명 사용자란?

스프링 시큐리티 DOC 에 나와 있듯이 '익명으로 인증된' 사용자와 '인증되지 않은' 사용자 사이에는 실질적인 개념적 차이는 없다. Spring Security의 익명 인증은 단지 액세스 제어 속성을 구성하는 더욱 편리한 방법을 제공할 뿐이다.

필자가 보기엔 로그인과 같은 인증 과정을 거치지 않은 유저를 익명 사용자로 봐도 무방한 듯 하다. 실제로 Spring Security anonymous() 설정을 따로 하지 않아도 익명 사용자와 관련된 AnonymousAuthenticationFilter 가 기본으로 작동하기 때문에 로그인 하지 않은 상태에서 보낸 모든 요청은 AnonymousAuthenticationFilter 를 거친다.

그럼 왜 굳이 '익명 사용자'라는 개념을 명확히 분리한 것일까?

로그인 하지 않은, 즉 인증되지 않은 사용자는 그냥 인증되지 않았다고 보면 되는데 왜 굳이 스프링 시큐리티는 '익명 사용자'라는 개념을 따로 만든 것일까?

일반적으로 허용되는 것을 명시적으로 지정하고 나머지는 모두 거부하는 '기본적으로 거부' 자세를 채택하는 것이 좋은 보안 관행으로 간주된다. 인증되지 않은 사용자가 액세스할 수 있는 것을 정의하는 것도 비슷한 상황이며,특히 웹 애플리케이션의 경우 그렇다.

쉽게 얘기하면 특정 url은 로그인한(인증된) 사용자만 접근할 수 있고(ex: 마이페이지, 회원 정보 변경 등) 로그인 창, 회원가입 화면과 같은 url은 모든 사용자가 접근할 수 있도록 일종의 규칙을 설정하는 것이다. 심지어 로그인하지 않은(인증하지 않은) 사용자들만이 접근 가능한 경로도 설정할 수 있다.

anonymous() 예제

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
            // http 통신에 대한 인가 정책 설정
            .authorizeHttpRequests(auth -> auth
                    .requestMatchers("/anonymous").hasRole("GUEST") // 인증된 사용자는 해당 자원 접근 불가능
                    .requestMatchers("/anonymous-context", "/authentication").permitAll()
                    .anyRequest().authenticated())
            // 인증 실패 시 인증 받도록 하는 방식 설정

            //.formLogin(Customizer.withDefaults()); // 폼 로그인 방식을 기본 default 방식으로 설정
            .formLogin(form -> form
                  ..
            )
            // 익명 사용자 설정
            .anonymous(anonymous -> anonymous
                    .principal("guest") // default: anonymousUser
                    // 해당 권한을 가진 사용자만 접근할 수 있는 자원 설정 가능
                    .authorities("ROLE_GUEST") // default: ROLE_ANONYMOUS
            )
    ;
    return http.build();
}
@GetMapping("/anonymous")
        public String anonymous() {
    return "anonymous";
}

@GetMapping("/authentication")
public String authentication(Authentication authentication) {
    if (authentication instanceof AnonymousAuthenticationToken) {
        return "anonymous";
    } else {
        // 인증하지 않으면 Authentication 객체가 null이기 때문에 else 문으로 빠진다.
        // 따라서 인증하지 않아도 "not anonymous"가 리턴됨
        return "not anonymous";
    }
}

@GetMapping("/anonymous-context")
public String anonymousContext(@CurrentSecurityContext SecurityContext securityContext) {
    // 익명 객체 이름 리턴 가능
    return securityContext.getAuthentication().getName();
}

해당 예제의 동작 방식은 다음과 같다.

1) 로그인하지 않았을(익명 사용자) 경우

  • /anonymous 요청에 접근 가능
  • /authentication 요청
    • 파라미터의 Authentication 객체가 스프링 MVC의 HandlerMethodArgumentResolver를 통해 주입되는데 Authentication 객체가 SecurityContext에서 직접 주입되며, 인증되지 않은 사용자일 경우 null이 전달됨
      → 따라서 인증하지 않았음에도 불구하고 'not anonymous'가 리턴됨
      → 익명사용자에 대한 객체 이름을 얻거나 익명 사용자만 접근 가능한 자원이나 기능을 만들고 싶을 경우에는@CurrentSecurityContext를 이용하면 됨
  • /anonymous-contex 요청
    • SecurityFilterChain 빈에서 설정한 'guest' 라는 문자열 리턴(기본값: 'anonymousUser')
    • @CurrentSecurityContext를 이용하여 보안 컨텍스트에서 SecurityContext를 직접 가져오기 때문에 Authentication이 null이 아니고 AnonymousAuthenticationToken 이 리턴된다.

두 메서드의 차이

메서드 매개변수 결과
@GetMapping("/authentication") Authentication authentication 인증되지 않은 경우 null 반환 (스프링 MVC 처리 방식)
@GetMapping("/anonymous-context") @CurrentSecurityContext SecurityContext 인증되지 않은 경우 AnonymousAuthenticationToken 반환
  • Authentication 매개변수: 인증되지 않은 경우 null을 전달할 수 있음.
  • @CurrentSecurityContext: 항상 보안 컨텍스트에서 값을 가져오기 때문에 null이 아님. 익명 사용자일 경우 AnonymousAuthenticationToken 반환.
  • 이러한 차이는 스프링 MVC와 스프링 시큐리티의 어노테이션 처리 방식 차이에서 비롯된다.

2) 로그인(인증)했을 경우

  • /anonymous url 요청에 접근 불가
  • /authentication 요청 - 'not anonymous' 문자열 리턴
  • /anonymous-contex 요청 - 로그인한 객체의 이름 리턴
728x90
반응형