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,持续输出硬核文章。

目录
相关文章
|
安全 前端开发 Java
Spring Security系列教程25--解决Spring Security环境中的跨域问题
前言 上一章节中,一一哥 给各位讲解了同源策略和跨域问题,以及跨域问题的解决方案,在本篇文章中,我会带大家进行代码实现,看看在Spring Security环境中如何解决跨域问题。 一. 启用Spring Security 的CORS支持 1. 创建web接口 我先在SpringBoot环境中,创建一个端口号为8080的web项目,注意这个web项目没有引入Spring Security的依赖包。然后在其中创建一个IndexController,定义两个测试接口以便被ajax进行跨域访问。 @RestController public class IndexController {
1146 1
|
6月前
|
存储 安全 Java
Spring Security 的TokenStore三种实现方式
Spring Security 的TokenStore三种实现方式
|
6月前
|
安全 Java 数据安全/隐私保护
【Spring Security】Spring Security 认证过程源码分析
【Spring Security】Spring Security 认证过程源码分析
76 0
|
6月前
|
安全 Java Spring
springboot整合spring security 安全认证框架
springboot整合spring security 安全认证框架
102 0
|
安全 Java 数据库
Spring Security的简单介绍与案例
# 引言 Spring Security是一个功能强大的安全框架,旨在为Java应用程序提供身份验证和授权功能。它可以很容易地整合到Spring应用程序中,并支持多种身份验证方法,包括基于表单的身份验证、基于HTTP的身份验证以及基于OAuth 2.0的身份验证。 # 依赖 ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ``` # 第一个例子 要在Spring应用
|
移动开发 安全 Java
Spring Security 配置多WebSecurityConfigurerAdapter
Spring Security 配置多WebSecurityConfigurerAdapter
2284 0
|
安全 Java 测试技术
Spring Security系列教程02--初识Spring Security
在开始学习Spring Security系列教程之前,一一哥 先带大家来了解一下Spring Security,看看它到底是什么,有哪些功能,有什么特点,以及它与别的同类框架相比,有什么不同,我们以后学习任何一个新技术,其实都可以遵循"3W1H"法则,这样学习起来才能更有条理。 首先请各位跟着 一一哥 来了解一下 Spring Security的概念,我们得先知道要学习的是个什么东西,以后我们出去面试的时候,面试官可能会问你,"请你介绍一下Spring Security",那么答案其实就是这个概念了。
264 1
|
安全 网络协议 Java
Spring Security 最佳实践,看了必懂!
Spring Security 最佳实践,看了必懂!
439 0
Spring Security 最佳实践,看了必懂!
|
安全 Java 数据库
Spring Security-项目和UserDetailsService详解
Spring Security-项目和UserDetailsService详解
Spring Security-项目和UserDetailsService详解
|
安全 Java 数据库
Spring Security系列教程23--Spring Security的四种权限控制方式
前言 在前面的章节中,一一哥 已经给大家介绍了Spring Security的很多功能,在这些众多功能中,我们知道其核心功能其实就是 认证+授权。在前面我们分别基于内存模型、基于默认的数据库模型、基于自定义数据库模型实现了认证和授权功能,但是不管哪种方式,我们对某个接口的拦截限制,都是通过编写一个SecurityConfig配置类,在该类的configure(HttpSecurity http)方法中,通过http.authorizeRequests().antMatchers("/admin/**")...这样的代码进行的权限控制。 这种权限控制方法虽然也可以实现对某些接口的拦截或放行,但
3670 0