Spring Аутентификация безопасностиCredentialsNotFoundException, SecurityContextHolder.getContext имеет значение null

У меня странная ошибка, часы отладки и я не могу понять.

ОБНОВЛЕНИЕ 1: Я использую Spring Security 4.0.3, работающий на Tomcat 7.

Проблема близка к этому вопросу, возможно, SecurityContextHolder теряется во время response.redirect(), но ответ не помогает.

Проблема кажется близкой к этому вопросу, но ответ не имеет для меня никакого смысла.

Это моя конфигурация:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class ProjectSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests().antMatchers("/login").anonymous();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser(Constants.PROFIL_ADMIN).password(Constants.PROFIL_ADMIN).
            roles("ADMIN","TEST_SERVICE");
    }
}

После входа в систему я пытаюсь получить защищенный URL-адрес:

@RequestMapping(value = "/myurl", method = RequestMethod.GET)
@ResponseBody
public boolean getTestService(HttpServletRequest request)
        throws SQLException, PoRulesException {

    System.out.println("get security context");
    System.out.println("--------------------");
    SecurityContext secuContext = (SecurityContext) request.getSession().getAttribute("SPRING_SECURITY_CONTEXT");
    System.out.println(secuContext);
    System.out.println("get security context holder");
    System.out.println(SecurityContextHolder.getContext());

    return testService.getTestMethod();
}

метод внутри службы

@Secured("ROLE_TEST_SERVICE")
public boolean getTestMethod() {
    Sysout.out.println("Hiii")
    return true
}

Теперь журнал, когда он не работает:

INFO    2016-07-11 15:57:00,006 [http-bio-8080-exec-3] com.po.mvc.controller.LoginController  - Login
INFO    2016-07-11 15:57:00,057 [http-bio-8080-exec-3] com.po.mvc.controller.LoginController  - User : axel
INFO    2016-07-11 15:57:00,065 [http-bio-8080-exec-3] com.po.mvc.controller.LoginController  - Authenticate : true
INFO    2016-07-11 15:57:00,065 [http-bio-8080-exec-3] com.po.mvc.controller.LoginController  - Authenticate : org.springframew[email protected]bbbc4f45: Principal: [email protected]: Username: ADM; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN,ROLE_CONSULTER_CA,ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.sprin[email protected]fffde5d4: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 613DFE47B2CE6E29DA3C227F8E028590; Granted Authorities: ROLE_ADMIN, ROLE_TEST_SERVICE
get security context
--------------------
[email protected]bc4f45: Authentication: org.springframew[email protected]bbbc4f45: Principal: [email protected]: Username: ADM; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN,ROLE_CONSULTER_CA,ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.sprin[email protected]fffde5d4: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 613DFE47B2CE6E29DA3C227F8E028590; Granted Authorities: ROLE_ADMIN, ROLE_TEST_SERVICE
get security context holder
[email protected]ffffff: Null authentication
ERROR   2016-07-11 15:57:01,960 [http-bio-8080-exec-10] com.po.exception.GlobalControllerExceptionHandler  - org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:378)
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:222)
    at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:64)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655)
    at com.po.service.CAService$$EnhancerBySpringCGLIB$$f6e21857.getVarAndPrevCa(<generated>)
    at com.po.mvc.controller.CaController.getVarAndPrevCa(CaController.java:56)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:222)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:814)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:737)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:969)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:860)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:845)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:304)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:224)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:169)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:929)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:405)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:964)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:515)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:304)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:745)

и когда он работает. Я просто вижу разницу... контекст Spring установлен и содержит учетные данные как прокси sysout, но SecurityContextHolder не установлен.

INFO    2016-07-11 16:09:45,374 [http-bio-8080-exec-3] com.po.mvc.controller.LoginController  - Login
INFO    2016-07-11 16:09:45,393 [http-bio-8080-exec-3] com.po.mvc.controller.LoginController  - User : axel
INFO    2016-07-11 16:09:45,395 [http-bio-8080-exec-3] com.po.mvc.controller.LoginController  - Authenticate : true
INFO    2016-07-11 16:09:45,395 [http-bio-8080-exec-3] com.po.mvc.controller.LoginController  - Authenticate : org.springframew[email protected]bbbc4f45: Principal: [email protected]: Username: ADM; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN,ROLE_TEST_SERVICE; Credentials: [PROTECTED]; Authenticated: true; Details: org.sprin[email protected]fffde5d4: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 613DFE47B2CE6E29DA3C227F8E028590; Granted Authorities: ROLE_ADMIN, ROLE_TEST_SERVICE
get security context
--------------------
[email protected]bc4f45: Authentication: org.springframew[email protected]bbbc4f45: Principal: [email protected]: Username: ADM; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN,ROLE_TEST_SERVICE Credentials: [PROTECTED]; Authenticated: true; Details: org.sprin[email protected]fffde5d4: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 613DFE47B2CE6E29DA3C227F8E028590; Granted Authorities: ROLE_ADMIN, ROLE_CONSULTER_CA, ROLE_USER
get security context holder
[email protected]40cc59: Authentication: org.springframew[email protected]4440cc59: Principal: [email protected]: Username: ADM; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN,ROLE_CONSULTER_CA,ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.sprin[email protected]166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 19994511EEE72462E8D680CDADCFEC4C; Granted Authorities: ROLE_ADMIN, ROLE_CONSULTER_CA, ROLE_USER

