在上一节中,我们发现SpringBoot会自动配置Security,让我们跳转到表单登录页面,输入默认的用户名和随机生成的密码。
其实在springboot1.x版本中,并不是跳转到表单登录页,而是弹出一个对话框,在对话框里输入信息,我们可以修改代码尝试一下。
两种认证方式
首先我们在browser模块创建一个BrowserSecurityConfig类
1 |
|
这个类继承了WebSecurityConfigurerAdapter,安全配置适配器类,并重写configure方法,这里configure有三种重载方法,我们这里先使用Http形式的。
通过代码很好理解,即通过一系列配置,使用了httpBasic的方式进行身份认证的拦截。
重启Application,查看认证方式(注意需要把之前关闭自动配置的代码给注释掉)

登录方式成为了弹出框的形式
图解基本原理
对于SpringSecurity的基本原理,这里先放一张图,根据这张图我们进行简单了解

这里首先分为Security的过滤器链,由多个过滤器组成,和REST API即我们编写的服务需要被保护的服务。
再进行API请求时,会先走一遍过滤器链,成功认证后才会调用,返回时会再走一遍
绿色过滤器
这里有两个UsernamePasswordAuthenticationFilter和BasicAuthenticationFilter,通过名字我们可以联想到刚刚实现的两种认证方式,第一个就是表单认证过滤器,第二个是弹出登录框的基础Http认证过滤器。
这里还能看到有第三个绿色框,其实还有很多种认证方式,比如短信验证,第三方登录等等。
请求通过绿色过滤器之后如果认证成功会加一个标记,然后进入橙色过滤器FilterSecurityInterceptor里,如果没有认证成功也会进去,只不过没有认证成功的标记。
橙色过滤器
FilterSecurityInterceptor是非常重要的过滤器,会根据我们的配置进行拦截判断,比如我们刚刚的代码:
1 |
|
这里的配置都会被它读取,从绿色过滤器过来之后会判断是否有成功标记,如果没有抛出异常,如果有再判断是否有其他的一些认证配置,比如vip用户认证,判断用户是否为vip用户,不是还是抛出异常,如果全部认证通过会放行请求API接口
蓝色过滤器
ExceptionTranslationFilter,看名字也知道是异常捕获的过滤器,即当FilterSecurityInterceptor抛出异常后,会被此过滤器拦截。
比如Interceptor抛出没有身份认证的异常,Exception Translation Filter会看看前面究竟配置的是什么样的Filter,然后做出相应的处理。比如说前面配置的是Username Password Authentication Filter,那么就会跳转到带表单的登录页面。但如果说前面配置的是BasicAuthenticationFilter,那么就会弹出登录框等待用户输入信息。
再比如用户没有输入用户名密码,Interceptor抛出认证失败异常,此过滤器就会抛出401异常

源码查看
首先是FilterSecurityInterceptor:
1 | public void invoke(FilterInvocation fi) throws IOException, ServletException { |
这里简单的看一下,主要是else里的代码,获取请求信息,然后调用super.beforeInvocation()来判断是否有权限调用API,如果有权限放行,如果没有权限抛出异常,这里代码就不放出来了,可以自己去看,我们主要目的是查看调用链的执行过程。
然后查看ExceptionTranslationFilter:
1 | public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) |
因为是捕获异常的过滤器,重要代码都在catch里,对不同异常进行不同处理
最后查看UsernamePasswordAuthenticationFilter:
1 | public Authentication attemptAuthentication(HttpServletRequest request, |
提取请求中的用户名密码,进行校验,成功则设置认证成功。
断点调试
接着我们对这几个类进行打断点,并DEBUG启动测试,看一下执行流程

首先进入Interceptor,这是因为第一次访问需要验证过滤

接着进入Exception过滤器,捕获到Interceptor抛出的AccessDeniedException: Access is denied异常,这里可以深入的跟一下

首先调用handleSpringSecurityException()方法

因为异常是AccessDeniedException,所以调用sendStartAuthentication()方法

最后进入authenticationEntryPoint.commence()方法,因为我们使用表单方式,所以进入LoginUrlAuthenticationEntryPoint类

调用这个方法,通过注释也可知道是转发到login界面了

接着我们输入用户名密码,再次查看调用拦截链路

这里就会进入到第一个绿色框了即表单登录拦截器,这里判断用户名密码正确后会在request中添加token

最后到Interceptor的时候判断请求头具有token认证,即放行,成功请求到API方法

总结一下,拦截链路为:
Interceptor -> ExceptionFilter -> /login页面 -> 表单验证Filter -> Interceptor -> API接口