关于spring:记录一个-SpringSecurity-和-xauthtoken-一直登录失败的排查过程

34次阅读

共计 6998 个字符,预计需要花费 18 分钟才能阅读完成。

前言​近期要跑一个对于微信登录的 Demo(应用 Spring+Angular),为了省事,打算从已有的我的项目上复制粘贴。我进行了以下操作:​第一步:初始化我的项目,粘贴基本功能,如简略的页面、实体、必要的服务和控制器等,如果呈现依赖,则视状况粘贴依赖或删掉代码​第二步:粘贴登录性能,以后这个我的项目用的和之前学的曾经不一样了,应用的是 SpringSecurity、SpringSession 和 x -auth-token,所以第一反馈就是粘贴所有的过滤器和拦截器,登录性能可能会失常运行​第三步:实现 Demo 的微信登录的性能​而后因为某种问题,在第二步呈现了明码登录失败的状况,具体表现在:​①输出正确的用户名明码后,网页提醒登录失败​​

​​②控制台 network 提醒 401,没有响应的 body,console 提醒登录失败​​

​​​

​​​

​​③ 后端的控制台没有任何输入​​

​④ 如果在 login 办法上打断点,这个断点并不会被触发​​

​​到这里没有发现有用信息,仿佛无从下手​我找到原来失常的我的项目,在 login 办法上打断点发现:​⑤输出正确明码的状况下,后端 C 层 login 会被触发,否则不会​于是推出:明码的正确性应该是过滤器来校验,而不是用 login 办法校验,login 只负责校验胜利后更新登录信息​排查​那么问题可能出在这些过滤器上,于是在后端尽可能打断点来排查​既然鉴权性能个别是过滤器和拦截器来实现的,于是排查办法就是把所有解决申请的办法打上断点​打完断点重启我的项目,就能够看到真正的执行程序,分为两个阶段​一是后端启动时进行的一系列初始化,二是发动申请时的处理过程​先来看启动时是如何初始化的(省去了微信相干的步骤):​首先是加载主类​javapublic static void main(String[] args) {

SpringApplication.run(WebSoctetAndStompStudyApplication.class, args);

}
​过滤器执行构造函数(不止一个过滤器)​java public WechatAuthFilter(WxMaService wxMaService, UserRepository userRepository) {

this.wxMaService = wxMaService;
this.userRepository = userRepository;

}
​校验工具执行构造函数​java public SuperPasswordBCryptPasswordEncoder(OneTimePassword oneTimePassword) {

super();
this.oneTimePassword = oneTimePassword;

}
​header 解决​java /**

  • 应用 header 认证来替换默认的 cookie 认证
    */

@Bean
public HttpSessionStrategy httpSessionStrategy() {

return new HeaderAndParamHttpSessionStrategy();

}
​明码工具​java @Bean
PasswordEncoder passwordEncoder() {

return this.passwordEncoder;

}
​SpringSecurity 设置路由​java /**

  • 设置凋谢权限的路由
  • https://spring.io/guides/gs/s…
    *
  • @param http http 平安
  • @throws Exception 异样
    */

@Override
protected void configure(HttpSecurity http) throws Exception {

http
        .authorizeRequests()
        // 凋谢端口
        .antMatchers("/h2-console/**").permitAll()
        .antMatchers("/Data").permitAll()
        .antMatchers("/user/resetPassword").permitAll()
        .antMatchers("/user/getLoginQrCode/**").permitAll()
        .antMatchers("/wechat/**").permitAll()
        .antMatchers("/websocket/**").permitAll()
        .antMatchers("/user/sendVerificationCode", "/favicon.ico").permitAll()
        .anyRequest().authenticated()
        .and()
        // 增加通过 header 获取 host 信息的过滤器
        // 过滤器执行链请参考:https://docs.spring.io/spring-security/site/docs/5.5.1/reference/html5/#servlet-security-filters
        .addFilterBefore(this.headerRequestHostFilter, BasicAuthenticationFilter.class)
        // 增加微信认证过滤器
        .addFilterBefore(this.wechatAuthFilter, BasicAuthenticationFilter.class)
        .httpBasic()
        .and().cors()
        .and().csrf().disable();
http.headers().frameOptions().disable();

}

​url 解决​java /**

  • URL 疏忽大小写
    *
  • @param configurer 配置信息
    */

@Override
public void configurePathMatch(final PathMatchConfigurer configurer) {

final AntPathMatcher pathMatcher = new AntPathMatcher();
pathMatcher.setCaseSensitive(false);
configurer.setPathMatcher(pathMatcher);

}
​jsonview​java /**

  • 配置 JsonView
    */

@Override
public void configureMessageConverters(final List<HttpMessageConverter<?>> converters) {

final ObjectMapper mapper = Jackson2ObjectMapperBuilder.json().defaultViewInclusion(true).build();
converters.add(new MappingJackson2HttpMessageConverter(mapper));

}
​以上大略是我的项目启动时执行的流程,不必细看。​而后是前端发动申请的时的流程​我找到那个运行失常的生产我的项目,失常状况下应该是:​①获取 session 数据​java @Override
public String getRequestedSessionId(HttpServletRequest request) {

String token = request.getHeader(this.headerName);
return (token != null && !token.isEmpty()) ? token : request.getParameter(this.headerName);

}
​② header 过滤器​java public HostHeaderHttpServletRequest(HttpServletRequest request) {

  super(request);
  this.request = request;
}

​③微信过滤器​java@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

final boolean debug = this.logger.isDebugEnabled();

