0


【Spring Security详解】第一章 | 概述

从本系列开始,博主将带来大家深入学习Spring Security。博主对该框架的看法是不但要会使用,还有能够理解其源码,要知其然,还要知其所以然。
相信朋友们阅读完博主本系列全部文章之后,定会理解Spring Security,让我们从入门、到理解、最终吊打面试官!

PS:博主早在8月中旬开始写本系列博客,本来想一文搞定Spring Security,但由于Spring Security的细节特别多,已经写了2w字却感觉才将心中所想写了近半不到,因此萌生了想写Spring Security体系一系列文章的想法。还请多多关注博主,不胜感激!

在本篇内容,博主给大家介绍一下Spring Security在市场上的使用情况,以及Spring Security是通过什么原来完成认证操作的(梗概)。同时也涉及Spring Security的源码结构,可能不太易懂,建议配合本系列文章食用。

一、 Spring Security简介

在Java企业开发中,市面上常见的开源安全框架非常少,主要有以下几种方案:

  • Shiro
  • Spring Security
  • 企业自行开发的方案

几年前,微服务还没有大火的时候,Shiro以其轻量、简单、易于集成的优点独当一面。
而最近今年,随着微服务的大火,Spring Security作为Spring家族的首推的安全框架,在与Spring等其他组件的无缝整合的特点,导致其市面占有率也是逐年提高。

二、 Spring Security提供了哪些功能

Spring Security是Spring全家桶里面的一个项目,提供认证、授权以及应对漏洞攻击的保护。

  • 认证authentication: 可以简单理解成”你是谁“,最简单的例子就是用户登录,这就是认证,下文中登录操作代表认证
  • 授权authorization:可以简单理解成“你有哪些权限,你能做什么”,比如登录进来的用户是具有管理员或是普通用户的权限。
  • 保护protection:应对遭受漏洞利用的保护。

三、 Spring Security是如何完成认证的

Spring Security通过一系列过滤器完成认证与授权的工作。

对于SpringBoot工程,并没有引入其他依赖。
客户端发起请求时,tomcat容器会创建一个包括Filter和Servlet的FilterChain(过滤器链)。通过Filter可以控制请求与响应,以及是否调用下游的过滤器或Servlet。

接来下博主简要说明下Spring Security中起到核心作用的几个类,这是通过这几个类Spring Security才能集成到SpringBoot当中,并发挥作用。

此处参考了Spring Security的官网文档:链接: Spring Security官方文档

3.1 DelegatingFilterProxy

Spring Seucrity实现认证与授权的功能提供了很多过滤器,通过这些过滤器来拦截请求,并做相应处理。那么如何将这些过滤器嵌入到Spring的IOC容器呢,最好的做法就是将Spring Security这些过滤器注册成Bean,这样就可以统一的进行管理了,

DelegatingFilterProxy

就是为了实现这个目的。

Delegating

这个名字很绕口

ˈdelɪɡeɪtɪŋ'

,是委托的意思。

DelegatingFilterProxy

合到一起就是

委托过滤器代理


整体意思就是

DelegatingFilterProxy

是一个代理,他委托了某个类(

FilterChainProxy

下文会提到),并让那个类完成后续拦截操作。
可以把他理解成一个胶水,由他连接了web应用的原生过滤器和Spring Security的过滤器。
在这里插入图片描述

DelegatingFilterProxy

是一个过滤器,里面有个成员变量

privatevolatileFilter delegate;

他就是委托对象。

在客户端请求来临的时候会执行

doFilter()

方法。
首先会判断delegate是否为空,若为空的话从IOC容器中通过

getBean()

的方法拿到这个代理对象

FilterChainProxy

protectedFilterinitDelegate(WebApplicationContext wac)throwsServletException{String targetBeanName =getTargetBeanName();Assert.state(targetBeanName !=null,"No target bean name set");Filter delegate = wac.getBean(targetBeanName,Filter.class);if(isTargetFilterLifecycle()){
            delegate.init(getFilterConfig());}return delegate;}

并执行代理对象的

doFilter()

方法。

protectedvoidinvokeDelegate(Filter delegate,ServletRequest request,ServletResponse response,FilterChain filterChain)throwsServletException,IOException{

        delegate.doFilter(request, response, filterChain);}

这样后续的操作就交由这个代理对象去做了。

3.2 FilterChainProxy & SecurityFilterChain(二者关系密切,放一起讲述)

FilterChainProxy

这个类可以理解成过滤器链代理。

DelegatingFilterProxy

正是委托给

FilterChainProxy

,就是上文提到的

delegate

来完成拦截等操作。

FilterChainProxy

