Spring Security是如何工作的?

简介: Spring Security 是一个强大的框架,用于保护 Spring 应用程序,提供全面的安全服务,包括身份验证、授权等功能。本文将介绍其核心概念及默认配置。Spring Security 通过与 Spring MVC、Spring Webflux 或 Spring Boot 集成,创建高度可定制的身份验证和访问控制框架。其核心组件包括 Servlet Filters、Authentication 和 Authorization。通过默认的过滤器链和一系列预定义过滤器,Spring Security 可以轻松实现各种安全功能。

嗨,你好呀,我是猿java

Spring Security 是一个帮助保护企业应用程序的框架,通过与 Spring MVC、Spring Webflux 或 Spring Boot 集成,创建一个功能强大且高度可定制的身份验证和访问控制框架。在本文中,我们将解释核心概念,并仔细研究 Spring Security 提供的默认配置以及它们的工作原理。

什么是Spring Security?

Spring Security 是一个用于保护Spring应用程序的框架,它提供了全面的安全服务,包括身份验证、授权、加密、以及防止常见攻击(如CSRF和会话固定攻击)。Spring Security 的设计使其易于扩展,可以根据特定需求进行高度定制。

Spring Security的核心组件

要了解 Spring Security 默认配置的工作原理,我们首先需要了解其三大核心组件:

  • Servlet Filters
  • Authentication
  • Authorization

Servlet Filters

当 DefaultSecurityFilterChain 请求到达 DispatcherServlet 之前触发了一连串过滤器。DispatcherServlet 是 Web 框架中的一个关键组件,用于处理传入的 Web 请求并将其分派给适当的处理程序进行处理。

为了理解 FilterChain 是如何工作的,让我们看一下 Spring Security 文档中的流程图:

security-filter-chain.png

接下来,让我们看一下参与 filter chain 的核心组件:

  • DelegatingFilterProxy:它是 Spring 提供的一个 servlet 过滤器,充当 Servlet 容器和 Spring 应用程序上下文之间的桥梁。DelegatingFilterProxy 类负责将实现 javax.servlet.Filter 的任何类连接到过滤器链中。
  • FilterChainProxy Spring security 在内部创建一个名为 springSecurityFilterChain 的 FilterChainProxy bean,该 bean 包装在 DelegatingFilterProxy 中。FilterChainProxy 是一个过滤器,它根据安全配置链接多个过滤器。因此,DelegatingFilterProxy 将请求委托给 FilterChainProxy,后者确定要调用的过滤器。
  • SecurityFilterChain:SecurityFilterChain 中的安全过滤器是在 FilterChainProxy 中注册的 bean。一个应用程序可以有多个 SecurityFilterChain。FilterChainProxy 在 HttpServletRequest 上使用 RequestMatcher 接口来确定需要调用哪个 SecurityFilterChain。

通过上述分析可以看出 Spring Security 会提供了一个默认的过滤器链,该过滤器链调用一组预定义的和有序的过滤器,因此,我们接下来要分析几个重要的过滤器角色:

  • org.springframework.security.web.csrf.CsrfFilter :默认情况下,此过滤器将CSRF保护应用于所有REST端点。要了解有关 Spring Boot 和 Spring Security 中的 CSRF 功能的更多信息,请参阅此文章。
  • org.springframework.security.web.authentication.logout.LogoutFilter :当用户注销应用程序时,将调用此过滤器。将调用 LogoutHandler 的默认注册实例,这些实例负责使会话无效并清除 SecurityContext。接下来,LogoutSuccessHandler 的默认实现将用户重定向到新页面 (/login?logout)。
  • org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter :使用启动时提供的默认凭据验证URL(/login)的用户名和密码。
  • org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter :在/login处生成默认登录页面html
  • org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter:在/login?logout处生成默认注销页面html
  • org.springframework.security.web.authentication.www.BasicAuthenticationFilter :此过滤器负责处理具有Authorization,Basic Authentication scheme,Base64编码用户名密码的HTTP请求头的任何请求。身份验证成功后,Authentication 对象将放置在 SecurityContextHolder 中。
  • org.springframework.security.web.authentication.AnonymousAuthenticationFilter :如果在SecurityContext中找不到Authentication对象,它将创建一个具有主体anonymousUser和role ROLE_ANONYMOUS的对象。
  • org.springframework.security.web.access.ExceptionTranslationFilter :处理过滤器链中抛出的AccessDeniedException和AuthenticationException。对于 AuthenticationException,需要 AuthenticationEntryPoint 的实例来处理响应。对于 AccessDeniedException,此筛选器将委托给 AccessDeniedHandler,其默认实现为 AccessDeniedHandlerImpl。
  • org.springframework.security.web.access.intercept.FilterSecurityInterceptor :此过滤器负责授权在请求到达控制器之前通过过滤器链的每个请求。

