Anonymous 인증이란?

Spring Security의 Anonymous 인증은 인증되지 않은 사용자(로그인하지 않은 사용자)를 처리하는 메커니즘입니다. 인증되지 않은 요청에 대해 AnonymousAuthenticationToken을 생성하여 보안 컨텍스트에 저장합니다. 내용을 길지않으니 빠르게 알아보겠습니다.

 

 

 

사용법

anonymous 설정은 아래와 같습니다.

        http.anonymous(anonymous -> anonymous
            .principal("anonymousUser")      // 익명 사용자의 주체
            .authorities("ROLE_ANONYMOUS")    // 익명 사용자의 권한
        );
        // 비활성화
        http.anonymous(AbstractHttpConfigurer::disable);

 

 

 

사용처

로그인/회원가입은 로그인한 회원은 접근하지 못해야합니다.

        http.authorizeHttpRequests(request -> request
            .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
            .requestMatchers("/admin/**").hasRole("ADMIN")
            .requestMatchers("/join", "/signup").anonymous()
            .anyRequest().permitAll()
        );

/join 과 /signup URL에는 로그인하지 않은 사용자만 접근하도록 설정했습니다.

 

 

결과

이제 로그인하고 /join 에 접속해보면

403 Forbidden 이 발생합니다.

 

 

Anonymous 인증의 동작 방식

 

Anonymous 인증 필터

  • AnonymousAuthenticationFilter가 인증되지 않은 요청을 처리
  • SecurityContextHolder에 인증 객체가 없을 때 AnonymousAuthenticationToken 생성
  • 필터 체인의 맨 마지막 부분에서 동작

 

인증 객체 구조

AnonymousAuthenticationToken {
    principal: "anonymousUser",
    authorities: ["ROLE_ANONYMOUS"],
    details: WebAuthenticationDetails,
    authenticated: true
}

 

 

 

주의사항

Anonymous 를 사용할 때에는 알고있어야 하는 것이 있습니다. AnonymousAuthenticationToken 이 SecurityContextHolder에 생성되었다는 것은 인증된 상태로 간주된다는 것입니다.

 

 

 

[Spring Security] 스프링 시큐리티 이해하기 (1)

Spring SecuritySpring Security는 인증, 인가 및 일반적인 공격에 대한 보호를 제공하는 프레임워크 입니다. 서비스를 개발할때 필수적으로 구현해야하는것이 로그인, 회원가입과 권한인데요. 이를 Sprin

tmd8633.tistory.com

이 특징에 대해서 3-1 SecurityContextHolder 부분을 읽어보시면 이해가 되실겁니다.

 

 

Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println("isAuthenticated: " + auth.isAuthenticated());  // true 반환

이렇게 true가 반환되니 주의하시기 바랍니다.

 

실제 로그인한 사용자인지 파악하기위해서는

if (auth instanceof AnonymousAuthenticationToken) {
    // 익명 사용자
} else {
    // 실제 인증된 사용자
}

이렇게 사용하셔야합니다.

 

 

 

 

ExceptionHandling

http.exceptionHandling(handling -> handling
    .accessDeniedHandler()           // 권한 부족 시 처리
    .accessDeniedPage()             // 권한 부족 시 리다이렉트할 페이지
    .authenticationEntryPoint()     // 인증되지 않은 사용자 처리
    .defaultAuthenticationEntryPointFor()  // 특정 요청에 대한 인증 진입점 설정
);

 

 

accessDeniedHandler

.accessDeniedHandler((request, response, accessDeniedException) -> {
    // 인증된 사용자가 권한이 부족한 리소스에 접근할 때
    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();
    out.println("<script>alert('권한이 없습니다.'); window.location.href='/';</script>");
    out.flush();
})

 

 

accessDeniedPage

.accessDeniedPage("/error/403")  // 간단히 특정 페이지로 리다이렉트

 

 

authenticationEntryPoint

.authenticationEntryPoint((request, response, authException) -> {
    // 인증되지 않은 사용자가 보호된 리소스에 접근할 때
    response.sendRedirect("/login");
})

 

 

defaultAuthenticationEntryPointFor

.defaultAuthenticationEntryPointFor(
    new LoginUrlAuthenticationEntryPoint("/api/login"),
    new AntPathRequestMatcher("/api/**")
)

 

 

전체 코드

        http.exceptionHandling(handling -> handling
            // 1. 권한 부족 처리 (403)
            .accessDeniedHandler((request, response, accessDeniedException) -> {
                response.setContentType("text/html;charset=UTF-8");
                PrintWriter out = response.getWriter();
                out.println("<script>alert('권한이 없습니다.'); history.back();</script>");
                out.flush();
            })
            
            // 2. 인증되지 않은 사용자 처리 (401)
            .authenticationEntryPoint((request, response, authException) -> {
                if (isAjaxRequest(request)) {
                    response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
                } else {
                    response.sendRedirect("/login");
                }
            })
            
            // 3. API 요청에 대한 특별한 처리
            .defaultAuthenticationEntryPointFor(
                new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
                new AntPathRequestMatcher("/api/**")
            )
        );
        
    // AJAX 요청 확인
    private boolean isAjaxRequest(HttpServletRequest request) {
        return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
    }

 

코드만 봐서는 헷갈리는게 많습니다. 정리해보자면 다음과 같습니다.

 

주요 차이점

  1. accessDeniedHandler vs authenticationEntryPoint
    • accessDeniedHandler: 인증은 됐지만 권한이 없을 때 (403)
    • authenticationEntryPoint: 인증이 안된 경우 (401)
  2. accessDeniedPage vs accessDeniedHandler
    • accessDeniedPage: 단순 페이지 리다이렉트
    • accessDeniedHandler: 더 복잡한 로직 구현 가능
  3. defaultAuthenticationEntryPointFor
    • 특정 URL 패턴에 대해서만 다른 인증 진입점 설정 가능
    • API와 웹 페이지를 다르게 처리할 때 유용

 

 

오늘은 여기까지하고 마치겠습니다.

+ Recent posts