programing

스프링 부트 상태 비저장 필터가 두 번 호출되는 이유는 무엇입니까?

firstcheck 2023. 6. 22. 23:55
반응형

스프링 부트 상태 비저장 필터가 두 번 호출되는 이유는 무엇입니까?

Spring Boot을 이용하여 개발한 rest api에 stateless 토큰 기반 인증을 구현하려고 합니다.이 개념은 클라이언트가 모든 요청이 포함된 JWT 토큰을 포함하고 필터가 요청에서 이를 추출하여 토큰의 내용을 기반으로 관련 인증 개체와 함께 SecurityContext를 설정한다는 것입니다.그런 다음 요청은 정상적으로 라우팅되고 매핑된 메서드에서 @PreAuthorize를 사용하여 보안됩니다.

내 보안 구성은 다음과 같습니다.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private JWTTokenAuthenticationService authenticationService;

@Override
protected void configure(HttpSecurity http) throws Exception
{
    http
        .csrf().disable()
        .headers().addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN))
        .and()
        .authorizeRequests()
            .antMatchers("/auth/**").permitAll()
            .antMatchers("/api/**").authenticated()
            .and()
        .addFilterBefore(new StatelessAuthenticationFilter(authenticationService), UsernamePasswordAuthenticationFilter.class);
}

GenericFilterBean을 확장하는 상태 비저장 필터를 사용하여 다음과 같이 정의합니다.

public class StatelessAuthenticationFilter extends GenericFilterBean {

    private static Logger logger = Logger.getLogger(StatelessAuthenticationFilter.class);

    private JWTTokenAuthenticationService authenticationservice;

    public StatelessAuthenticationFilter(JWTTokenAuthenticationService authenticationService)
    {
        this.authenticationservice = authenticationService;
    }

    @Override
    public void doFilter(   ServletRequest request,
                            ServletResponse response,
                            FilterChain chain) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        Authentication authentication = authenticationservice.getAuthentication(httpRequest);
        SecurityContextHolder.getContext().setAuthentication(authentication);

        logger.info("===== Security Context before request =====");
        logger.info("Request for: " + httpRequest.getRequestURI());
        logger.info(SecurityContextHolder.getContext().getAuthentication());
        logger.info("===========================================");

        chain.doFilter(request, response);

        SecurityContextHolder.getContext().setAuthentication(null);
        logger.info("===== Security Context after request =====");
        logger.info("Request for: " + httpRequest.getRequestURI());
         logger.info(SecurityContextHolder.getContext().getAuthentication());
        logger.info("===========================================");
    }

}

엔드포인트는 다음과 같이 정의됩니다.

@PreAuthorize("hasAuthority('user')")
@RequestMapping (   value="/api/attachments/{attachmentId}/{fileName:.+}",
                    method = RequestMethod.GET)
public ResponseEntity<byte[]> getAttachedDocumentEndpoint(@PathVariable String attachmentId, @PathVariable String fileName)
{
    logger.info("GET called for /attachments/" + attachmentId + "/" + fileName);

    // do something to get the file, and return ResponseEntity<byte[]> object
}

토큰을 포함하여 /api/attachments/some attachments/some filename에서 GET을 실행하면 필터가 토큰과 함께 한 번, 없는 한 번 두 번 호출되는 것을 알 수 있습니다.그러나 요청에 매핑된 나머지 컨트롤러는 한 번만 호출됩니다.

[INFO] [06-04-2015 12:26:44,465] [JWTTokenAuthenticationService] getAuthentication - Getting authentication based on token supplied in HTTP Header
[INFO] [06-04-2015 12:26:44,473] [StatelessAuthenticationFilter] doFilter - ===== Security Context before request =====
[INFO] [06-04-2015 12:26:44,473] [StatelessAuthenticationFilter] doFilter - Request for: /api/attachments/1674b08b6bbd54a6efaff4a780001a9e/jpg.png
[INFO] [06-04-2015 12:26:44,474] [StatelessAuthenticationFilter] doFilter - Name:iser, Principal:user, isAuthenticated:true, grantedAuthorites:[user]
[INFO] [06-04-2015 12:26:44,474] [StatelessAuthenticationFilter] doFilter - ===========================================
[INFO] [06-04-2015 12:26:44,476] [AttachmentRESTController] getAttachedDocumentEndpoint - GET called for /api/attachments/1674b08b6bbd54a6efaff4a780001a9e/jpg.png
[INFO] [06-04-2015 12:26:44,477] [AttachmentDBController] getAttachment - getAttachment method called with attachmentId:1674b08b6bbd54a6efaff4a780001a9e , and fileName:jpg.png
[INFO] [06-04-2015 12:26:44,483] [StatelessAuthenticationFilter] doFilter - ===== Security Context after request =====
[INFO] [06-04-2015 12:26:44,484] [StatelessAuthenticationFilter] doFilter - Request for: /api/attachments/1674b08b6bbd54a6efaff4a780001a9e/jpg.png
[INFO] [06-04-2015 12:26:44,484] [StatelessAuthenticationFilter] doFilter - 
[INFO] [06-04-2015 12:26:44,484] [StatelessAuthenticationFilter] doFilter - ===========================================
[INFO] [06-04-2015 12:26:44,507] [JWTTokenAuthenticationService] getAuthentication - No token supplied in HTTP Header
[INFO] [06-04-2015 12:26:44,507] [StatelessAuthenticationFilter] doFilter - ===== Security Context before request =====
[INFO] [06-04-2015 12:26:44,507] [StatelessAuthenticationFilter] doFilter - Request for: /api/attachments/1674b08b6bbd54a6efaff4a780001a9e/jpg.png
[INFO] [06-04-2015 12:26:44,507] [StatelessAuthenticationFilter] doFilter - 
[INFO] [06-04-2015 12:26:44,508] [StatelessAuthenticationFilter] doFilter - ===========================================
[INFO] [06-04-2015 12:26:44,508] [StatelessAuthenticationFilter] doFilter - ===== Security Context after request =====
[INFO] [06-04-2015 12:26:44,508] [StatelessAuthenticationFilter] doFilter - Request for: /api/attachments/1674b08b6bbd54a6efaff4a780001a9e/jpg.png
[INFO] [06-04-2015 12:26:44,508] [StatelessAuthenticationFilter] doFilter - 
[INFO] [06-04-2015 12:26:44,508] [StatelessAuthenticationFilter] doFilter - ===========================================

