这里主要是为了总结一下调试的方式方法。毕竟调试了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调用栈居然是空的,于是查看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); }
private List,通过查看chain的值发现其中包含了多个filter栈,每个栈都有自己的匹配规则,如果匹配成功,则返回。正常的情况chain里有一个默认filter栈,匹配所有路径的访问。而不正常情况下此默认filter消失了。那么解决这个问题就是将默认的filter添加进去。于是配置文件添加第三个配置getFilters(HttpServletRequest request) { for (SecurityFilterChain chain : filterChains) { if (chain.matches(request)) { return chain.getFilters(); } } return null; }
@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还是登录失效。原因还在继续排查在中。。。