programing

@WithMockUser가 없는 Spring Boot 응용 프로그램에서 "A Authentication Object not found in the SecurityContext"를 제거할 수 없음

jooyons 2023. 10. 7. 10:44
반응형

@WithMockUser가 없는 Spring Boot 응용 프로그램에서 "A Authentication Object not found in the SecurityContext"를 제거할 수 없음

저는 이미 며칠을 꼬박 꼬박 제가 무엇을 잘못하고 있는지 알아내기 위해 노력했지만, 왜 그것이 작동하지 않는지 전혀 모르겠습니다.먼저, 다음 구성은 대부분 제가 작업 중인 다른 프로젝트에서 복사한 것이며, 해당 프로젝트는 아무런 문제 없이 작동할 수 있습니다(다만 약간 다르게 구성되어 있고 이전의 Spring/Spring Boot 버전을 사용함).이 클래스들이 잘못 구성되어 있고 다음 구성 클래스에서 오타나 다른 것을 볼 수 없다고 생각하기 때문에 코드를 줄일 수 없습니다.처음부터 다시 쓰고 싶지만 이번에는 그렇지 않습니다.(이름이 다음으로 시작하는 구성요소I스프링 프레임워크의 일부가 아니라 제 것입니다.

여기서 예외는 다음과 같습니다.

org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:379) ~[spring-security-core-4.2.1.RELEASE.jar:4.2.1.RELEASE]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:223) ~[spring-security-core-4.2.1.RELEASE.jar:4.2.1.RELEASE]
    at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:65) ~[spring-security-core-4.2.1.RELEASE.jar:4.2.1.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:656) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at FOO.BAR.AuthenticationController$$EnhancerBySpringCGLIB$$b4949cda.getSelf(<generated>) ~[classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_65]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_65]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_65]
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_65]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:220) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) [spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861) [spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:622) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) [spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:65) [spring-test-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167) [spring-test-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134) [spring-test-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:155) [spring-test-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at FOO.BAR.AbstractControllerTest.get(AbstractControllerTest.java:55) [web-test-0-SNAPSHOT.jar:na]
    at FOO.BAR.AuthenticationControllerOkTest.testAuthenticate(AuthenticationControllerOkTest.java:31) [test-classes/:na]
    <...JUnit stuff...>

내가 웹에서 찾은 유일한 비슷한 질문은 이것입니다.하지만 제가 틀리지 않았다면 약간 다른 경우를 설명하는 것 같습니다.물론 아무 가치도 없는 일: 추가@WithMockUser테스트에 예외가 발생하는 것은 아니지만 인증 컨트롤러를 테스트하고 있기 때문에 이 주석을 사용할 수 없습니다(물론 프로덕션 모드에서는 불가능합니다).

추상 사용자 정의GlobalMethodSecurityConfiguration 유형

사용자 지정 유형 지원을 추가하기 위해 사용하는 보일러 플레이트 클래스입니다.@PreAuthorize. 꽤 쉽다고 생각하는데요, 그리고 이건 의심스러워 보이지 않습니다.

public abstract class AbstractCustomTypesGlobalMethodSecurityConfiguration
        extends GlobalMethodSecurityConfiguration {

    @Nonnull
    protected abstract ApplicationContext applicationContext();

    @Nonnull
    protected abstract ConversionService conversionService();

    @Nonnull
    protected abstract PermissionEvaluator permissionEvaluator();

    @Nonnull
    @SuppressWarnings("DesignForExtension")
    protected Object filter(@Nonnull final MethodSecurityExpressionHandler handler, @Nonnull final Object filterTarget,
            @Nonnull final Expression filterExpression, @Nonnull final EvaluationContext context) {
        return handler.filter(filterTarget, filterExpression, context);
    }

    @Override
    protected final MethodSecurityExpressionHandler createExpressionHandler() {
        final ApplicationContext applicationContext = applicationContext();
        final TypeConverter typeConverter = new StandardTypeConverter(conversionService());
        final DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler() {
            @Override
            public StandardEvaluationContext createEvaluationContextInternal(final Authentication authentication, final MethodInvocation methodInvocation) {
                final StandardEvaluationContext decoratedStandardEvaluationContext = super.createEvaluationContextInternal(authentication, methodInvocation);
                return new ForwardingStandardEvaluationContext() {
                    @Override
                    protected StandardEvaluationContext standardEvaluationContext() {
                        return decoratedStandardEvaluationContext;
                    }

                    @Override
                    public TypeConverter getTypeConverter() {
                        return typeConverter;
                    }
                };
            }

            @Override
            public Object filter(final Object filterTarget, final Expression filterExpression, final EvaluationContext context) {
                return AbstractCustomTypesGlobalMethodSecurityConfiguration.this.filter(this, filterTarget, filterExpression, context);
            }
        };
        handler.setApplicationContext(applicationContext);
        handler.setPermissionEvaluator(permissionEvaluator());
        return handler;
    }

}

보안 구성

기본적으로 하기 구성은 템플릿 방법 설계 패턴을 사용하여 필요한 콩을 제공하는 후자의 구성을 확장하는 것입니다.의심할 만한 건 없어요, 제 생각엔 다른 건 빼고요@EnableGlobalMethodSecurity, 그러나 주석이 작동하는 것처럼 보이고 플래그를 활성화/비활성화하는 것은 전체적인 동작에도 영향을 미칩니다. (주석을 다른 구성으로 이동하는 것도 경우에 따라 작동하지 않을 수 있습니다.)

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
class SecurityConfiguration
        extends AbstractCustomTypesGlobalMethodSecurityConfiguration {

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private ConversionService conversionService;

    @Nonnull
    @Override
    protected ApplicationContext applicationContext() {
        return applicationContext;
    }

    @Nonnull
    @Override
    protected ConversionService conversionService() {
        return conversionService;
    }

    @Nonnull
    @Override
    protected final PermissionEvaluator permissionEvaluator() {
        return getAlwaysPermittedPermissionEvaluator();
    }

    @Nonnull
    @Override
    protected final Object filter(@Nonnull final MethodSecurityExpressionHandler handler, @Nonnull final Object filterTarget,
            @Nonnull final Expression filterExpression, @Nonnull final EvaluationContext context) {
        final MethodSecurityExpressionOperations operations = (MethodSecurityExpressionOperations) context.getRootObject().getValue();
        operations.setFilterObject(filterTarget);
        return filterExpression.getValue(context, Object.class);
    }

}

웹 보안 구성

서비스 엔드포인트에 액세스하기 위한 몇 가지 규칙을 정의하는 사소한 웹 보안 구성입니다.필터는 다음과 같이 "빈(bean)"됩니다.authenticationTokenProcessingFilter예외가 먼저 발생하므로 호출되지 않습니다.

@Configuration
@EnableWebSecurity
class WebSecurityConfiguration
        extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private ITokenAuthenticationService tokenAuthenticationService;

    @Override
    protected final void configure(final HttpSecurity httpSecurity)
            throws Exception {
        httpSecurity
                .authorizeRequests()
                .antMatchers(POST, "/api/v0/authentication").permitAll()
                .antMatchers("/api/v0/**").fullyAuthenticated()
                .antMatchers("/**").permitAll();
        httpSecurity
                .csrf().disable()
                .httpBasic()
                .authenticationEntryPoint(customAuthenticationEntryPoint());
        httpSecurity
                .sessionManagement()
                .sessionCreationPolicy(STATELESS);
        httpSecurity
                .addFilterBefore(authenticationTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    AuthenticationEntryPoint customAuthenticationEntryPoint() {
        return getCustomAuthenticationEntryPoint();
    }

    @Bean
    GenericFilterBean authenticationTokenProcessingFilter() {
        return getAuthenticationTokenProcessingFilter(tokenAuthenticationService);
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Autowired
    void registerGlobalAuthentication(final AuthenticationManagerBuilder managerBuilder)
            throws Exception {
        managerBuilder
                .userDetailsService(userDetailsService)
                .and()
                .eraseCredentials(false);
    }

}

그것은 진단이 필요할 수도 있는 거의 모든 코드이고 바라건대 완전한 코드이기를 바랍니다.고장 났거나 그런 것 같지는 않은데, 왜 예외를 받는지 이유를 아직도 모르겠어요.머리가 희끗희끗해지는 느낌이 들기 시작했습니다.

어떤 도움이라도 주시면 대단히 감사하겠습니다!

종속성:

  • 스프링 골조부트:스프링-부트 종속성:1.4.3.RELEASE:폼
  • 스프링 골조부팅:spring-boot-starter-web:1.4.3.RELEASE
  • 스프링 골조보안:spring-security-config:4.2.1.RELEASE
  • 스프링 골조보안:스프링-보안-코어:4.2.1.RELEASE
  • 스프링 골조보안:spring-security-web:4.2.1.RELEASE

편집 1

@Test
@DatabaseSetup(DATASET)
// @WithMockUser is commented out -- we're authenticating as Alice ourselves to obtain the authentication token
public void testAuthenticate()
        throws Exception {
    final MockHttpServletResponse response = post("/authentication", asJson(), identityWithKeyGsonIncomingDto("Alice", "alice123"))
            // Here is where it fails: the exception causes HTTP 500 rather than HTTP 201
            .andExpect(status().isCreated())
            .andReturn()
            .getResponse();
    @SuppressWarnings("unchecked")
    final Map<String, Object> responseMap = gson.fromJson(response.getContentAsString(), Map.class);
    final String token = (String) responseMap.get("token");
    get("/users/self", headers("Authorization", token))
            .andExpect(status().isOk());
}

편집2

public final class AuthenticationTokenProcessingFilter
        extends GenericFilterBean {

...

    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
            throws IOException, ServletException {
        @Nullable
        final String authenticationToken = getAuthenticationToken(request);
        if ( authenticationToken != null ) {
            try {
                final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
                final Authentication authentication = tokenAuthenticationService.authenticate(authenticationToken, httpServletRequest);
                setCurrentAuthentication(authentication);
            } catch ( final AuthenticationException ex ) {
                ...
            }
        }
        chain.doFilter(request, response);
    }

}

유감스럽게도 위 필터가 어떤 이유로 제어하기 전에 예외가 발생합니다.이 필터는 특정 상황에서만 현재 사용자 인증을 설정하기 위한 것이며, 익명으로 설정하는 것은 아닙니다.적어도 제 다른 모듈에서는 이렇게 작동합니다.

제가 제공한 수많은 코드가 문제의 진짜 원인을 밝히지 못해 죄송합니다.몇 가지 실험을 더 해본 결과, 생산 모드로 사용 케이스를 실행하라는 제안을 받았고(첫 번째 테스트 때문에 완전히 잊어버렸습니다), 문제없이 생산 모드로 작동합니다.테스트로 범위를 좁혀 테스트 주석을 먼저 확인하여 다음을 포함한 모든 주석이 있는지 확인했습니다.@WithSecurityContextTestExecutionListener다른 모듈이 가지고 있는 것처럼.을 놓쳤다는 되었습니다.을 미칠 수 범위는 단 객체다)입니다. 청취자들이 영향을 미칠 수 있는 가장 작은 범위는 단일 테스트이며 아마도 조롱당한 MVC 객체일 것입니다.MockMvc원래 질문에 포함하지 않은 것은 단순히 구성 문제라고 생각했기 때문입니다)가 잘 구성되어 있지 않습니다.네님.MockMvc과 같은 되었습니다).@Before테스트 슈퍼 클래스 중 하나의 메서드):

mvc = webAppContextSetup(webApplicationContext)
        .build();

MockMvc인스턴스도 구성해야 합니다.

mvc = webAppContextSetup(webApplicationContext)
        .apply(springSecurity()) // this is the key
        .build();

"주석은 필요한 모든 것을 스스로 할 수 있습니다."라고 믿지 않는 좋은 예입니다.안타깝게도 많은 시간을 낭비했지만 결국 그 원인을 찾을 수 있어서 기쁩니다.

언급URL : https://stackoverflow.com/questions/43008149/cannot-get-rid-of-an-authentication-object-was-not-found-in-the-securitycontext

반응형