共计 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);
}
jsonviewjava /**
- 配置 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());
}
⑤ 明码比对正确后,拦截器放行,拜访到 controllerjava @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 地址: