而AbstractUserDetailsAuthenticationProvider实现了AuthenticationProvider接口。
5.在ProviderManager中,执行到result = provider.authenticate(authentication)时,其中provider是由AuthenticationProvider定义的,但AuthenticationProvider是一个接口,需由其子类具体实现。根据上面分析,可知,AbstractUserDetailsAuthenticationProvider会具体实现provider.authenticate(authentication)方法。debug进入到其authenticate方法当中,会跳转到AbstractUserDetailsAuthenticationProvider重写的authenticate()方法当中,接下来会详细介绍该authenticate()执行的代码模块:
5.1.首先,第一步,会执行this.userCache.getUserFromCache(username)获取缓存里的信息。
5.2 若缓存里没有UserDetails信息,将会继续往下执行,执行到retrieveUser方法,该方法的总体作用是:通过登录时传入的userName去数据库里做查询,若查询成功,便将数据库的User信息包装成UserDetails对象返回,当然,具体如何从数据库里获取到信息,则需要重写一个方法,即前面提到的loadUserByUsername()方法。
值得注意一点是,一般新手接触到security框架,都会有一个疑问,即我登录时传入了username,是如何获取到数据库里的用户信息?
其实,这个疑问的关键答案,就藏在这个retrieveUser()方法里。该方法名的英文解析是:“(训练成能寻回猎物的)猎犬”。我觉得这个翻译在这里很有意思,暂且可以把它当成信息提供者Provider驯养的一头猎犬,它可以帮我们的游戏主角线程在茫茫的森林里,寻找到藏匿宝箱的山洞(数据库)。
5.3 ,接下来,就让这头猎犬给我们带路吧——点击retrieveUser(),进入到方法当中,发现,这其实是一个抽象方法,故而其具体实现将在子类中进行。
5.4 进入到其子类实现的方法当中,发现会进入前面提到AbstractUserDetailsAuthenticationProvider的子类DaoAuthenticationProvider,它也是一个AuthenticationProvider,即所谓的信息提供者之一。在DaoAuthenticationProvider类里,实现了父类的retrieveUser方法。
在猎犬的(retrieveUser)的带路下,我们最后看到 了熟悉的老朋友,关键方法loadUserByUserName()。
点进loadUserByUsername()方法里,会进入到UserDetailsService接口里,该接口只有loadUserByUsername一个方法,该方法具体在子类里实现。
这个接口被我们自定义重写了,即前面露过面的:
在DaoAuthenticationProvider类中,调用loadUserByUserName()方法时,最终会执行我们重写的loadUserByUsername()方法,该方法将会去数据库里查询username的信息,并返回一个User对象,最后SysUser对象转换成UserDetails,返回给DaoAuthenticationProvider对象里的UserDetails,跳转如下图: