Spring Security基本原理
上一篇文章中我们搭建好了springsecurity的相关模块。这篇文章我们先简单讲一讲Spring Security的基本原理,首先我们把demo模块的application中的@EnableAutoConfiguration注解注释掉。然后在browser模块中添加以下代码:
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.and()
.authorizeRequests()//表示下面是认证的配置
.anyRequest()//任何请求
.authenticated();//都需要身份认证
}
}
在这里我们重写了WebSecurityConfigurerAdapter的configure(HttpSecurity http),这要是对Http请求做一些安全处理。然后启动demo应用,这时我们随便访问一个url会跳转到以下页面,而不是我们想访问的页面:

这个页面是SpringSecurity的默认登录界面,这里需要我们输入用户名和密码,用户名默认是user,密码我们可以看到SpringBoot启动的时候,会把密码打印出来,如下图所示:

这个密码每次重启应用都会改变,这里我们输入了用户名和密码后,就可以跳转到我们想看到的界面了。
通过这么一个简单的改动我们可以看到SpringSecurity有如下的核心功能:
- 认证(你是谁)
- 授权(你能干什么)
-
攻击防护(防止伪造身份)
为什么去掉这么一个注解,加了一段代码就会有这样的功能了呢?这就是我们这篇文章要讲的重点了。我们先看一个图:
spring security
在没有Spring Security的时候,我们直接访问REST APi可以得到结果,但是当我们的应用加入了Spring security之后,相当于加上了过滤器,其实Spring Security本身就是一个过滤器链,所有的请求在访问REST API时都要经过Spring Security的过滤器链,当返回应答的时候,也会走一遍这个过滤器链,然后返回给用户。
在图中我们可以看到第一个绿色的过滤器链Username Password Authentication Filter,这个就是用来拦截我们刚才在图中输入的用户名和密码的过滤器。
图中的BasicAuthenticationFilter如果换成老版本一点的Spring security会更加好理解一些,在相对比较老版本的SpringSecurity中,还有弹出登录框供用户输入的情况,其实和我们看到的第一个登录表单非常相似。只不过Username Password过滤的是页面中的表单,BasicAuthenticationFilter过滤的是登录框中的信息。不过现在新版本中我们已经看不到登录框了,两者似乎被融合了。我们后续要讲的微信登录,第三方认证登录,其实就是配置在绿色方框中的Filter~
绿色方框的Filter可以有很多,顺序也可以有变动,而且可有可无。但是后面蓝色的Exception Translation Filter和Filter Security Interceptor是Spring Security过滤器链中顺序是固定的而且是一定存在的。
我们先讲讲最后一个Filter Security Interceptor,它是Spring Security的守门员,也决定了我们的请求究竟能不能访问后面的REST API。在以下代码中我们配置的信息都被放到了Filter Security Interceptor中,所以请求来了之后它会看我们有没有做用户登录认证,如果没有认证,Interceptor就会抛出异常。当然我们还可以在我们的代码里面配置只有VIP用户才可以访问相关的信息。这样,即使我们登录认证了,但是不是VIP身份,仍然不可以访问后台的API,Filter Security Interceptor依然抛异常
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.and()
.authorizeRequests()//表示下面是认证的配置
.anyRequest()//任何请求
.authenticated();//都需要身份认证
}
刚才我说了Interceptor要抛出异常,那么这些异常被谁捕获?对的,那就是我最后要讲的Exception Translation Filter。它就是专门负责捕获异常并且做出相关翻译的过滤器。如果说Interceptor抛出没有身份认证的异常,Exception Translation Filter会看看前面究竟配置的是什么样的Filter,然后做出相应的处理。比如说前面配置的是Username Password Authentication Filter,那么就会跳转到带表单的登录页面。但如果说前面配置的是BasicAuthenticationFilter,那么就会弹出登录框等待用户输入信息。
接下来我们以断点调试的方式来对这个基本原理做一个实战总结。为了印证我之前讲的是正确的,我们将会在以下3个类中打断点:
1.FilterSecurityInterceptor

重点看我标注红框的地方,super.beforeInvocation(fi)表示说我在正式调用后台的rest服务之前,我要看看前面的filter是否都校验通过了。fi.getChain().doFilter(fi.getRequest(), fi.getResponse());表示真正的调用后台的服务。所以我们在124行打上断点。
2.ExceptionTranslationFilter

这个ExceptionFilter其实我们看到它只做简单的过滤,但是它真正核心的逻辑再catch(Exception ex)里面,它捕获了Interceptor中抛出的异常,并对这些异常作相关的处理。
3.UsernamePasswordAuthenticationFilter

UsernamePasswordAuthenticationFilter,从它的构造函数我们可以看出只对/login的POST请求做拦截,核心路基就是获取表单中的username和password做相关校验。
OK,此时我们访问一下我们的后台API,在浏览器输入
http://localhost:8080/user 回车

第1个进入的断点是:FilterSecurityInterceptor中的断点

为啥会直接跑到最后一个Interceptor里面呢?作者你是不是在忽悠我们,第一个UsernamePasswordAuthenticationFilter都没拦截,差评。其实,原因是这样的,我们直接访问的URL,根本就没有输入任何的用户名和密码,所以Filter如果发现没有任何输入信息,它就放过了,什么都不处理。而且我们刚才看了截图,发现这个Filter只关注POST的/login请求。什么?!那相当于没用咯?别着急继续往下看
第2个进入的断点是:ExceptionTranslationFilter中的断点

为什么会这样?这是因为我在文章的开篇就写了,我们的任何请求都要登录认证,所以果断抛出异常,被ExceptionTranslationFilter拦截住。并且重定向到登录页面:如下图所示:

这个不就是我们熟悉的登录页面了么~~
第3输入用户名密码,被UsernamePasswordAuthenticationFilter拦截


为什么会被拦截,因为我们真正的发起了login的post请求了!

断点中我们可以看到我们输入的用户名和密码信息~就是我们在前台页面输入的信息哦
第4个进入的断点是:FilterSecurityInterceptor中的断点
又回到了Interceptor的怀抱了!但是这次它就不要想拦住我们了!我们认证了!不能再收过路费了,不然过分了啊!

大结局

终于看到我们想要的请求啦~~SpringSecurity的基本原理我们就先讲到这里,下一篇我将给大家讲解自定义用户认证逻辑。因为我们不可能直接用spring security默认的认证逻辑,这样不满足我们的需求~