一文带你读懂Spring Security 6.0的实现原理

本文深入剖析Spring Security 6.1.x的架构和实现原理,从身份认证(Authentication)和鉴权控制(Authorization)两方面入手,解释其设计思想,帮助读者理解Spring Security框架的总体架构。文章介绍了Spring Security如何通过FilterChainProxy、SecurityFilterChain和DelegatingFilterProxy等组件实现安全验证,同时分析了Authentication和Authorization的核心流程。通过对源码的解读,阐述了UsernamePasswordAuthenticationFilter、AuthorizationFilter的工作机制,以及如何自定义身份认证和鉴权策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

导言

Spring Security是一个功能强大且高度且可定制的身份验证和访问控制框架,除了标准的身份认证和授权之外,它还支持点击劫持,CSRF,XSS,MITM(中间人)等常见攻击手段的保护,并提供密码编码,LDAP认证,Session管理,Remember Me认证,JWT,OAuth 2.0等功能特性。

由于安全领域本身的复杂性和丰富的安全特性支持,以及Spring Security高度的可定制性,使得它成为一个庞大且复杂的框架。每次升级可能带来的破坏性更新,加上网络上的陈旧教程,更是加重了Spring Security非常难用的印象。很多新手可能跟作者一样,首次引入Spring Security框架之后,突然发现很多页面无法访问,感到无所适从。

为此,本文将基于Spring Boot 3.1.x依赖的Spring Security 6.1.x版本,深入探讨Spring Security的架构和实现原理。本文将着重解释Spring Security的设计思想,而不会过多涉及具体的实现细节。文章的目标是让读者在阅读完本文之后,能够对整个Spring Security框架有个清晰的理解,并在面对问题时知道如何着手排查。另外,本文重点关注Spring Security的总体架构,以及身份认证(Authentication)和鉴权控制(Authorization)的实现。

【版本兼容性】Spring Security 6引入了很多破坏性的更新,包括废弃代码的删除,方法重命名,全新的配置DSL等,但是架构和基本原理还是保持不变的。本文在讲解过程中会尽量指出当前版本跟老版本的差异,尤其是涉及到兼容性问题的时候。
【阅读提示】本文的篇幅较长,并且包含了部分源码分析,时间有限的情况下,可以重点阅读架构图部分。

Java Web应用的Security实现基本思路

大家可以尝试思考下,安全相关的校验和处理,应该处于应用的哪个部分呢?答案是,应该放在所有请求的入口,因为它是跟具体的业务逻辑无关的,在Spring MVC世界里就是@Controller之前。

在JakartaEE(JavaEE的新版)规范中,Filter和Servlet都符合这个前置要求。然而,Spring的Web应用基本上只包含一个DispatcherServelt,主要用于请求分发,缺乏安全相关的支持和合适的扩展机制。而Filter运行在Servlet之前,而规范本身就支持配置多个Filter。因此,在请求到达Servlet之前,先通过Filter进行安全验证就是一个非常合理的实现方式。这样可以在请求进入业务逻辑之前,对请求进行拦击,然后进行必要的安全性检查和处理。

这也是Spring Security的实现方式。本质上,Spring Security的实现原理很简单,就是提供了一个用于安全验证的Filter。假如我们自己实现一个简化版的Filter,它的大概逻辑应该是这样的:

Java复制代码public class SimpleSecurityFilter extends HttpFilter {
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        UsernamePasswordToken token = extractUsernameAndPasswordFrom(request);  // (1)
        if (notAuthenticated(token)) {  // (2)
            // 用户名密码错误
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // HTTP 401.
            return;
        }
        if (notAuthorized(token, request)) { // (3)
            // 当前登录用户的权限不足
            response.setStatus(HttpServletResponse.SC_FORBIDDEN); // HTTP 403
            return;
        }
        // 通过了身份验证和权限校验,继续执行其它Filter,最终到达Servlet
        chain.doFilter(request, response); // (4)
    }
}
  1. 从HTTP请求中获取用户名和密码,来源包括标准的Basic Auth HTTP Header,表单字段或者cookie等等。
  2. 身份认证,也就是校验用户名和密码。
  3. 认证通过后,需要检查当前登录的用户有没有访问当前HTTP请求的权限,也就是鉴权逻辑。
  4. 权限校验也通过后,就继续执行其它Filter,所有Filter都通过后,进入Servlet,最终到达具体的Controller。

