사용자 정의 인증을 사용하는 React-Spring 앱에서 401 무단 Spring 보안 오류 수정

사용자 정의 인증을 사용하는 React-Spring 앱에서 401 무단 Spring 보안 오류 수정
사용자 정의 인증을 사용하는 React-Spring 앱에서 401 무단 Spring 보안 오류 수정

사용자 정의 로그인 구현에서 Spring Security의 인증 문제 디버깅

Spring Security 프로젝트에서 401 Unauthorized 오류가 발생하면 특히 로그인 구성이 올바르게 설정된 것처럼 보일 때 실망스러울 수 있습니다. 😣 많은 개발자가 Spring Security의 기본값 외부에서 사용자 정의 로그인 페이지를 구현하는 동안 앱의 백엔드 리소스를 보호하려고 할 때 이 문제에 직면합니다.

이 문제는 React와 같은 프런트엔드 프레임워크가 로그인 페이지를 관리하고 Spring Security의 양식 기반 로그인 설정을 우회하여 백엔드와 통신할 때 발생할 수 있습니다. 이러한 설정에서는 Spring Security가 인증된 세션을 인식하지 못해 보호된 리소스를 사용하려고 할 때 액세스가 거부될 수 있습니다.

이 글에서는 성공적으로 로그인한 후 발생하는 무단 액세스 오류의 일반적인 원인에 대해 자세히 살펴보겠습니다. Spring의 SecurityContext와 세션 관리의 역할을 이해함으로써 사용자 정의 설정에서 이 문제를 해결하는 방법에 대한 명확성을 얻을 수 있습니다.

인증 로직이 올바른 세션 상태를 일관되게 설정하여 애플리케이션 전반에 걸쳐 원활하고 승인된 액세스를 가능하게 하는 실용적인 전략을 살펴보겠습니다. 🚀