이게 무슨 일입니까?

편집:

처음에 생각했던 것보다 훨씬 더 이상합니다. 단순한 메시지만 반환하는 단순한 엔드포인트를 구현하면 예상되는 동작이 표시됩니다. 위와 같이 데이터를 ResponseEntity로 반환하려고 할 때만 이러한 문제가 발생하는 것 같습니다.

끝점:

@PreAuthorize("hasAuthority('user')")
@RequestMapping("/api/userHelloWorld")
public String userHelloWorld()
{
    return "Hello Secure User World";
}

필터링할 단일 호출을 표시하는 출력(추가 디버그 사용):

[INFO] [06-04-2015 19:43:25,831] [JWTTokenAuthenticationService] getAuthentication - Getting authentication based on token supplied in HTTP Header
[INFO] [06-04-2015 19:43:25,844] [StatelessAuthenticationFilter] doFilterInternal - ===== Security Context before request =====
[INFO] [06-04-2015 19:43:25,844] [StatelessAuthenticationFilter] doFilterInternal - Request for: /api/userHelloWorld
[INFO] [06-04-2015 19:43:25,844] [StatelessAuthenticationFilter] doFilterInternal - Response = null 200
[INFO] [06-04-2015 19:43:25,844] [StatelessAuthenticationFilter] doFilterInternal - Name:user, Principal:user, isAuthenticated:true, grantedAuthorites:[user]
[INFO] [06-04-2015 19:43:25,845] [StatelessAuthenticationFilter] doFilterInternal - ===========================================
[DEBUG] [06-04-2015 19:43:25,845] [DispatcherServlet] doService - DispatcherServlet with name 'dispatcherServlet' processing GET request for [/api/userHelloWorld]
[DEBUG] [06-04-2015 19:43:25,847] [AbstractHandlerMethodMapping] getHandlerInternal - Looking up handler method for path /api/userHelloWorld
[DEBUG] [06-04-2015 19:43:25,848] [AbstractHandlerMethodMapping] getHandlerInternal - Returning handler method [public java.lang.String RESTController.userHelloWorld()]
[DEBUG] [06-04-2015 19:43:25,849] [DispatcherServlet] doDispatch - Last-Modified value for [/api/userHelloWorld] is: -1
[DEBUG] [06-04-2015 19:43:25,851] [AbstractMessageConverterMethodProcessor] writeWithMessageConverters - Written [Hello Secure User World] as "text/plain;charset=UTF-8" using [org.springframework.http.converter.StringHttpMessageConverter@3eaf6fe7]
[DEBUG] [06-04-2015 19:43:25,852] [DispatcherServlet] processDispatchResult - Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
[DEBUG] [06-04-2015 19:43:25,852] [FrameworkServlet] processRequest - Successfully completed request
[INFO] [06-04-2015 19:43:25,852] [StatelessAuthenticationFilter] doFilterInternal - ===== Security Context after request =====
[INFO] [06-04-2015 19:43:25,853] [StatelessAuthenticationFilter] doFilterInternal - Request for: /api/userHelloWorld
[INFO] [06-04-2015 19:43:25,853] [StatelessAuthenticationFilter] doFilterInternal - Response = text/plain;charset=UTF-8 200
[INFO] [06-04-2015 19:43:25,853] [StatelessAuthenticationFilter] doFilterInternal - 
[INFO] [06-04-2015 19:43:25,853] [StatelessAuthenticationFilter] doFilterInternal - ===========================================

필터의 일부로 저에게는 여전히 블랙 매직이 있습니다(*). 하지만 저는 이것이 일반적인 문제라는 것을 알고 있으며, 스프링에는 다음과 같은 하위 클래스가 있습니다.GenericFilterBean특히 그것을 다루기 위해: 그냥 사용.OncePerRequestFilter기본 클래스 및 필터는 한 번만 호출해야 합니다.

(*) 요청 발송자를 통해 요청이 여러 번 발송되어 발생할 수 있다고 읽었습니다.

좋아요 - 그래서 이것은 꽤 우스꽝스럽지만, 제가 요청을 호출하는 방식에 문제가 있는 것 같습니다(포스트맨 크롬 확장을 통해).

우체부가 2개의 요청을 발포한 것 같습니다 하나는 헤더가 있고 하나는 없습니다https://github.com/a85/POSTMan-Chrome-Extension/issues/615 에서 이를 설명하는 공개 버그 보고서가 있습니다.

요청이 curl을 사용하여 호출된 경우 또는 브라우저에서 직접 호출된 경우에는 동작이 표시되지 않습니다.

언급URL : https://stackoverflow.com/questions/30643064/why-is-my-spring-boot-stateless-filter-being-called-twice

반응형