是Spring Security发挥作用的入口,一切Spring Security的过滤器都是从这之后开始调用的。

另外值得注意的是,

DelegatingFilterProxy

是注册到Tomcat容器的一个过滤器,他的生命周期由Tomcat来控制。而

FilterChainProxy

则是Spring的IOC容器中的一个Bean。
在这里插入图片描述
这幅图展示了客户端client请求到系统中时,经过Tomcat的某些原生过滤器后,到达

DelegatingFilterProxy

。并委托给

FilterChainProxy

,而

FilterChainProxy

通过

SecurityFilterChain

来代理各种Filter实例。之后再到Tomcat的原生过滤器,最终到达Servet。
简而言之,

FilterChainProxy

使用

SecurityFilterChain

确定应对此请求调用哪些Spring Security过滤器。

在这里插入图片描述
可以看到delegate对象中包括一个过滤器链的列表(

SecurityFilterChain

)。其中

DefaultSecurityFilterChain

对象就是Spring Security的一个过滤器链,如前一个图片所示的

SecurityFilterChain

FilterChainProxy

作为一个代理类,他的

doFilter()

方法最终会调到下面的

doFilterInternal()

privatevoiddoFilterInternal(ServletRequest request,ServletResponse response,FilterChain chain)throwsIOException,ServletException{FirewalledRequest firewallRequest =this.firewall.getFirewalledRequest((HttpServletRequest) request);HttpServletResponse firewallResponse =this.firewall.getFirewalledResponse((HttpServletResponse) response);List<Filter> filters =getFilters(firewallRequest);if(filters ==null|| filters.size()==0){if(logger.isTraceEnabled()){
                logger.trace(LogMessage.of(()->"No security for "+requestLine(firewallRequest)));}
            firewallRequest.reset();
            chain.doFilter(firewallRequest, firewallResponse);return;}if(logger.isDebugEnabled()){
            logger.debug(LogMessage.of(()->"Securing "+requestLine(firewallRequest)));}// 看这里VirtualFilterChain virtualFilterChain =newVirtualFilterChain(firewallRequest, chain, filters);
        virtualFilterChain.doFilter(firewallRequest, firewallResponse);}

将后续的执行操作交由他的一个内部静态类去实现。执行

VirtualFilterChain#doFilter()

方法。

@OverridepublicvoiddoFilter(ServletRequest request,ServletResponse response)throwsIOException,ServletException{if(this.currentPosition ==this.size){if(logger.isDebugEnabled()){
                    logger.debug(LogMessage.of(()->"Secured "+requestLine(this.firewalledRequest)));}// Deactivate path stripping as we exit the security filter chainthis.firewalledRequest.reset();this.originalChain.doFilter(request, response);//退出循环return;}this.currentPosition++;// 执行Spring Security的过滤器Filter nextFilter =this.additionalFilters.get(this.currentPosition -1);if(logger.isTraceEnabled()){
                logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(),this.currentPosition,this.size));}
            nextFilter.doFilter(request, response,this);}

在这里面会循环的调用每个Spring Security提供的过滤器进行各种拦截处理操作,并在最后退出循环,进入Tomcat的其他过滤器中…
请添加图片描述

3.3 多个SecurityFilterChain

在Spring Security中,可以配置多个

SecurityFilterChain

,由

FilterChainProxy 

决定应使用哪个

SecurityFilterChain

FilterChainProxy 

会根据请求的路由匹配第一个符合条件的

SecurityFilterChain

,并执行其过滤器。
在这里插入图片描述

四、 Spring Security的初步使用

很多人对Spring Security的感觉都是太繁琐,其实到了微服务的天下,Spring Security的使用非常简单。
接下来博主以一个简单的例子给大家演示一下。

4.1 集成Spring Security

引入pom依赖。

<!--标识一个springboot的web工程--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--引入Spring Security--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>

Spring Security是通过一系列过滤器来完成认证与授权的功能的。客户端请求之后逐个通过Spring Security的各种过滤器。当引入Spring Security依赖时,其实已经加载了Spring Security提供的许多个默认过滤器。

添加请求URL,当做用来测试的资源URL。

@RequestMapping("hello")publicclassHelloController{@GetMapping()publicStringhello(){return"hello";}}

启动项目,可以看到控制台输出的日志中,包括了如下的内容。
按照Spring Security官网的描述,其实生成了名为

user

的用户,密码为如下

71c36beb-7af5-4116-b807-ab84e484e6fa


并且可以看到控制台打印了Spring Security默认加载的15个过滤器,正是他们支撑着Spring Security做到了认证相关的操作。
稍后博主会挑常见的过滤器给大家说明一下,值得注意的是,这15个过滤器的先后执行顺序就是控制台打印的顺序。