명령 사용예
sessionManagement 이 명령은 Spring Security가 HTTP 세션을 처리하는 방법을 구성합니다. session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)를 사용하면 각 요청이 개별적으로 인증됩니다. 이는 REST 기반 토큰 인증 설정에서 자주 사용되는 상태 비저장 API에 필수적입니다.
OncePerRequestFilter OncePerRequestFilter는 요청당 단일 실행을 보장하는 Spring 보안 필터입니다. 중복 없이 각 요청에 대해 인증 논리가 일관되게 적용되도록 사용자 지정 인증 필터에 사용됩니다.
SecurityContextHolder SecurityContextHolder.getContext().setAuthentication(authentication)을 호출함으로써 이 명령은 보안 컨텍스트에서 인증된 사용자의 세부 정보를 설정하여 Spring Security가 현재 세션에 대해 인증된 사용자를 인식하도록 합니다.
DaoAuthenticationProvider 이 새로운 DaoAuthenticationProvider() 명령은 특정 사용자 세부 정보 서비스 및 비밀번호 인코더를 사용하여 인증을 설정하여 사용자 데이터베이스를 기반으로 사용자 정의 유효성 검사를 허용하고 안전한 비밀번호 처리를 보장합니다.
MockMvcRequestBuilders.post 단위 테스트의 이 명령은 mockMvc.perform(MockMvcRequestBuilders.post("/login"))에서 볼 수 있듯이 HTTP POST 요청을 시뮬레이션합니다. HTTP 요청을 컨트롤러의 엔드포인트에 직접 전송하여 Spring MVC 컨트롤러를 테스트할 수 있습니다.
authorizeHttpRequests 이 명령은 인증이 필요한 요청과 공개적으로 액세스할 수 있는 요청을 지정합니다. Authorize.requestMatchers("/user/login").permitAll()을 사용하면 자격 증명 없이 로그인 및 등록 엔드포인트에 액세스할 수 있습니다.
TokenProvider TokenProvider와 같은 사용자 정의 클래스는 JWT 토큰을 생성하고 관리하는 데 사용됩니다. 이 클래스는 토큰 기반 인증에 필수적인 모듈식, 재사용 가능 및 보안 토큰 처리를 보장하기 위해 토큰 생성 논리를 캡슐화합니다.
csrf().disable() Disabling CSRF is critical in stateless API configurations, particularly for REST APIs without session-based login. csrf(csrf ->CSRF를 비활성화하는 것은 상태 비저장 API 구성, 특히 세션 기반 로그인이 없는 REST API의 경우 중요합니다. csrf(csrf -> csrf.disable())은 일반적으로 토큰 기반 인증을 사용하는 애플리케이션에 필요합니다. 이 경우 CSRF 보호가 필요하지 않기 때문입니다.
requestMatchers 이 명령은 Authorize.requestMatchers("/user/register")와 같은 특정 보안 규칙과 일치하는 엔드포인트를 필터링합니다. 여기서는 인증 요구 사항에서 등록 및 로그인 끝점을 제외하는 데 사용됩니다.
usernamePasswordAuthenticationToken 이 명령은 사용자 정의 인증 프로세스에 필수적입니다. new UsernamePasswordAuthenticationToken()은 제공된 자격 증명으로 인증 토큰을 생성하여 인증 관리자가 저장된 사용자 세부 정보에 대해 이러한 자격 증명을 확인할 수 있도록 합니다.

사용자 정의 로그인 인증을 위한 Spring 보안 구성 이해

제공된 스크립트에서 처리를 위한 사용자 정의 구성을 볼 수 있습니다. 입증 기본 로그인 양식을 사용하지 않고 Spring Security에서. 별도로 생성하여 보안필터체인 구성을 통해 보호되는 엔드포인트와 Spring이 세션을 관리하는 방법을 제어할 수 있습니다. 이 구성은 프런트엔드 프레임워크(예: React)가 안전한 토큰 기반 요청을 사용하여 통신하므로 REST API에서 흔히 발생하는 CSRF(Cross-Site Request Forgery) 보호를 비활성화합니다. 여기서는 AuthorizeHttpRequests 명령이 핵심입니다. "/user/login" 및 "/user/register"와 같은 특정 URL이 모든 사용자에게 공개되도록 하는 동시에 보호된 리소스에 액세스하는 것과 같은 다른 요청은 인증된 사용자에게만 제한됩니다.

또한 필요한 경우에만 세션 생성을 허용하는 SessionCreationPolicy.IF_REQUIRED를 사용하여 세션 생성 정책을 설정했습니다. 이 접근 방식은 일부 요청이 세션 기반 인증에 의존할 수 있지만 다른 요청(예: 토큰이 있는 요청)은 그렇지 않은 애플리케이션에 적합합니다. 예를 들어, 사용자가 React 프런트엔드를 통해 로그인하고 리소스에 대한 지속적인 액세스가 필요한 경우 이 세션 정책은 애플리케이션에서 경로를 전환하는 동안 사용자가 반복적으로 로그아웃되지 않도록 보장합니다. 클라이언트(React 앱)가 백엔드 API와 상호 작용하는 방식에 따라 세션 및 상태 비저장 요구 사항을 모두 처리하는 데 특히 유용합니다.

서비스 클래스에는 AuthenticationManager 빈이 작동하는 authenticateUser라는 메소드가 포함되어 있습니다. 이 Bean은 데이터베이스에 대해 사용자 자격 증명을 확인하는 데 필수적인 DaoAuthenticationProvider 및 PasswordEncoder로 구성됩니다. 이 메소드는 UsernamePasswordAuthenticationToken을 사용하여 인증 관리자.authenticate를 호출하고 제공된 사용자 이름과 비밀번호를 기반으로 인증을 시도합니다. 성공하면 Spring Security의 SecurityContextHolder가 인증된 사용자의 세션을 보유합니다. 이런 식으로 프런트엔드가 또 다른 요청을 하면 Spring은 재검증을 요구하지 않고 사용자의 인증 상태를 검색할 수 있습니다.

그러나 이러한 설정에도 불구하고 세션이나 토큰이 제대로 유지되지 않으면 401 Unauthorized 오류 수신과 같은 문제가 발생할 수 있습니다. 예를 들어, 상태 비저장 세션과 함께 REST API를 사용할 때 서버가 요청 간 인증을 유지하지 않으면 이 설정이 실패할 수 있습니다. 이 문제를 해결하기 위해 생성된 토큰이 로그인 후 각 요청 헤더에 첨부되어 세션을 서버와 독립적으로 만드는 토큰 기반 인증을 구현할 수 있습니다. 테스트 환경에서 MockMvcRequestBuilders를 사용하면 개발자가 요청을 시뮬레이션하고 로그인 엔드포인트가 인증 토큰을 올바르게 반환하는지 확인할 수 있습니다. 그런 다음 이 토큰을 추가 요청에 사용할 수 있어 React 프런트엔드가 재인증 없이 보안 엔드포인트에 액세스할 수 있어 보다 원활한 사용자 경험을 제공할 수 있습니다. 🔐

솔루션 1: 상태 비저장 세션 관리를 위한 Spring 보안 구성 업데이트

이 접근 방식은 Spring Security의 상태 비저장 세션 정책을 사용하여 React와 같은 단일 페이지 애플리케이션(SPA)에 최적화된 REST API 컨텍스트에서 세션 관리를 해결합니다. 여기서는 REST API 상태 비저장 모델과 일치하도록 SecurityFilterChain 구성을 조정합니다.

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
        .csrf(csrf -> csrf.disable())  // Disable CSRF for REST APIs
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/user/register", "/user/login").permitAll()
            .anyRequest().authenticated()
        )
        .httpBasic(Customizer.withDefaults())
        .sessionManagement(session ->
            session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        )
        .build();
}

