解决关于spring security手动设置认证用户无效的问题

这里主要是为了总结一下调试的方式方法。毕竟调试了2天,实在是差劲。还未找到问题根源,但比起https://stackoverflow.com/questions/4664893/how-to-manually-set-an-authenticated-user-in-spring-security-springmvc中的解决方式还是没有那么hack了。

问题:通过微信回调程序的认证接口进行登录,并将认证用户设置到spring security的上下文环境,但是并未设置生效,一直重复登录。

@Configuration
@Order(1)
public static class WeChatWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/user/**").exceptionHandling().authenticationEntryPoint(new CustomLoginUrlAuthenticationEntryPoint(""))
                .and().authorizeRequests().anyRequest().authenticated().and().formLogin()
                .and().logout().logoutUrl("/user/logout").logoutSuccessUrl("/");
        http.csrf().disable();

        // 检查是否已经使用医生身份登录,如果是,注销医生身份
        http.antMatcher("/user/**").addFilterAfter(new AutoLogoutDoctorFilter(), SecurityContextPersistenceFilter.class);
    }
}

@Configuration
@Order(2)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.headers().frameOptions().disable();     //解决SpringSecurity不能加载iframe
        http.authorizeRequests()
                .antMatchers(HttpMethod.POST, "/consultant/dispatch").hasAnyRole("Admin")
                .antMatchers("/consultant/dispatch/**").hasAnyRole("Admin", "Consultant", "AugmentedConsultant")
                .antMatchers("/consultant/**").hasAnyRole("Admin", "Consultant", "AugmentedConsultant")
                .and().exceptionHandling().authenticationEntryPoint(new CustomConsultantLoginUrlAuthenticationEntryPoint("")).and()
                .formLogin().loginPage("/consultant/login").defaultSuccessUrl("/consultant").permitAll()
                .and().logout().logoutUrl("/consultant/logout").logoutSuccessUrl("/consultant/login");
        http.csrf().disable();
        // 检查是否已经使用病人身份登录,如果是,注销病人身份
        http.antMatcher("/consultant/**").addFilterAfter(new AutoLogoutPatientFilter(), SecurityContextPersistenceFilter.class);
    }
}

配置说明:因为用户可以以相同的身份使用对方的功能,所以添加了过滤器,当用户尝试访问不属于自己的功能时将其强制退出。添加了这段配置后,翻车。。。

调试前的已知条件:单独添加

http.antMatcher("/user/**").addFilterAfter(new AutoLogoutDoctorFilter(), SecurityContextPersistenceFilter.class);
没有问题,添加
http.antMatcher("/consultant/**").addFilterAfter(new AutoLogoutPatientFilter(), SecurityContextPersistenceFilter.class);
后翻车。

第一次调试:查看spring security官方文档,了解如何添加自定义filter(我觉得ok);然后觉得是不是filter的位置添加错了,于是尝试将filter添加到默认filter栈的各种位置,什么after、before、at一一尝试。全部翻车!

第二次调试:又看了一下文档,发现添加filter的时候可以不指定匹配的路径。遂去掉antMatcher("/user/**")和antMatcher("/consultant/**")。然后突然蹦出一个是不是2个filter拥有相同的父类造成了异常的想法(脑子有问题),于是新建了一个类添加到filter栈。结果是登录成功!。。。个屁,页面上显示的是登录成功,但是一旦访问任何功能,立即要求登录。

第三次调试:有了第二次新建类的想法,又产生了用一个filter来处理用户的强制退出问题。于是改造以前的代码,通过在自定义filter里判断请求的路径来决定是否强制退出。ok!正常登录!。。。等等,这种方式比SO上大哥的hack方式也好不到哪去,面向对象的概念去哪了???我不是来找问题根源的吗,怎么搞出了一个解决方式。。。跑偏了。。。

第四次调试:于是,我又来到了查找问题根源的路上。不是只添加antMatcher("/user/**")就没问题吗,那我把FormLoginWebSecurityConfigurerAdapter的配置改成和WeChatWebSecurityConfigurationAdapter 应该就行了吧。翻车!

第五次调试:痛定思痛,没办法,看堆栈。将正常和非正常情况的堆栈打出来对比,发现了不同的之处

at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:317)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:127)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:115)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:169)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:121)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:214)

正常的情况多了以上的堆栈调用。进源码,打断点(还好能看源码,美滋滋)。

private void doFilterInternal(ServletRequest request, ServletResponse response,
      FilterChain chain) throws IOException, ServletException {

   FirewalledRequest fwRequest = firewall
         .getFirewalledRequest((HttpServletRequest) request);
   HttpServletResponse fwResponse = firewall
         .getFirewalledResponse((HttpServletResponse) response);

   List filters = getFilters(fwRequest);

   if (filters == null || filters.size() == 0) {
      if (logger.isDebugEnabled()) {
         logger.debug(UrlUtils.buildRequestUrl(fwRequest)
               + (filters == null ? " has no matching filters"
                     : " has an empty filter list"));
      }

      fwRequest.reset();

      chain.doFilter(fwRequest, fwResponse);

      return;
   }

   VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
   vfc.doFilter(fwRequest, fwResponse);
}
判断条件filters调用栈居然是空的,于是查看
private List getFilters(HttpServletRequest request) {
   for (SecurityFilterChain chain : filterChains) {
      if (chain.matches(request)) {
         return chain.getFilters();
      }
   }

   return null;
}
,通过查看chain的值发现其中包含了多个filter栈,每个栈都有自己的匹配规则,如果匹配成功,则返回。正常的情况chain里有一个默认filter栈,匹配所有路径的访问。而不正常情况下此默认filter消失了。那么解决这个问题就是将默认的filter添加进去。于是配置文件添加第三个配置
@Configuration
public static class DefaultWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.headers().frameOptions().disable();     //解决SpringSecurity不能加载iframe

        http.csrf().disable();
    }
}

,问题解决!现在走的是正经流程了!

总结:

不正确的调试方法:

1.毫无根据的调试。第一次调试中尝试把filter添加到不同位置,第二次使用没有父类的类(怎么可能没有父类,基础知识啊!!!)。

2.目标不清晰。第二次调试中明明是为了测试是不是antMatcher导致的错误,中途又跑去新写了个类。第三次调试中忘记该做什么,没有查找原因而是提供解决方案。第四次的调试及时解决问题,还是不知道为什么导致了错误,还是只能看到表面现象。

正确的调试方法:

1.制定目标。搞清楚到底要干什么。

2.搞清原理。了解代码是如何工作的。

3.记录结果。每次测试的结果记录下来,以免重复测试。

ps:理论上没有添加第五次调试中的默认配置时单独添加

http.antMatcher("/user/**").addFilterAfter(new AutoLogoutDoctorFilter(), SecurityContextPersistenceFilter.class);
和添加
http.antMatcher("/consultant/**").addFilterAfter(new AutoLogoutPatientFilter(), SecurityContextPersistenceFilter.class);
会没有问题。但是只添加AutoLogoutPatientFilter还是登录失效。原因还在继续排查在中。。。

你可能感兴趣的:(spring)