FilterChain

在安全领域,由于攻防手段的多样性和认证鉴权方式的复杂性,将所有功能都放在一个Filter中会导致该Filter迅速演变为一个庞大而复杂的类。

因此,在实际应用场景中,我们常常将这个庞大的Filter拆分成多个小Filter,并将它们链接在一起。每个Filter都只负责特定领域的功能,比如CsrfFilter,AuthenticationFilter,AuthorizationFilter等。

这种概念被称为FilterChain,实际上JarkataEE规范也有相识的概念。通过使用FilterChain,你就可以以插拔的方式添加或移除特定功能的Filter,而无需改动现有的代码。

Spring Security框架的基本架构和原理

上一节其实已经说明了Spring Security框架的基本思路,下面我们深入分析其实现原理和架构。

实现原理

一个应用引入了Spring Security Starter包后,再启动应用,你会发现控制台多了下面这条日志,说明已经开启了Security特性。

kotlin复制代码2023-07-12T10:05:23.168+08:00  INFO 680540 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@46e3559f, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@3b83459e, org.springframework.security.web.context.SecurityContextHolderFilter@26837057, org.springframework.security.web.header.HeaderWriterFilter@2d74c81b, org.springframework.security.web.csrf.CsrfFilter@3a17b2e3, org.springframework.security.web.authentication.logout.LogoutFilter@5f5827d0, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@4ed5a1b0, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@3b332962, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@32118208, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@67b355c8, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@991cbde, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@dd4aec3, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@414f87a9, org.springframework.security.web.access.ExceptionTranslationFilter@59939293, org.springframework.security.web.access.intercept.AuthorizationFilter@f438904]

从这条日志可以观察到,Spring Security通过DefaultSecurityFilterChain类来完成安全相关的功能,而该类本身又由其它Filter组成。默认情况下,Spring Security Starter引入了15个Filter,下面我们简要介绍下其中几个重要的Filter:

  1. CsrfFilter:这个Filter用于防止跨站点请求伪造攻击,这也是导致所有POST请求都失败的原因。基于Token验证的API服务可以选择关闭CsrfFilter,而一般Web页面需要开启。
  2. BasicAuthenticationFilter:支持HTTP的标准Basic Auth的身份验证模块。
  3. UsernamePasswordAuthenticationFilter:支持Form表单形式的身份验证模块。
  4. DefaultLoginPageGeneratingFilter和DefaultLogoutPageGeneratingFilter:用于自动生成登录页面和注销页面。
  5. AuthorizationFilter: 这个Filter负责授权模块。值得注意的是,在老版本中鉴权模块是FilterSecurityInterceptor.

这些Filter构成了Spring Security的核心功能,通过它们,我们可以实现身份验证、授权、防护等安全特性。根据应用的需求,我们可以选择启用或禁用特定的Filter,以定制和优化安全策略。

SecurityFilterChain

DefaultSecurityFilterChain类实现了SecurityFilterChain接口,我们打开这个接口的源码,会发现它只有两个方法,matches用于匹配特定的Http请求(比如特定规则的URL),getFilters 用于获取可用的所有Security Filter。

Java复制代码public interface SecurityFilterChain {
    boolean matches(HttpServletRequest request); // 规则匹配
    List<Filter> getFilters(); // 该FilterChain下的所有Security Filter
}

从这段代码可以得出两个结论:

  1. 不同的Http请求可以对应不同的SecurityFilterChain(通过matches方法)。
  2. SecurityFilterChain不是我们以为的JakartaEE的Servlet Filter实现,它仅仅是一个包含多个Filter的容器,本身不负责调度和执行。它只是一个配置项,用于指定一组Filter,以实现特定的安全需求。

DelegatingFilterProxy

实际上,JakartaEE层面上的Filter实现是DelegatingFilterProxy类,它在Spring Security中起到了一个重要的桥梁作用,连接了Servlet容器和Spring容器。Servlet容器不了解Spring定义的Beans,而Spring Security的大部分组件及其依赖都是注册到Spring容器中的Bean。

DelegatingFilterProxy核心代码的主要工作就是从WebApplicationContext获取指定名称的Filter Bean,然后委托给这个Bean的doFilter方法。以下是简化后的伪代码:

Java复制代码public void doFilter(ServletRequest request, ServletResponse
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值