해결 방법 2: 토큰 기반 인증을 위한 사용자 지정 인증 필터

이 솔루션에서는 사용자 지정 필터가 사용자를 인증하고 응답 헤더에 토큰을 첨부합니다. 이 필터는 RESTful 애플리케이션에 이상적이며 React와 원활하게 작동할 수 있는 토큰 기반 인증을 사용합니다.

@Component
public class CustomAuthFilter extends OncePerRequestFilter {
    private final AuthenticationManager authenticationManager;
    public CustomAuthFilter(AuthenticationManager authManager) {
        this.authenticationManager = authManager;
    }
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
                                    throws ServletException, IOException {
        String authHeader = request.getHeader("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String token = authHeader.substring(7);
            Authentication authentication = new UsernamePasswordAuthenticationToken(token, null);
            Authentication authResult = authenticationManager.authenticate(authentication);
            SecurityContextHolder.getContext().setAuthentication(authResult);
        }
        filterChain.doFilter(request, response);
    }
}

솔루션 3: 서비스 클래스 조정 및 토큰 응답

이 서비스 구현은 모듈식 설계를 사용하여 성공적인 로그인 시 JWT 토큰을 전송하여 애플리케이션 전체에서 각 기능을 테스트하고 재사용할 수 있도록 합니다.

@Service
public class AuthenticationService {
    private final AuthenticationManager authenticationManager;
    private final TokenProvider tokenProvider; // Custom class for generating JWTs
    public AuthenticationService(AuthenticationManager authenticationManager,
                                 TokenProvider tokenProvider) {
        this.authenticationManager = authenticationManager;
        this.tokenProvider = tokenProvider;
    }
    public String authenticateAndGenerateToken(LoginDTO loginDTO) {
        Authentication authentication = authenticationManager
            .authenticate(new UsernamePasswordAuthenticationToken(loginDTO.getUserName(),
                                                                loginDTO.getPassword()));
        SecurityContextHolder.getContext().setAuthentication(authentication);
        return tokenProvider.createToken(authentication);
    }
}

토큰 생성 및 인증을 위한 단위 테스트

이 JUnit 테스트는 인증 및 토큰 생성이 올바르게 작동하는지 확인하고 보안 리소스에 액세스하기 위한 인증의 유효성을 검사합니다.

