通过前面的文章介绍,我们对于SpringSecurity的运行原理已经有了一个基本的掌握。所以现在就来在框架默认功能的基础之上自定义我们的功能。
一、思考
如果想自定义一个认证流程,我们需要哪些模块?稍微想一下就可以得出答案:
1、登录信息验证
2、会话管理
3、授权
4、注销
SpringSecurity独特的SecurityBuilder和SecurityConfigurer结构,使得模块与模块之间既能够独立工作,又能够组合发挥。SecurityConfigurer的init方法,可以将不同模块的变量进行共享,从而影响到configurer阶段对于SecurityBuilder的属性设置,以至于最终影响到框架运行结果。
我们来一步步的定义我们自己的框架流程:
二、如何自定义SpringSecurity的配置
前面的文章中我们提到过,SpringSecurity框架的springSecurityFilterChain,其主要工作是交给SecurityFilterChain来完成的。创建springSecurityFilterChain的SecurityBuilder为WebSecurity,创建SecurityFilterChain的SecurityBuilder为HttpSecurity。而与WebSecurity的SecurityConfigurer在框架中只提供了一个默认的实现:
但是从代码中我们可以发现,Spring上下文中可以同时存在多个WebSecurityConfigurerAdapter,但只有@Order最大的那个会生效,并且Order是唯一的。如果不指定Order则默认为Integer.MAX_VALUE,也就说我们只需要定义一个不带@Order注解的WebSecurityAdapter注册到spring容器中就能替换掉SpringSecurity提供的DefaultConfigurerAdapter,来修改WebSecurity的行为,通过WebSecurity来创建我们自定义的springSecurityFilterChain
但是真正起作用的其实是SecurityFilterChain对象,所以自定义WebSecurityConfigurerAdapter的主要目的其实是为了自定义SecurityFilterChain的构造器——HttpSecurity。
WebSecurityConfigurerAdapter中涉及到HttpSecurity的主要是getHttp方法和configure方法。从代码中来看,如果我们想只应用SpringSecurity提供的骨架,而自定义请求认证的流程,第一步是要把HttpSecurity对象所有的SecurityConfigurer都取消掉,然后选择我们需要的进行设置。
所以我们可以进行两处修改:
1、disableDefaults属性设置为true
2、重写configurer方法
现在我们的配置类是这个样子了。我们只保留了请求拦截,登陆,请求缓存,会话管理,注销,对于所有请求需要登录认证这几个功能。至于配置的顺序,我们并不需要去关心,因为框架中已经事先定义好了所有的filter的顺序,在build的时候其会进行排序:
三、自定义AuthenticationProvider
前面的源码分析文章中,我们已经知道登陆是交给UsernamePasswordAuthenticationFilter来做的,而具体的细节又是交给AuthenticationManager做的。SpringSecurity默认给我们提供的AuthenticationManager是ProviderManager,而且其工作原理之前也已经介绍了,本篇是实战篇,所以不涉及过多的原理。HttpSecurity中的AuthenticationManager是从HttpSecurity中获取的
所以要想自定义AuthenticationManager还得从HttpSecurity对象入手,即前面提到的WebSecurityConfigurerAdapter的getHttp方法。
前面的文章中已经分析过这段代码,这里简单的讲下。由于disableLocalConfigureAuthenticationBldr属性默认为true,即使用框架默认的AuthenticationManager:
系统默认的AuthenticationManager是由全局的AuthenticationManagerBuilder——authBuilder结合globalAuthConfigurers配置类创建的。所以我们要想自定义全局的AuthenticationManager,首先要清楚globalAuthConfigurers为全局的AuthenticationManagerBuilder做了哪些支持:
globalAuthConfigurers主要的功能就是提供了加载Provider到AuthenticationManager中:
1、第一个AuthenticationProvider默认加载的是spring上下文中的第一个bean,如果不存在则为null
2、如果第一个Provider不存在,则会使用spring上下文中userDetailsService创建了一个DaoAuthenticationProvider
对于AuthenticationManager的验证流程我们也已经分析过了,他会依次调用其所有的provider,只要有一个验证成功就说明登录成功。而且从Spring中加载的AuthenticationProvider作为第一个,很明显就是留给我们的钩子,所以我们只需要在Spring上下文中创建一个我们自己的Provider,就能自动被SpringSecurity框架所加载,这样SpringSecurity就不会创建默认的Provider了
通过debug可以看到我们自定义的provider确实应用到框架里了。
同样的道理,如果我们不自定义AuthenticationProvider,转而自定义UserDetailsService,也一样可以实现我们的目的。不过为了方便我们还是自定义Provider。
四、自定义登陆成功和失败处理器
既然有登录,那就肯定有登录成功和登录失败的情况,我们可以统称为登录事件。SpringSecurity作为一个成熟的框架,肯定不会连这么简单的功能都遗漏掉。登陆成功会调用SuccessHandler的onAuthenticationSuccess方法,登陆失败会调用FailedHandler的onAuthenticationFailure方法,系统默认提供的handler是SimpleUrlAuthenticationFailureHandler和MyLoginSuccessHandler。但是我们可能要做自己的定制化操作,比如记录用户登录时间,登录失败原因等,所以要自定义handler:
继承SimpleUrlAuthenticationFailureHandler和SimpleUrlAuthenticationSuccessHandler即可,并且标记为bean,然后在自定义的配置文件中引入,设置到登录模块中去。
同样的,注销之后我们可能也要处理注销的事件,所以可以继承LogoutSuccessHandler构造我们自己的处理器
定义好之后直接在自定义的MyWebSecurityConfigurerAdapter配置类中将其注入到登录和注销的configure里面即可。
最后的authorizeRequests().anyRequest().authenticated()表示对于所有的请求,都需要"authenticated"授权,即要求所有的请求访问都需要进行登录认证,但登录页面除外,默认的登录页面具有"permit all"权限,即对所有请求开放。具体的权限配置,还是放到下一篇来讲,因为和认证相同,授权也是一个很复杂的过程。
以上我们就配置好了一个SpringBoot + SpringSecurity项目的基本骨架,应该说是十分的简单,现在来回顾一下我们配置了哪些东西,然后还需要哪些东西:
1、自定义了AuthenticationProvider,并且标记为bean
2、自定义了三个handler,loginSuccessHandler,loginFailedHandler,logoutSuccessHandler
3、继承WebSecurityConfigurerAdapter,自定义了SpringSecurity的配置类。
是不是很简单。只需要这么简单的几个类,我们就算是初步接入了SpringSecurity框架,并且具备了认证的能力。不过这也仅仅是有了认证的能力,具体的认证细节,事件处理细节还需要我们自己去按照自己的业务去自己写。
这里没有去写AuthenticationProvider的authenticate方法的具体细节的原因主要是:
认证和授权虽然是两个部分但是他们的功能会有部分重合,所以在定义认证结果之前,还得先看授权的细节。所以下面一篇文章就来一起看下授权的细节,以及我们如何去根据认证的结果来控制权限。