源码级讲解Security用户名密码认证

更多精彩文章请关注本人公众号

更多精彩文章请关注本人公众号

更多精彩文章请关注本人公众号

一、前言

本文根据我的项目进行Security密码认证的源码级别讲解,我们将通过localhost:9090访问来开始进行Debug说明,我已经在源码中打了很多个端点,基本能讲到Security用户名密码认证的全部流程,主要是给自己加深印象,其次分享给大家,如果讲解过程中有什么错误,也请大家不吝指正,谢谢!代码基于springboot2.2.1、security5、jdk8、mysql8.0、maven构建。

二、debug启动springboot应用

1、由于我们在MvcConfig配置文件中进行如下配置,所以访问localhost:9090会跳转home.html

/** * @author yunqing */ @Configuration public class MvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(“/home”).setViewName(“home”); //注意:这里配置了 / 跳转home.html页面 registry.addViewController(“/”).setViewName(“home”); registry.addViewController(“/hello”).setViewName(“hello”); registry.addViewController(“/login”).setViewName(“login”); } }

1.1、application.yml中配置了端口为9090

server: port: 9090

1.2、我在WebSecurityConfig中配置了不需要认证就可以访问的页面,其中包含 / 和/home

@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() // 所有用户均可访问的资源 .antMatchers(“/css/**”, “/js/**”,“/images/**”, “/webjars/**”, “**/favicon.ico”, “/index”).permitAll() .antMatchers(HttpMethod.POST, “/user/registration”).permitAll() .antMatchers(“/”, “/home”,“/user/registration”,“/hello”).permitAll() //剩下的任何请求都需要进行认证 .anyRequest().authenticated() .and() //表单登录 .formLogin() //登录请求页面 .loginPage(“/login”) //自定义登录成功和失败处理器 .successHandler(ajaxAuthSuccessHandler) .failureHandler(ajaxAuthFailHandler) .permitAll() .and() .logout() .permitAll(); }

2、看完上面的基本介绍,接下来我们进入了第一个断点

可以看到进了断点停在了抽象类AbstractAuthenticationToken,并且权限信息传入ROLE_ANONYMOUS参数,匿名角色,按下一步发现进入了如下AnonymousAuthenticationToken类,可以发现是anonymousUser匿名用户封装了一个匿名认证的Token,通过this.setAuthenticated(true0)设置认证通过。

所以得出结论:WebSecurityConfig中我们设置不需认证的资源或者路径,实际上Security使用匿名用户进行认证访问。

2.1、这里扩展一下spring security过滤器链

如上图所示:SecurityContextPersistenceFilter过滤器位于过滤器链的最前端,请求先进这个过滤器,当请求进入,检查session中是否有securityContext,如果有则拿出来放到线程中,请求响应回来最后一个也经过此过滤器,检查线程中是否有SecurityContext,如果有则拿出来存到Session中。这样就可以完成认证结果在多个请求之间共享。

2.2、通过获取共享在多个请求之间的用户信息

/** * @author yunqing * @Date 2019/12/15 16:44 */ @Slf4j @RestController @RequestMapping(“/api/account”) public class SecurityController { @GetMapping(“/me”) public Object getAuthentication() { return SecurityContextHolder.getContext().getAuthentication(); } }

记得在SecurityConfig中设置/api/account/me不需要认证,即匿名登录。

2.3、获取当前认证用户信息

直接跳过断点localhost:9090访问到 home.html页

访问localhost:9090/api/account/me获取当前认证用户信息,可以看到当前确实是匿名用户

#返回结果证明当前认证成功的是匿名用户 {“authorities”:[{“authority”:“ROLE_ANONYMOUS”}],“details”: {“remoteAddress”:“0:0:0:0:0:0:0:1”,“sessionId”:“45C0AA46462B773A0606D24C91D70722”}, “authenticated”:true,“principal”:“anonymousUser”,“keyHash”:431445726,“credentials”:“”, “name”:“anonymousUser”}

3、正式开始讲解数据库中的用户认证

3.1、点击Sign In登录跳转到第一个断点UsernamePasswordAuthenticationFilter

断点停到UsernamePasswordAuthenticationFilter,判断当前请求是否是POST请求→获取表单提交的用户密码→根据用户名密码构造一个UsernamePasswordAuthenticationToken→进入我们发现两个构造函数如下图

可以发现走的第一个两个参数的构造函数,因为并没有传入authorities角色信息,通过this.setAuthenticated(false)设置未经过认证→返回未认证的token到UsernamePasswordAuthenticationFilter

通过this.setDetails(request,authRequest);设置ip、sessionId等信息到UsernamePasswordAuthenticationToken中,如下图所示:

返回UsernamePasswordAuthenticationToken到this.getAuthenticationManager().authenticate()进行处理,如下图所示:

之后进入ProviderManager中的断点,介绍一下,ProviderManager实现了AuthenticationManager接口,可以看到如下图,var8是一个Providers集合,循环遍历这个集合,找到适合处理UsernamePassword的Provider进行处理用户名密码认证,可以看到当前是处理匿名用户的AnonymousAuthenticationProvider,继续下一步

循环直到当前Provider是DaoAuthenticationProvider这个是专门处理用户名密码认证的Provider,然后执行到result = provider.authenticate(authentication);这个断点的时候,会进入到AbstractUserDetailsAuthenticationProvider 抽象类,它实现了 AuthenticationProvider接口,而DaoAuthenticationProvider又继承了这个抽象类。

接下来重点讲解DaoAuthenticationProvider和AbstractUserDetailsAuthenticationProvider 这两个类,主要的认证方法写在了这个抽象类中,如下图断点中this.retrieveUser()方法获取到了一个UserDetails实例,这个this.retrieveUser()方法也是一个抽象方法,他的实现写在了DaoAuthenticationProvider中。

接下来看this.retrieveUser()方法的实现,终于在实现中看到了调用loadUserByUsername()方法。

可以看到确实进入了我们自定义的MyUserDetailsService类,这个类就不多解释了,从数据库里取数据验证而已

回到AbstractUserDetailsAuthenticationProvider可以看到三个检查,三个检查的实现就在本类中,可以进行查看

preAuthenticationCheck检查

this.additionalAuthenticationChecks密码检查,实现类在DaoAuthenticationProvider中

this.postAuthenticationChecks()还是对UserDetails接口中剩下的一个布尔值进行检查

所有检查通过之后,认为认证成功,拿着认证成功的这些信息进入this.createSuccessAuthentication()

可以看到走的确实是三个参数的构造函数,如下图,通过super.setAuthenticated(true)设置认证状态为成功

然后DaoAuthenticationProvider返回一个认证成功的Authentication,经过认证的Authentication会沿着认证的流程返回去,一直返回到UsernamepasswordAuthenticationFilter.

接下来就调用认证成功处理器进行处理,认证信息设置到线程中,用与session共享认证结果

登陆成功

© 版权声明
THE END
喜欢就支持一下吧
点赞12 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片