@SpringBootTest
@AutoConfigureMockMvc
public class AuthenticationServiceTest {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private AuthenticationService authenticationService;
    @Test
    public void testAuthenticateAndGenerateToken() throws Exception {
        LoginDTO loginDTO = new LoginDTO("user", "password");
        String token = authenticationService.authenticateAndGenerateToken(loginDTO);
        mockMvc.perform(MockMvcRequestBuilders.post("/login")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\\"userName\\":\\"user\\", \\"password\\":\\"password\\"}"))
                .andExpect(status().isOk())
                .andExpect(header().exists("Authorization"))
                .andExpect(content().string("Successfully Authenticated"));
    }
}

Stateless Spring 보안 애플리케이션의 세션 문제 극복

다음과 같은 경우 스프링 시큐리티 상태 비저장 API 통신용으로 구성되어 있으므로 특히 사용자 정의 로그인 흐름을 사용할 때 세션 관리가 까다로울 수 있습니다. 상태 비저장 구성은 각 요청이 이상적으로 자체 인증 토큰을 전달해야 하며 서버가 이전 요청과 독립적으로 유효성을 검사해야 함을 의미합니다. 이는 사용자가 한 번 로그인하면 해당 세션이 서버에 지속되는 기존 세션 기반 설정과 다릅니다. 인증을 처리하고 REST API를 통해 로그인 요청을 보내는 데 일반적으로 사용되는 React 프런트엔드를 사용하면 통합 시 JWT와 같은 토큰을 사용하여 각 API 요청이 인증되도록 해야 합니다.

Spring Security의 기본 세션 관리가 사용자 정의 구성으로 대체되면 Spring Security 내에서 사용자 인증을 설정하고 유지하는 방법을 이해하는 것이 중요합니다. SecurityContextHolder. 이 문제를 해결하는 한 가지 방법은 세션에 의존하는 대신 요청 헤더에 포함된 토큰을 확인하는 사용자 지정 인증 필터를 사용하는 것입니다. 애플리케이션에 세션 지속성 없이 반복적인 사용자 식별이 필요한 경우 토큰을 프런트엔드에 로컬로 저장하고 각 요청의 헤더에 포함할 수 있습니다. 이렇게 하면 서버가 세션 상태를 추적할 필요가 없어 안전하고 효율적인 RESTful API를 위한 무상태 설계 모델에 맞춰 조정됩니다.

또한 로그아웃 기능을 구현하는 것은 상태 비저장 애플리케이션에서 고려해야 할 또 다른 측면입니다. 서버에 세션이 없기 때문에 일반적으로 로그아웃하면 클라이언트 측에서 토큰이 제거됩니다. 이 시나리오에서는 클라이언트의 로컬 저장소에 있는 토큰을 삭제하고 서버에 있는 토큰이 포함된 요청을 거부하면 성공적인 로그아웃이 달성됩니다. 이 방법은 서버 측 세션 처리 없이 무단 액세스를 방지하여 더 높은 보안 수준을 지원합니다. 궁극적으로 이 구성은 특히 토큰 저장을 효과적으로 관리할 수 있는 React와 같은 프런트엔드 프레임워크와 결합할 때 확장성과 보안을 우선시하는 애플리케이션에 매우 적합합니다. 🚀