INFO    2016-07-11 16:09:46,879 [http-bio-8080-exec-3] Hiii

Единственная разница между тем, когда он работает, а когда нет:

  • работает, когда я подключаюсь со страницы моего веб-сайта, например, я нахожусь на mywebsite.com, который является общедоступным и собирается mywebsite.com/login?user=me работает
  • но не работает, если я пришел из Google на mywebsite.com/login?user=me

Свойство session SPRING_SECURITY_CONTEXT задано в обоих случаях, но не SecurityContextHolder, которое вызвало исключение внутри @Secured строка 222

Я не хочу использовать трюк, например check вручную SecurityContextHolder в методе (после перенаправления), а если null установить свойство session SPRING_SECURITY_CONTEXT, которое не является null, я хочу исправить проблему в корне.

Ответ 1

Наконец, я нашел решение здесь

https://spring.io/blog/2013/07/03/spring-security-java-config-preview-web-security/

моя конфигурация отсутствовала в этой точке

Последний шаг - нам нужно сопоставить springSecurityFilterChain. Мы можем легко сделать это, расширив AbstractSecurityWebApplicationInitializer и, возможно, переопределение методов для настройки отображения.

Самый базовый пример ниже принимает сопоставление по умолчанию и добавляет springSecurityFilterChain со следующими характеристиками:

springSecurityFilterChain отображается на "/*" springSecurityFilterChain использует типы отправки ERROR и REQUEST. SpringSecurityFilterChain сопоставление вставляется перед любым сервлетом Конфигурации фильтров, которые уже настроены

public class SecurityWebApplicationInitializer 
   extends AbstractSecurityWebApplicationInitializer {
}

Просто добавив, что класс решил проблему, если вы не можете добавить @Order

"Заказ WebApplicationInitializer"

Если любые сопоставления фильтров сервлета добавляются после Вызывается AbstractSecurityWebApplicationInitializer, они могут быть случайно добавлено до springSecurityFilterChain. Если приложение содержит экземпляры фильтров, которые не нужно защищать, springSecurityFilterChain должен быть перед любыми другими фильтрами. Аннотацию @Order можно использовать, чтобы WebApplicationInitializer загружается в детерминированном порядке.

@Order(1)
public class SpringWebMvcInitializer extends
   AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new Class[] { HelloWebSecurityConfiguration.class };
  }
  ...
}

    @Order(2)
    public class SecurityWebApplicationInitializer 
       extends AbstractSecurityWebApplicationInitializer {
    }

Ответ 2

Предполагая, что вы не можете интегрироваться с существующим провайдером единого входа, следующее решение, вероятно, лучшее, на что вы можете надеяться.

В принципе, то, что вы делаете, не будет работать, если вы не ограничитесь одним потоком в вашем контейнере (это ужасная идея). Из документов SecurityContextHolder:

Связывает заданный SecurityContext с текущим потоком выполнения.

Это важно - текущий поток выполнения. Если вы сделаете запрос на вход, который обрабатывается потоком A, то вы пытаетесь перейти на защищенную страницу, но этот запрос обрабатывается потоком B, а затем SecurityContext, установленный в A, не будет доступен на B.

Spring Безопасность вполне способна справиться с этим для вас, сохраняя контекст безопасности в сеансе и сохраняя/извлекая его по мере необходимости (см. org.springframework.security.web.session.SessionManagementFilter). Однако вам нужно внести некоторые изменения в вашу конфигурацию.

В первую очередь вам нужно создать новый класс, который простирается от AbstractAuthenticationProcessingFilter и переопределить метод attemptAuthentication. Затем этот новый фильтр должен быть зарегистрирован и ему необходимо будет заменить существующий org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter. Этот класс также будет хорошим местом для того, чтобы понять, как все это объединяется, и что вы можете настроить.

Этот новый фильтр будет делать что-то в строках

public Authentication attemptAuthentication(HttpServletRequest request,
    HttpServletResponse response) throws AuthenticationException {
    UserLDAP ldapUser = CorpLDAP.findUser(request);
    User user = new User(ldapUser.getProfil(), ldapUser.getPwd(), Collections.emptyList());
    return new UsernamePasswordAuthenticationToken(user, "", Collections.emptyList());
}

Это приведет к возврату полностью аутентифицированного пользователя, который может быть использован позже. Это то, что будет храниться в SecurityContextHolder и может быть легко извлечено. Он также будет сохранен в сеансе (если он настроен), поэтому я рекомендовал бы обеспечить его сериализацию. Поскольку вы не используете пароль, я настоятельно рекомендую не хранить его в User.

Надеемся, что это установит вас в правильном направлении.