Spring Security身份验证凭据未找到异常,SecurityContextHolder.getContext为null

12

我遇到了一个奇怪的错误,花费数小时进行调试,但我无法理解。

更新1:我使用的是运行在Tomcat 7上的Spring Security 4.0.3。

问题类似于这个问题,也许在response.redirect()期间,SecurityContextHolder丢失了,但该答案没有帮助。

问题似乎也与这个问题相似,但答案对我来说毫无意义。

这是我的配置:

@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.springframework.security.authentication.UsernamePasswordAuthenticationToken@bbbc4f45: Principal: org.springframework.security.core.userdetails.User@fc8a: 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.springframework.security.web.authentication.WebAuthenticationDetails@fffde5d4: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 613DFE47B2CE6E29DA3C227F8E028590; Granted Authorities: ROLE_ADMIN, ROLE_TEST_SERVICE
get security context
--------------------
org.springframework.security.core.context.SecurityContextImpl@bbbc4f45: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@bbbc4f45: Principal: org.springframework.security.core.userdetails.User@fc8a: 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.springframework.security.web.authentication.WebAuthenticationDetails@fffde5d4: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 613DFE47B2CE6E29DA3C227F8E028590; Granted Authorities: ROLE_ADMIN, ROLE_TEST_SERVICE
get security context holder
org.springframework.security.core.context.SecurityContextImpl@ffffffff: 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.springframework.security.authentication.UsernamePasswordAuthenticationToken@bbbc4f45: Principal: org.springframework.security.core.userdetails.User@fc8a: 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.springframework.security.web.authentication.WebAuthenticationDetails@fffde5d4: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 613DFE47B2CE6E29DA3C227F8E028590; Granted Authorities: ROLE_ADMIN, ROLE_TEST_SERVICE
get security context
--------------------
org.springframework.security.core.context.SecurityContextImpl@bbbc4f45: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@bbbc4f45: Principal: org.springframework.security.core.userdetails.User@fc8a: 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.springframework.security.web.authentication.WebAuthenticationDetails@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
org.springframework.security.core.context.SecurityContextImpl@4440cc59: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@4440cc59: Principal: org.springframework.security.core.userdetails.User@fc8a: 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.springframework.security.web.authentication.WebAuthenticationDetails@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时,它就不工作了。

在这两种情况下都设置了会话属性SPRING_SECURITY_CONTEXT,但没有设置SecurityContextHolder,这在@Secured中引发异常第222行

我不想使用像在方法中手动检查SecurityContextHolder,如果为null则设置会话属性SPRING_SECURITY_CONTEXT(该属性不为null)这样的技巧,我想要从根本上解决这个问题。


1
在控制器中设置SecurityContext中的身份验证对象,而不是经典的Spring安全模式Filter > Manager > Provider,对我来说听起来很奇怪。 - jlumietu
尽管如此,我看到了一件奇怪的事情。根据UsernamePasswordAuthenticationToken API,它有两个构造函数;一个接受(Object principal,Object credentials),生成非认证令牌,另一个接受(Object principal,Object credentials,Collection<? extends GrantedAuthority> authorities),返回已认证的令牌。您正在使用第一个构造函数,因此您的令牌可能未经过身份验证。 - jlumietu
我们通过SSO访问门户,然后从SSO字符串中使用LDAP访问每个应用程序,LDAP登录是通过内部API(在我的示例中为CorpLDAP)实现的,如果经过授权则返回用户,否则返回null。因此,我无法对登录过程进行操作,该过程具有强大的安全性。在这里,Spring安全性不适用于登录,因此“登录-密码”并不重要。LDAP返回一个用户(如果未经授权则不返回),并且我使用Spring安全性来定义哪个配置文件可以访问服务。 - amdev
1
什么样的SSO?如果您正在使用SAML或OAuth,与其尝试绕过Spring Security,不如正确集成它可能更有意义。如果没有,那么就会变得有些棘手。 - ipsi
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - amdev
1
使用LDAP和Spring Security中已有的每个API都可以。@ipsi所说的有点棘手的是从控制器而不是Filter> AuthenticationManager> Provider> UserDetailsService模式中调用ldap身份验证。 - jlumietu
2个回答

6
假设您无法与现有的SSO提供商集成,那么以下解决方案可能是您所能期望的最佳方案。从SecurityContextHolder的文档中可以看出,从根本上讲,除非您将自己限制在容器中的单个线程(这是一个糟糕的想法),否则您所做的事情是行不通的。
“将给定的SecurityContext与当前执行线程相关联。”这很重要——当前执行线程。如果您发出登录请求(由线程A处理),然后尝试访问受保护页面,但此请求由线程B处理,则您在A上设置的SecurityContext将不会在B上可用。
Spring Security很擅长为您处理这个问题,通过在会话中存储安全上下文,并根据需要存储/获取它(请参见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 中。
希望这能为您指明正确的方向。

答案似乎很有趣,但最终我发现一个与之相距甚远的解决方案。SecurityContextHolder丢失是因为在某些情况下(从网站外部以GET方式访问/login),配置的bean加载顺序错误。我真的不太明白。如果您或其他人能够解释为什么扩展AbstractSecurityWebApplicationInitializer确实解决了这个问题,我会给予奖励。 - amdev

4

最终我在这里找到了解决方案

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

我的配置缺少了这一点

最后一步是我们需要映射springSecurityFilterChain。我们可以通过扩展AbstractSecurityWebApplicationInitializer并可选地重写方法来自定义映射,轻松实现此目的。

以下是最基本的示例,它接受默认映射并添加springSecurityFilterChain,具有以下特征:

springSecurityFilterChain被映射到“/*”,springSecurityFilterChain使用错误和请求的调度类型,springSecurityFilterChain映射在已经配置的任何servlet过滤器映射之前插入

public class SecurityWebApplicationInitializer 
   extends AbstractSecurityWebApplicationInitializer {
}

只需添加该类即可解决问题,如果不行,则可以添加@Order注释。
“WebApplicationInitializer 的排序”
如果在调用 AbstractSecurityWebApplicationInitializer 之后添加了任何 servlet 过滤器映射,则它们可能会意外地添加到 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 {
    }

SecurityWebApplicationInitializer 对我有所帮助,谢谢! - Breina

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接