Spring-Security

커스텀 인증 필터에 명시적으로 DelegatingSecurityContextRepository, SecurityContext 추가

neal89 2025. 5. 24. 12:47

✅ 1. 커스텀 인증 필터 만들기

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Collections;

public class CustomAuthFilter extends OncePerRequestFilter {

    private final SecurityContextRepository securityContextRepository;

    public CustomAuthFilter(SecurityContextRepository securityContextRepository) {
        this.securityContextRepository = securityContextRepository;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {

        String token = request.getHeader("Authorization");

        if (token != null && token.equals("Bearer valid-token")) {
            Authentication authentication = new UsernamePasswordAuthenticationToken(
                "customUser", null,
                Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
            );

            SecurityContext context = SecurityContextHolder.createEmptyContext();
            context.setAuthentication(authentication);
            SecurityContextHolder.setContext(context);

            // 명시적으로 저장
            securityContextRepository.saveContext(context, request, response);
        }

        filterChain.doFilter(request, response);
    }
}

✅ 2. DelegatingSecurityContextRepository 설정

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.context.*;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityContextRepository securityContextRepository() {
        return new DelegatingSecurityContextRepository(
            new RequestAttributeSecurityContextRepository(),
            new HttpSessionSecurityContextRepository()
        );
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        SecurityContextRepository repository = securityContextRepository();

        http
            .securityContext(securityContext -> securityContext
                .requireExplicitSave(true)
                .securityContextRepository(repository)
            )
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .formLogin(withDefaults());

        // 커스텀 필터 등록 (UsernamePasswordAuthenticationFilter 앞에 위치)
        http.addFilterBefore(new CustomAuthFilter(repository),
            org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.class
        );

        return http.build();
    }
}

✅ 요약

  • 커스텀 필터 CustomAuthFilter를 만들고, 헤더에서 토큰을 검사해 인증을 수행합니다.
  • 인증 후 SecurityContextHolder에 인증 객체를 저장하고, SecurityContextRepository를 통해 명시적으로 저장합니다.
  • 설정 파일에서는 DelegatingSecurityContextRepository를 만들고 SecurityContext 설정과 함께 필터 체인에 등록합니다.

 


원래는 userdetailservice를 구현해서 디비에서 정보 조회하고 userdetails 객체를 반환해야 한다.

보통 customauthfilter같은 필터에서 토큰 검사하고, 토큰에 담긴 사용자 이름으로 userdetailsservice 호출, 디비에 정보 불러오고 권한 정보 포함해서 authentication 객체 만들고, securitycontext에 저장.
이렇게 만든 userdetailsservice 구현체는 빈으로 등로하고, authenticationmanager나 필터에서 주입받아서 사용한다.

 

 

 

 


**추가

 

1. DelegatingSecurityContextRepository 안에

  • RequestAttributeSecurityContextRepository,
  • HttpSessionSecurityContextRepository

어느 것을 먼저 사용해서 저장하거나 로드할지를 어떻게 정하나요?


✅ 결론 먼저

DelegatingSecurityContextRepository는 순서대로 접근합니다.
→ 즉, 첫 번째로 등록된 RequestAttributeSecurityContextRepository가 우선 사용됩니다.


✅ 구체적인 작동 방식

  1. loadContext() 호출 시:
    • DelegatingSecurityContextRepository는 등록된 순서대로 loadContext()를 호출합니다.
    • 첫 번째 리포지토리 (RequestAttributeSecurityContextRepository)가 SecurityContext를 리턴하면 거기서 멈추고 두 번째는 실행하지 않습니다.
  2. saveContext() 호출 시:
    • DelegatingSecurityContextRepository는 등록된 모든 리포지토리의 saveContext()를 호출합니다.
    • 즉, 저장은 여러 곳에 할 수 있고,
    • 로딩은 첫 번째에서 성공하면 그걸로 끝입니다.

✅ 예시로 정리

new DelegatingSecurityContextRepository(
    new RequestAttributeSecurityContextRepository(), // 1순위
    new HttpSessionSecurityContextRepository()       // 2순위
)
  • 읽기(load) 시:
    • 먼저 RequestAttributeSecurityContextRepository가 SecurityContext를 반환하는지 확인.
    • 못 찾으면 다음 HttpSessionSecurityContextRepository로 넘어감.
  • 쓰기(save) 시:
    • 두 리포지토리 모두에게 저장 요청을 보냄.

✅ 따라서, 우선 순위를 바꾸고 싶다면?

→ 순서를 바꾸면 됩니다:

new DelegatingSecurityContextRepository(
    new HttpSessionSecurityContextRepository(),      // 1순위
    new RequestAttributeSecurityContextRepository()  // 2순위
)