Using generated security password: 71c36beb-7af5-4116-b807-ab84e484e6fa

2022-08-22 20:22:07.179  INFO 10672 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: any request, [
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@4784013e,  
org.springframework.security.web.context.SecurityContextPersistenceFilter@2ca6546f,  
org.springframework.security.web.header.HeaderWriterFilter@aa10649,  
org.springframework.security.web.csrf.CsrfFilter@c4c0b41,  
org.springframework.security.web.authentication.logout.LogoutFilter@3af356f, 
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@267517e4, 
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@231baf51, 
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@6f952d6c, 
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@56ba8773, 
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7923f5b3, 
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@6050462a, 
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@5965844d, 
org.springframework.security.web.session.SessionManagementFilter@37095ded, 
org.springframework.security.web.access.ExceptionTranslationFilter@368d5c00, 
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@1763992e 
]

4.2 访问测试

接下来使用浏览器访问该资源。请求地址

http://localhost:8080/hello

(SpringBoot默认启动端口为8080)
可以观察到页面直接跳转到了

http://localhost:8080/login

并打开了一个登录页面。
在这里插入图片描述
F12可以看到页面请求

http://localhost:8080/hello

之后,返回响应302,并重定向到

http://localhost:8080/login

接口进行请求,该接口响应为一个页面。让我们完成登录操作。
在这里插入图片描述
输入账号密码后,点击登录(用户:

user

,密码:

71c36beb-7af5-4116-b807-ab84e484e6fa

),此时可以看到页面返回了接口

hello

,这也意味着只有认证成功才会允许访问资源。

这就是Spring Security的魅力。博主只是引入了一个Spring Security依赖就做到了所有资源的保护,那他是怎么做到的呢,且听我慢慢道来。

4.3 为什么默认访问资源会返回登录页面?

在这里插顶顶顶顶

该图片来自《深入浅出Spring Security》

当客户端发起一个资源的请求时(

http://localhost:8080/hello

),会经过上文所述的15个Spring Security的过滤器依次执行。

直到走到

FilterSecurityInterceptor

这个过滤器的时候,抛出一个访问被拒绝的异常。在这里插入图片描述
此处代码走到

AbstractSecurityInterceptor

类的原因是FilterSecurityInterceptor的

doFilter()

调用到了父类的代码,在父类的方法中抛出了

AccessDeniedException

,该异常会继续往上抛出。

直到ExceptionTranslationFilter的catch模块捕获到了这个异常。
在这里插入图片描述
并最终调用

authenticationEntryPoint.commence(request, response, reason);

在这里插入图片描述
最终将请求重定向到

http://localhost:8080/login

页面。
在这里插入图片描述

紧接着,客户端再次向服务请求

http://localhost:8080/login

老规矩又开始按顺序执行这15个过滤器,直到到达

DefaultLoginPageGeneratingFilter

过滤器的时候,会判断若是访问登录请求URL或是登录失败或是退出成功中的一个,会执行下面的逻辑。
在这里插入图片描述
很明显

isLoginUrlRequest(request) == true

然后代码来到了

generateLoginPageHtml()

可以看到通过StringBuilder拼接了一个HTML的登录页面。
在这里插入图片描述
后续操作就是往response写入了这个html的登录页面,并返回。所以就有了当初请求

http://localhost:8080/hello

时,出现了一个登录页面。

这便是集成Spring Security后,Spring Security的默认安全策略。

博主简单梳理一下这块逻辑。

  1. 客户端请求一个资源URL。
  2. 请求会按照顺序经过Spring Security默认提供的15个过滤器,在FilterSecurityInterceptor过滤器中发现用户没有认证会抛出AccessDeniedException异常。
  3. 异常会被ExceptionTranslationFilter过滤器被捕获到,并调用authenticationEntryPoint#commence方法将请求重定向到/login接口。
  4. 客户端再次请求/login接口。
  5. 请求被DefaultLoginPageGeneratingFilter过滤器拦截,并生成了一个登录页面并返回给客户端。

五、 小结

本章博主主要给大家介绍了Spring Security在市场上的使用情况,以及Spring Security的整体架构。并举了一个简单的例子说明为什么仅仅引入了Spring Security的maven依赖就对资源做了保护。
接下来博主会带来大家进一步理解Spring Security的认证细节,尽情期待!

标签: java 后端 spring boot

本文转载自: https://blog.csdn.net/hdd5678/article/details/127061512
版权归原作者 不堪提 所有, 如有侵权,请联系我们删除。

“【Spring Security详解】第一章 | 概述”的评论:

还没有评论