Authentication

Authentication(身份验证)是验证用户凭据并确保其有效性的过程,接下来看看 spring 框架是如何验证创建的默认 credentials凭据,其具体步骤如下:

  • 步骤.1:当启用 FormLogin 时,即当向 URL /login发出请求时,UsernamePasswordAuthenticationFilter将作为安全过滤器链的一部分被调用。此类是基 AbstractAuthenticationProcessingFilter 的特定实现。尝试进行身份验证时,筛选器会将请求转发到 AuthenticationManager。
  • 步骤2:UsernamePasswordAuthenticationToken是Authentication接口的实现。此类指定身份验证机制必须通过用户名-密码进行。
  • 步骤.3:获得身份验证详细信息后,AuthenticationManager 尝试借助 AuthenticationProvider 的适当实现对请求进行身份验证,并返回完全经过身份验证的 Authentication 对象。默认实现是 DaoAuthenticationProvider,它从 UserDetailsService 中检索用户详细信息。如果身份验证失败,则会引发 AuthenticationException。
  • 步骤.4:UserDetailsService 的 loadUserByUsername(username) 方法返回包含用户数据的 UserDetails 对象。如果未找到具有给定用户名的用户,则会引发 UsernameNotFoundException。
  • 步骤 5:身份验证成功后,SecurityContext 将使用当前经过身份验证的用户进行更新。

为了理解上述概述的步骤,让我们看一下 Spring Security 文档中定义的身份验证架构图:

security-authentication.png

ProviderManager 是 AuthenticationManager 最常见的实现,如图所示,ProviderManager 将请求委托给已配置的 AuthenticationProvider 列表,查询每个 AuthenticationProvider 以查看它是否可以执行身份验证。如果身份验证失败并显示 ProviderNotFoundException(一种特殊类型的 AuthenticationException),则表明 ProviderManager 不支持传递的身份验证类型。此体系结构允许我们在同一应用程序中配置多种身份验证类型。

AuthenticationEntryPoint 是一个接口,它充当身份验证的入口点,用于确定客户端在请求资源时是否包含了有效的凭据,否则,将使用接口的适当实现从客户端请求凭据。

那么,Authentication 对象是如何绑定整个身份验证过程?身份验证接口用于以下用途:

  • 向 AuthenticationManager 提供用户凭据。
  • 表示 SecurityContext 中当前经过身份验证的用户。每个身份验证实例都必须包含
    • principal - 这是标识用户的 UserDetails 实例。
    • credentials
    • authorities - GrantedAuthority GrantedAuthority 的实例在授权过程中起着重要作用。

Authorization

Authorization(授权)是确保访问资源的用户或系统具有有效权限的过程。
在 Spring 安全过滤器链中,FilterSecurityInterceptor 触发授权检查。从过滤器执行的顺序可以看出,身份验证在授权之前运行。在用户成功通过身份验证后,此筛选器将检查有效权限。如果授权失败,将引发 AccessDeniedException。

授予权限

如上一部分所示,每个用户实例都包含一个 GrantedAuthority(授予权限)对象的列表。GrantedAuthority 是一个具有单一方法的接口:

public interface GrantedAuthority extends Serializable {
   
    String getAuthority();
}

默认情况下,Spring 安全性调用具体的 GrantedAuthority 实现 SimpleGrantedAuthority。SimpleGrantedAuthority 允许我们将角色指定为 String,并自动将它们映射到 GrantedAuthority 实例中。AuthenticationManager 负责将 GrantedAuthority 对象列表插入到 Authentication 对象中。然后,AccessDecisionManager 使用 getAuthority() 来确定授权是否成功。

授予的权限与角色

Spring Security 分别使用 hasAuthority() 和 hasRole() 方法通过授予的权限和角色提供授权支持。这些方法用于基于表达式的安全性,并且是接口 SecurityExpressionOperations 的一部分。