Spring Security 사용자 정의 인증 문제에 대한 일반적인 질문

  1. 설정한 후에도 401 Unauthorized 오류가 계속 발생하는 이유는 무엇입니까? SecurityContextHolder?
  2. 인증 컨텍스트가 지속되지 않으면 401 오류가 자주 발생합니다. 애플리케이션이 Stateless인 경우 토큰 기반 인증을 사용하고 있는지 확인하세요.
  3. Spring Security에서 상태 비저장 세션 관리를 어떻게 활성화합니까?
  4. 세트 SessionCreationPolicy.STATELESS 당신의 SecurityFilterChain 각 요청이 독립적으로 인증되도록 합니다.
  5. 역할은 무엇입니까? DaoAuthenticationProvider 사용자 정의 인증에서?
  6. 그만큼 DaoAuthenticationProvider 데이터베이스에 대해 사용자 자격 증명을 확인하고 보안 인증을 위해 비밀번호를 인코딩합니다.
  7. Spring Security에서 세션 관리를 위해 JWT 토큰을 사용할 수 있나요?
  8. 예, JWT 토큰은 상태 비저장 애플리케이션에 이상적입니다. 인증 후 토큰을 생성하고 후속 요청을 위해 헤더에 포함합니다.
  9. CSRF 보호는 상태 비저장 API에 어떤 영향을 미치나요?
  10. CSRF 보호는 일반적으로 다음을 사용하여 상태 비저장 API에서 비활성화됩니다. csrf().disable() 세션이 없는 API에는 불필요하기 때문입니다.
  11. 로그인이나 등록과 같은 일부 엔드포인트에 대한 공개 액세스를 허용하려면 어떻게 해야 합니까?
  12. 사용 authorizeHttpRequests 다음을 사용하여 인증 없이 액세스할 수 있는 엔드포인트를 지정합니다. permitAll().
  13. React를 사용하여 클라이언트 측에 토큰을 어떻게 저장합니까?
  14. 토큰을 저장하세요 localStorage 또는 sessionStorage, 그런 다음 백엔드가 각 요청을 인증할 수 있도록 각 요청의 헤더에 이를 포함합니다.
  15. API용 CSRF를 비활성화해도 안전합니까?
  16. CSRF는 주로 쿠키 기반 공격으로부터 보호하므로 앱이 토큰에 의존하거나 쿠키를 사용하지 않는 경우 API에 대해 CSRF를 비활성화하는 것이 안전합니다.
  17. 의 기능은 무엇입니까? OncePerRequestFilter 사용자 정의 인증에서?
  18. 이 필터는 요청당 한 번만 실행되므로 요청 주기에서 중복 확인 없이 인증 논리가 일관되게 적용됩니다.
  19. 내 인증 토큰이 다른 엔드포인트에서 인식되지 않는 이유는 무엇입니까?
  20. 각 요청의 헤더에 토큰을 설정하고 일관된 토큰 확인 프로세스를 사용하여 서버에서 토큰이 올바르게 검증되었는지 확인하세요.
  21. Spring Security 구성을 어떻게 테스트할 수 있나요?
  22. 사용 MockMvc 요청을 시뮬레이션하고, 인증 응답을 확인하고, 로그인 후에만 보호된 엔드포인트에 액세스할 수 있는지 확인하는 테스트에서.

사용자 정의 Spring 보안 인증의 401 오류 해결에 대한 최종 생각

사용자 정의 로그인 페이지로 Spring 기반 애플리케이션을 성공적으로 보호하려면 특히 상태 비저장 세션이나 토큰 기반 접근 방식을 사용하는 경우 신중한 구성이 필요합니다. React 프런트엔드와 통합할 때 보안 구성이 RESTful, 상태 비저장 원칙과 일치하는지 확인하면 세션 문제를 방지하는 데 도움이 될 수 있습니다.

수정부터 보안필터체인 토큰 기반 흐름 구현에 대한 설정을 통해 각 접근 방식은 안정적인 인증 설정을 만드는 역할을 합니다. 세션 관리, 토큰 처리 및 SecurityContext를 이해함으로써 Spring Security 애플리케이션에서 401 Unauthorized 오류를 해결할 수 있는 준비를 갖추게 됩니다. 🔒

Spring Security에서 사용자 정의 인증을 구현하기 위한 리소스 및 참조
  1. Spring Security의 구성 및 세션 관리에 대한 자세한 내용은 다음을 참조하세요. 스프링 보안 공식 문서 .
  2. React 프런트엔드를 사용한 사용자 정의 인증 흐름을 이해하고 구현하려면 다음 가이드를 참조하세요. 스프링 보안 및 React 로그인 튜토리얼 .
  3. 이 기사의 구성 예제와 Spring Boot 설정은 다음의 통찰력을 기반으로 합니다. Baeldung Spring 보안 세션 가이드 .