引入SpringSecurity依赖之后,
<!--springsecurity-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
启动项目取访问任意一个URL的时候就会被SpringSecurity自带的过滤器拦截并自动跳转到login登陆界面。只有正确的username和password才会通过校验。SpringSecurity自带的认证方式是基于内存的随即用户,我们可以把它替换成我们自己的认证方式,比如说输入用户名与密码,然后从数据库中认证。
有了这个想法们就要去看看它的过滤器到底是怎么认证的。
要清楚,肯定是过滤器在发挥作用,所以,先从过滤器中查找,看看到底是哪一个过滤器在发挥作用。对项目进行Debug,查看原始过滤器链中的过滤器,发现UsernamePasswordAuthenticFilter最有可能是进行用户名与密码校验的,所以深入这个过滤器去看看。
UsernamePasswordAuthenticFilter过滤器长这样
我们都知道,过滤器是需要执行doFilter()方法的,但是我翻遍了这个类,都没有发现此方法,所以,他一定是执行了父类的这个方法,所以,去它的父类看看,也就是AbstractAuthenticationProcessingFilter。进入这个类并查看它的方法。
可以看到有两个doFilter方法,其中肯定有一个是自己写的,并一个是重写通用过滤器Filter的doFilter方法的。深入进去看看。
可以看到,在必定被调用的doFilter方法中(也就是被重写的那个)调用了private的doFilter方法,而这个重载的doFilter就应该是这个AbstractAuthenticationProcessingFilter要执行的逻辑。看看这个方法有什么。
逻辑大概4部分,先判断需不需要认证,如果不需要,那就放行;如果需要认证,那就执行以下代码
Authentication authenticationResult = attemptAuthentication(request, response);
然后拿到认证结果之后,就去做认证成功或者认证失败的逻辑。也就是说,具体的认证工作是在attemptAuthentication中完成的。而UsernamePasswordAuthenticFilter中重写了这个方法,那这个方法就是具体的认证方法。
大致逻辑有三步,第一,看看请求方式对不对;第二,拿出属性值,做赋值等操作,并没有做具体的方法逻辑;第三,调用代码
return this.getAuthenticationManager().authenticate(authRequest);
这个方法只是返回一个对象,而且这个对象并没有显示创建,所以,可能是由Spring自动创建的,而后注入进去。拿到这个AuthenticationManager类型的对象之后,就去调用了它的authenticate方法了。具体看看
点进去之后,发现是一个接口方法,是需要实现的,但是这个接口的实现类很多。
我也不知道具体是哪一个,那就打断点,看看他具体去了哪里。在
return this.getAuthenticationManager().authenticate(authRequest);
打断点,然后运行项目,访问一个需要认证的URL,进入它自动跳转的login,然后输入username和password,点击登录。
点这个两下,进入具体的认证方法里。
看,进入了ProviderManager中的authentication中。接下来看看方法里的具体逻辑。
逻辑大致三部分,首先判断这个provider支不支持我传进来的对象,支持的话会执行以下代码(中间logger可以跳过,因为是关于日志的)
result = provider.authenticate(authentication);
继续点进去,看看具体方法。
里边有一个接口,那继续看他的实现类,看看到底是哪一个在发挥作用。
这个接口的实现类太多了,只能在这里打断点,再次debug。
这次代码运行到了这里,叫AbstractUserDetailsAuthenticationProvider。
而抽象类很接口类似,也需要实现类,这个抽象类的实现类只有一个。
也可以在Debug的控制台上看,这个比较直观。
因为DaoAuthenticationProvider里没有重写authenticate方法,所以执行的是父类中的这个方法。这个代码的以下片段
调用了retrieveUser方法,因为子类中重写了,所以DaoAuthenticationProvider实例执行自己中的这个方法,这个方法返回一个user对象,而且有异常的话,直接抛出了UsernameNotFoundException。所以,我就怀疑这个retrieveUser方法可能会执行查询用户的逻辑。所以深入这个方法查看。
在DaoAuthenticationProvider重写的这个方法里,有一句代码很清楚,叫
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
就是根据用户名去查找用户的,也就是认证里最核心的代码了。
那么再次看看这个loadUserByUsername是谁去执行的、去执行了什么。点进去一看,还是一个接口。
需要对UserDetailsService这个接口做出实现。看看它的实现类吧。
很明显,是InMemoryUserDetailsManager。看看它的loadUserByUsername在干什么。
通过用户名从this.users里获取用户,
而users是一个map。那用户从哪里来?
可以看到,它的用户都是基于内存自己创建的。这样,认证逻辑也就说明白了
而我们要从数据库里获取用户,那就需要吧InMemoryUserDetailsManager换成我们自己的,也就是需要自己实现一个的UserDetailsService接口,实现类就是执行从数据库中取数据的逻辑。
后续请看主页~