在大多数情况下,这两种方法可以互换使用,最显着的区别是 hasRole() 不需要指定 ROLE 前缀,而 hasAuthority() 需要显式指定完整的字符串。例如,hasAuthority("ROLE_ADMIN") 和 hasRole("ADMIN") 执行相同的任务。

附加说明

Spring 允许我们使用 @PreAuthorize 和 @PostAuthorize 注解来配置方法级证券。顾名思义,它们允许我们在方法执行之前和之后对用户进行授权。可以在 Spring 表达式语言 (SpEL) 中指定授权检查的条件。我们将在后面的部分中查看一些示例。
我们可以通过公开 GrantedAuthorityDefaults bean 来配置授权规则以使用不同的前缀(而不是 ROLE_)。

如何使用?

这里以一个简单的登录功能为例进行说明。

首先,我们需要添加 Spring Security 的依赖,这里以 Maven 为例:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

接着,编写一个测试的 Controller,如下:

@RestController
@RequestMapping
public class TestController {
   

    @GetMapping("/login")
    public String login(){
   
        // 业务逻辑
        return "success";
    }
}

最后,使用浏览器访问地址:127.0.0.1:8080/login,会弹出一个登录页面,如下图:

security-login.png

通过上面 3步,我们在没有写一行前端代码的前提下实现了一个登录功能,默认情况下 Spring Security 会生成默认的密钥,我们也可以在 application.yml 中配置用户名和秘密,配置如下:

spring:
  security:
    user:
      name: test
      password: test

上述示例,我们展示了 Spring Security 最简单的一个使用,另外它还支持自定义配置,基于 Java 的配置是 Spring Security推荐的配置方式。通过扩展 WebSecurityConfigurerAdapter,可以覆盖 configure方法来配置认证和授权规则,如下示例:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
   
    @Override
    protected void configure(HttpSecurity http) throws Exception {
   
        http
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
   
        auth.inMemoryAuthentication()
            .withUser("user").password("{noop}password").roles("USER")
            .and()
            .withUser("admin").password("{noop}admin").roles("ADMIN");
    }
}

Spring Security使用场景

Spring Security 的使用场景非常多,下面列举一些常见的使用场景:

认证和授权

  • 用户认证:确保用户是系统中合法的用户,常用的认证方式包括表单登录、HTTP Basic 认证、OAuth2 等。
  • 用户授权:根据用户角色或权限,控制用户对特定资源或操作的访问。

保护 REST API

  • 保护 RESTful 服务,确保只有经过认证和授权的用户才能访问 API 端点。
  • 使用 OAuth2 或 JWT(JSON Web Token)进行令牌认证。

方法级别安全

  • 使用注解(如 @PreAuthorize、@Secured 等)在服务层或控制层的方法上进行安全控制。
  • 根据用户角色或权限动态控制方法的访问。

会话管理

  • 处理用户会话,包括会话超时、并发会话控制等。
  • 防止会话固定攻击(Session Fixation Attack)。

防止跨站请求伪造(CSRF)

  • 动生成和验证 CSRF 令牌,防止恶意网站伪造请求。

安全事件监控

  • 记录和监控安全事件,如登录尝试、登录失败、访问被拒绝等。
  • 集成日志系统,帮助分析和审计安全事件。

单点登录(SSO)

  • 实现跨多个应用程序的单点登录,使用 OAuth2、OpenID Connect 等协议。

加密和解密

  • 处理敏感数据的加密和解密,如用户密码的存储和校验。

总结

在本文中,我们介绍了 Spring Security 的基本概念以及其三大核心组件,此外,我们还解释了 Spring 提供的默认配置以及如何覆盖它们。

Spring Security 的功能很强大也很灵活性,但是入门有难度,初学者一定要抓大放小,抓住主要流程,其核心原理是通过一些列的 Servlet Filters来实现。

参考资料

Getting started with Spring Security and Spring Boot

spring-security官方文档

学习交流

如果你觉得文章有帮助,请帮忙转发给更多的好友,或关注:猿java,持续输出硬核文章。

目录
相关文章
|
23天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
15天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
20天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2572 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
18天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
3天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
2天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
159 2
|
19天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1570 16
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
21天前
|
编解码 JSON 自然语言处理
通义千问重磅开源Qwen2.5,性能超越Llama
击败Meta,阿里Qwen2.5再登全球开源大模型王座
943 14
|
3天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
187 2
|
16天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
711 10