赞
踩
上节中提到Spring Security核心就是一系列的过滤器链,当一个请求来的时候,首先要通过过滤器链的校验,校验通过之后才会访问用户各种信息。
这里要说明的是在过滤器的最前端有一个SecurityContextPersistenceFilter,当请求进来和返回的时候都会经过这个过滤器,它主要存放用户的认证信息。这里先简单提一下,后面会详解。
当用户发送登录请求的时候,首先进入到UsernamePasswordAuthenticationFilter中进行校验。
打断点发送登录请求进入源码中,我们会发现它会进入到UsernamePasswordAuthenticationFilter,在该类中,有一个attemptAuthentication方法在这个方法中,会获取用户的username以及password参数的信息,然后使用构造器new UsernamePasswordAuthenticationToken(username, password)封装为一个UsernamePasswordAuthenticationToken对象,在这个构造器内部会将对应的信息赋值给各自的本地变量,并且会调用父类AbstractAuthenticationToken构造器(这个父类的构造器后面会介绍到),传一个null值进去,为什么是null呢?因为刚开始并没有认证,因此用户没有任何权限,并且设置没有认证的信息(setAuthenticated(false)),最后会进入AuthenticationManager接口的实现类ProviderManager中。
request里边存放的是请求的信息
在ProviderManager这个实现类中,它会调用AuthenticationProvider接口的实现类获取用户的信息,用户的信息权限的验证就在该类中校验。进入ProviderManager类中调用authenticate(Authentication authentication)方法,它通过AuthenticationProvider实现类获取用户的登录的方式后会有一个for循环遍历它是否支持这种登录方式,具体的登录方式有表单登录,qq登录,微信登录等。如果都不支持它会结束for循环,如果支持则会进入AuthenticationProvider接口的抽象实现类AbstractUserDetailsAuthenticationProvider中调用 authenticate(Authentication authentication)方法对用户的身份进入校验。
进入抽象类AbstractUserDetailsAuthenticationProvider的内部的authenticate方法之后,先会判断user是否为空,这个user是UserDetail的对象,如果为空,表示还没有认证,就需要调用retrieveUser方法去获取用户的信息,这个方法是抽象类AbstractUserDetailsAuthenticationProvider的扩展类DaoAuthenticationProvider的一个方法。
UserDetailsService从数据库获取用户数据,封装成UserDetails返回
调用AuthenticationProvider的authenicate方法,实际上是调用其实现类AbstractUserDetailsAuthenticationProvider中的authenicate方法,authenticate再调用retrieveUser方法去获取用户信息验证
DaoAuthenticationProvider继承了AbstractUserDetailsAuthenticationProvider类,并且实现了retrieveUser方法
调用UserDetailsService这个接口的实现类的loadUserByUsername方法去获取用户信息,而这里我自己编写了实现类MyUserDetail类,在这个实现类中,我们可以编写自己的逻辑,从数据库中获取用户密码等权限信息返回。
如果拿到了用户信息user,如果没有拿到则会抛出异常
1.perAuthenticationChecks—预检查,检查三个布尔异常,密码是否为空等等
2.additionalAuthenticationChecks附加检查,用passwordEncoder校验当前密码是否匹配
3.如果都通过了,还有一个postAuthenticationChecks—后检查第四个布尔
三个检查都通过,则用户认证是成功的
当三个检查都通过时,最后构建一个createSuccessAuthentication
在拿到用户的信息后,返回到AbstractUserDetailsAuthenticationProvider类中调用**createSuccessAuthentication(principalToReturn, authentication, user)**方法,在该方法中会调用三个参数的UsernamePasswordAuthenticationToken构造器,不同于前面调用两个参数的,因为这里已经验证了用户的信息和权限,因此不再是给父类构造器中传null值了,而是用户的权限集合,并且设置认证通过(setAuthenticated(true)),
有三个参数,因为现在已经做了登录,认证已经成功了,已经拿到UserDetails,从UserDetails里可以获得用户的权限,所以super(authorities)
在UsernamePasswordAuthenticationToken的父类中,它会检查用的权限,如果有一个为null,表示权限没有相应的权限,抛出异常。
然后在createSuccessAuthentication方法返回后回到ProvioderManager的authenticate方法中返回result,最后回到UsernamePasswordAuthenticationFilter的刚开始进入的attemptAuthentication方法中返回。
认证成功后就回去调用successfulAuthentication,也就是调用successHandler登录成功处理器
如果认证过程中有异常,则会调用failureHandler认证失败处理器
总结:
通过上面的源码我们已经深入源码了解到用户的具体认证流程。这里简单总结一下,首先当用户发送请求的时候,会进入到UsernamePasswordAuthenticationFilter中得到一个UsernamePasswordAuthenticationToken,它其实相当于一个令牌,不过还没有经过认证,然后调用AuthenticationManager的实现类ProviderManager中判断登录方式是否支持,如果支持,则会调用AuthenticationProvider接口的抽象实现类AbstractUserDetailsAuthenticationProvider中调用它的扩展类DaoAuthenticationProvider中获取我们自己实现的MyUserDetails类获取用户密码进行用户身份验证,然后返回该对象,设置UsernamePasswordAuthenticationToken这个令牌认证通过,用户身份校验成功。
下面我们来看看用户在通过身份校验之后,是如何将认证结果在多个请求中共享的呢?肯定是放入session当中的。先来看看流程图。
身份认证成功后,最后在UsernamePasswordAuthenticationFilter返回后会进入一个AbstractAuthenticationProcessingFilter类中调用successfulAuthentication方法中,这个方法最后会返回我们自己定义的登录成功处理器handler,在返回之前,它会调用SecurityContext,最后将认证的结果放入SecurityContextHolder中,SecurityContext类很简单,重写了equals方法和hascode方法,保证了authentication的唯一性。SecurityContextHolder类实际上对ThreadLocal的一个封装,可以在不同方法之间进行通信,我们可以简单理解为线程级别的一个全局变量。因此可以在同一个线程中的不同方法中获取到认证信息。最后会被SecurityContextPersistenceFilter过滤器使用,这个过滤器的作用是什么呢?当一个请求来的时候,它会将session中的值传入到该线程中,当请求返回的时候,它会判断该请求线程是否有SecurityContext,如果有它会将其放入到session中,因此保证了请求结果可以在不同的请求之间共享。
如果我们需要获取用的校验过的所有信息,该如何获取呢?上面我们知道了会将校验结果放入session中,因此,我们可以通过session获取。
@GetMapping("/me")
public Object
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。