String code = request.getHeader(this.codeKey);
if (code != null) {
  try {WxMaJscode2SessionResult wxMaJscode2SessionResult = wxMaService.getUserService().getSessionInfo(code);
    String openid = wxMaJscode2SessionResult.getOpenid();
    Optional<User> optionalUser = userRepository.findByOpenid(openid);
    WeChatUser wechatUser = new WeChatUser(new User(), wxMaJscode2SessionResult.getSessionKey(), wxMaJscode2SessionResult.getOpenid());
    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken;
    if (optionalUser.isPresent()) {wechatUser.setUser(optionalUser.get());
    }
    // 设置认证用户:微信用户、平安令牌设置为 openid、认证权限为空 (前期可变更为正确的微信权限名称)
    usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
        wechatUser,
        openid,
        wechatUser.getAuthorities());

    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
  } catch (WxErrorException exception) {this.logger.warn("尽管接管到了 code,然而没有通过 code 换取无效的微信数据:" + exception.getMessage());
    exception.printStackTrace();}
}

filterChain.doFilter(request, response);

}
​④ 比对用户名明码​java @Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {

if (rawPassword == null) {throw new IllegalArgumentException("rawPassword cannot be null");
}

if (oneTimePassword.matches(rawPassword, encodedPassword)) {return true;}

return super.matches(rawPassword, encodedPassword);

}

@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {

// 减少微信扫码后应用 webSocket uuid 充当用户名与明码进行认证
if (this.userService.checkWeChatLoginUuidIsValid(rawPassword.toString())) {if (this.logger.isDebugEnabled()) {this.logger.info("校验微信扫码登录胜利");
  }
  return true;
}

// 当有一次性明码(每个明码仅能用一次)且未应用时,验证用户是否输出了超密
Optional oneTimePassword = this.getPassword();
return oneTimePassword.isPresent() && oneTimePassword.get().equals(rawPassword.toString());

}
​⑤ 明码比对正确后,拦截器放行,拜访到 controller​java @RequestMapping(“login”)
@JsonView(LoginJsonView.class)
public User login(Principal user) {

return this.userService.getByUsername(user.getName());

}
​⑥调用 Service,return,登录胜利​但在有问题的 Demo 中,我遇到的状况是,在第三步(微信过滤器)执行之后就间接返回了,没有明码比对的过程,C 层 login 的断点始终不会被触发,后端的控制台也没有任何报错,只有网页上显示登录失败​而后我又查看了一遍所有过滤器和配置文件,均没有发现问题​用户​正当不晓得怎么办的时候,我发现脱漏了一个中央:过滤器和拦截器怎么能取出用户名和明码呢?必然不能间接取出吧?必定是调用了 service 来取出的,但 UserService 是能通过编译的,阐明没有问题,过后感觉很费解,而后把 UserService 所有办法打上断点​果然有新的发现,在③微信过滤器解决和④比对明码之间,UserService 的 loadUserByUsername 办法断点被击中了,这一步就是方才猜想的“取出用户”的过程​​

​​但认真看,这个办法并没有被间接调用,所以显示 no usage​我回到那个有问题的我的项目一看,这个办法是这样的:​java @Override

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {return null;}

​粘贴的时候一看没有 usage 就感觉不会被调用,于是轻易写,间接返回空了​把正确的办法粘过来,却发现这个 UserServiceImpl 只是实现了 UserService,而是 UserDetailsService,这个类实现了两个接口​java// 定义类的语句
public class UserServiceImpl implements UserService, UserDetailsService {

....

}
​而这个 UserDetailService 也不出预料的是一个外部类:​​

​​所以才会呈现 usage 是 0,但它实实在在的被调用了的状况,咱们能够持续剖析,这个办法是取出用户的,如果 return 是 null,那么无论输出什么用户名明码,都会明码谬误,所以咱们看到的“没有任何报错”的状况,实际上被零碎认为“用户名明码输出谬误”的状况,零碎认为这是一个失常的状况​至于为什么明码不正确返回的 401 没有 response 信息?察看代码会发现是拦截器对 401 进行了解决,阻止 401 导致的报错​java/**

  • 在非 cors 的状况下,阻止浏览器在接管到 401 时弹窗
    */

export class Prevent401Popup implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

let headers = req.headers;
headers = headers.append('X-Requested-With', 'XMLHttpRequest');
req = req.clone({headers});
return next.handle(req);

}
}
​这样一来总算都走的通了,登录也失常了​​

​​因为第一次呈现这种没有错误信息的状况,因而在此文中把排查步骤记录了下来。​总结和反思​反思:​①为什么没有问?因为疑难杂症的 debug 太麻烦,而且他人不晓得本人埋的坑在哪,所以抉择了本人排查​②为什么没有查?因为问题无从下手,不晓得如何组织语言去 goooogle​总结:​①不同于手写代码,框架有它本人的调用形式,并且属于“我写了它就调,没写也不报错”的状况,换言之,如果我的代码实现了框架的接口、或者继承了框架的外部类、或者加了对应的注解,它就能失常执行​②被外部类调用的办法不会有显示调用的提醒(即 usage 为 0),但不能漠视​③如果想排查问题,能够应用大量打断点的形式,从外表上弄明确执行程序​④如果想深刻了解执行逻辑,最好的方法还是零碎的学习框架​⑤ debug 的时候必须同时察看 前端的页面、浏览器的 network、浏览器的 console、后端的 console、后端的断点,能力更好的发现问题​demo 地址:​​​

正文完
 0