详解springsecurity组件

在这里插入图片描述

1.架构图

在这里插入图片描述

核心组件构成

  1. Security Filter Chain
  2. AuthenticationManager
  3. Authentication Providers

架构图解释说明

1. Client Request

  • A user or client makes a request to the application, usually to access a protected resource.
  • This request is processed by a chain of Security Filters.

2. Security Filter Chain

  • Security Filter A, B, … N: These are different filters that apply to the incoming request, where each filter can handle specific types of security logic (like CORS, CSRF protection, and more).
  • One of these filters is responsible for authenticating the user.

3. Authentication Flow

  • UsernamePassword Authentication Token: This token represents the user’s credentials (username and password) and is passed to the authentication logic.
  • The authentication logic checks if the user credentials are valid. This typically involves checking a database or other identity source.

4. AuthenticationManager / ProviderManager

  • The AuthenticationManager or ProviderManager manages the overall authentication process. It delegates the authentication request to different Authentication Providers based on the type of authentication required.

5. Authentication Providers

  • JWTAuthentication Provider: If you’re using JWT (JSON Web Token) for authentication, this provider handles the verification of JWT tokens.
  • DaoAuthentication Provider: This provider handles traditional authentication using a database, checking user credentials against stored data (e.g., in a relational database).
  • Other Providers (Authentication Provider N): You can define multiple custom authentication providers if your application supports multiple methods of authentication (e.g., OAuth2, LDAP).

6. UserDetailsService

  • The UserDetailsService is responsible for loading user-specific data, typically by looking up the user’s details from a database (using the DaoAuthenticationProvider).
  • The PasswordEncoder ensures that passwords are securely encoded (hashed) before they are compared during the authentication process.

7. SecurityContext & JWT Authentication Filter

  • If the user is successfully authenticated, the SecurityContext is updated to store the user’s authentication status.
  • The JWT Authentication Filter is responsible for handling JWT tokens, ensuring that valid tokens allow access to protected resources.

8. Authentication Request/Response

  • Once the authentication is performed by the filters and providers, an Authentication Request is sent to the backend.
  • After validation, an Authentication Response is returned, which could contain the authentication token (such as a JWT), allowing the client to access secure resources in subsequent requests.

9. SecurityContextHeader

  • The SecurityContextHeader encapsulates important security information like the user’s Principal (authenticated user), Credentials (such as the password or token), and Authorities (permissions or roles).
  • These fields include:
    • getAuthorities(): Fetches the roles or permissions granted to the user.
    • getPassword(), getUsername(): Standard user details.
    • isAccountNonExpired(), isAccountNonLocked(), isCredentialsNonExpired(), isEnabled(): These are checks to ensure the user account is in good standing (not expired, locked, etc.).

2. 特性

1.认证(authentication)

认证是指我们如何验证试图访问特定资源的人的身份。

一个常见的验证用户的方法是要求用户输入用户名和密码。

一旦进行了认证,我们就知道了身份并可以执行授权。

密码存储
SpringSecurity的PasswordEncoder接口用于对密码进行单向转换,让密码安全地存储。

鉴于PasswordEncoder是一个单向转换,当密码转换需要双向时(如存储用于验证数据库的凭证),它就没有用了.

通常情况下,PasswordEncoder 用于存储在认证时需要与用户提供的密码进行比较的密码

创建用户

  //1.创建用户
        User.UserBuilder builder = User.withDefaultPasswordEncoder();
        UserDetails user = builder.username("user")
                .password("password123")
                .roles("USER")
                .build();

        UserDetails admin = builder.username("admin")
                .password("admin123")
                .roles("USER", "ADMIN")
                .build();

使用Spring Boot CLI对密码进行编码

spring encodepassword password123
{bcrypt}$2a$10$X5wFBtLrL/kHcmrOGGTrGufsBX8CJ0WpQpF3pgeuxBB/H73BK1DW6


BCryptPasswordEncoder的实现使用广泛支持的bcrypt算法对密码进行散列

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
        String result = encoder.encode("myPassword");
        System.out.println("result:"+encoder.matches("myPassword", result));

Argon2PasswordEncoder 的实现使用 Argon2 算法对密码进行散列

Argon2PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
 System.out.println(encoder.matches("myPassword", result));

Pbkdf2PasswordEncoder 的实现使用 PBKDF2 算法对密码进行散列

Pbkdf2PasswordEncoder encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
 System.out.println(encoder.matches("myPassword", result));

SCryptPasswordEncoder 的实现使用 scrypt 算法对密码进行散列


SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
System.out.println(encoder.matches("myPassword", result));

学习连接:https://springdoc.cn/spring-security/features/authentication/password-storage.html#authentication-password-storage-boot-cli

2.防范漏洞攻击

  • CSRF

  • HttpHeader

    默认的securityHeader

    Cache-Control: no-cache, no-store, max-age=0, must-revalidate
    Pragma: no-cache
    Expires: 0
    X-Content-Type-Options: nosniff
    Strict-Transport-Security: max-age=31536000 ; includeSubDomains
    X-Frame-Options: DENY
    X-XSS-Protection: 0
    

    security header 参数解释:

缓存控制(Cache Control):默认设置是禁用缓存以保护用户的内容
Content Type Options: X-Content-Type-Options: nosniff
HTTP严格传输安全 (HSTS): Strict-Transport-Security: max-age=31536000 ; includeSubDomains ; preload
HTTP 公钥锁定 (HPKP)
X-Frame-Options:解决点击劫持的一个更现代的方法是使用 X-Frame-Options 头。默认情况下,Spring Security 通过使用以下 Header 来禁止在 iframe 内渲染页面:

X-Frame-Options: DENY
X-XSS-Protection:默认情况下,Spring Security通过以下 header 来阻止该内容:

X-XSS-Protection: 0
  • Http
    1. 重定向到 HTTPS
    2. 严格的传输安全(Strict Transport Security)
    3. 代理服务器配置:SpringBoot用户使用server.use-forward-headers属性来配置应用程序

3.整合

1.加密器(Encryptor)

Encryptors类提供了构建对称加密器的工厂方法。BytesEncryptor实例,以原始 byte[]形式加密数据。TextEncryptor实例来加密文本字符串。
在这里插入图片描述

2. 密钥生成器(KeyGenerators)
  • BytesKeyGenerator

  • StringKeyGenerator

  • 密码编码
    spring-security-crypto 模块的 password 包提供对密码编码的支持。 PasswordEncoder 是中心服务接口,有以下签名:

    public interface PasswordEncoder {
    	String encode(CharSequence rawPassword);
    
    	boolean matches(CharSequence rawPassword, String encodedPassword);
    
    	default boolean upgradeEncoding(String encodedPassword) {
    		return false;
    	}
    }
    
    3.Java并发API

    Spring Security 的并发类:

    • DelegatingSecurityContextCallable
    • DelegatingSecurityContextExecutor
    • DelegatingSecurityContextExecutorService
    • DelegatingSecurityContextRunnable
    • DelegatingSecurityContextScheduledExecutorService
    • DelegatingSecurityContextSchedulingTaskExecutor
    • DelegatingSecurityContextAsyncTaskExecutor
    • DelegatingSecurityContextTaskExecutor
    • DelegatingSecurityContextTaskScheduler
4.Jackson支持
ObjectMapper mapper = new ObjectMapper();
ClassLoader loader = getClass().getClassLoader();
List<Module> modules = SecurityJackson2Modules.getModules(loader);
mapper.registerModules(modules);

// ... use ObjectMapper as normally ...
SecurityContext context = new SecurityContextImpl();
// ...
String json = mapper.writeValueAsString(context);

Spring Security 模块提供了对 Jackson 的支持。

  • spring-security-core (CoreJackson2Module)
  • spring-security-web (WebJackson2Module, WebServletJackson2Module, WebServerJackson2Module)
  • spring-security-oauth2-client (OAuth2ClientJackson2Module)
  • spring-security-cas (CasJackson2Module)

3.项目模块

1.Core — spring-security-core.jar

该模块包含核心认证和访问控制类和接口、远程支持和基本配置API.

顶级包:

  • org.springframework.security.core
  • org.springframework.security.access
  • org.springframework.security.authentication
  • org.springframework.security.provisioning

2. Remoting — spring-security-remoting.jar

提供与Spring Remoting的集成

3. Web — spring-security-web.jar

包含过滤器和相关的web安全基础设施代码

4. Config — spring-security-config.jar

包含security命名空间解析代码和Java配置代码

5.LDAP — spring-security-ldap.jar

提供LDAP认证和供应代码

6. spring-security-oauth2-core.jar

提供对OAuth 2.0 授权框架和 OpenID Connect Core 1.0 的支持,使用OAuth 2.0或OpenID Connect Core 1.0 的应用程序都需要它,如客户端、资源服务器和授权服务器;

7. OAuth 2.0 Client — spring-security-oauth2-client.jar

包含 Spring Security 对 OAuth 2.0 授权框架和 OpenID Connect Core 1.0 的客户端支持

8. spring-security-oauth2-jose.jar

包含Spring Security 对 JOSE(Javascript Object Signing and Encryption框架的支持

  • JSON Web Token (JWT)
  • JSON Web Signature (JWS)
  • JSON Web Encryption (JWE)
  • JSON Web Key (JWK)

包含以下顶级包:

  • org.springframework.security.oauth2.jwt
  • org.springframework.security.oauth2.jose

9. spring-security-oauth2-resource-server.jar

包含Spring Security 对OAuth 2.0 资源服务器的支持。它用于通过使用OAuth 2.0 Bearer Token来保护API。顶层包是 org.springframework.security.oauth2.server.resource。

10. spring-security-acl.jar

包含一个专门的 domain object ACL 实现。它用于在你的应用程序中对特定的 domain object 实例应用安全。顶层包是 org.springframework.security.acls

11. spring-security-cas.jar

包含Spring Security 的 CAS 客户端集成。想在CAS单点登录服务器上使用 Spring Security Web认证,使用它。顶层包是 org.springframework.security.cas。

12. spring-security-test.jar

该模块包含对Spring Security测试的支持。

13. spring-security-taglibs.jar

提供 Spring Security 的 JSP 标签实现。

4.spring security demo

1.架构

  • DelegatingFilterProxy

  • 在这里插入图片描述

  • FilterChainProxy:允许通过 SecurityFilterChain委托给许多 Filter 实例
    在这里插入图片描述

  • SecurityFilterChain:被FilterChainProxy用来确定当前请求应该调用哪些Spring Security Filter实例

在这里插入图片描述

在这里插入图片描述

  • Security Filter
    SecurityFilter是通过SecurityFilterChain API插入FilterChainProxy中的
    filter可以用于许多不同的目的,如认证、授权、漏洞保护等。filter是按照特定的顺序执行的,以保证它们在正确的时间被调用

  • 处理Security异常
    ExceptionTranslationFilter允许将AccessDeniedException和AuthenticationException翻译成HTTP响应。
    ExceptionTranslationFilter作为Security Filter之一被插入到FilterChainProxy中

    ExceptionTranslationFilter 与其他组件的关系:
    在这里插入图片描述

流程说明:

  1. 首先,ExceptionTranslationFilter 调用 FilterChain.doFilter(request, response) 来调用应用程序的其他部分

  2. 如果用户没有被认证或者是一个AuthenticationException那么就 开始认证
    [SecurityContextHolder 被清理掉
    HttpServletRequest 被保存起来,这样一旦认证成功,它就可以用来重放原始请求
    AuthenticationEntryPoint 用于请求客户的凭证。例如,它可以重定向到一个登录页面或发送一个 WWW-Authenticate

  3. 否则,如果是 AccessDeniedException,那么就是 Access DeniedAccessDeniedHandler 被调用来处理拒绝访问(access denied)

  • 保存认证之间的请求(RequestCache):HttpServletRequest 被保存在 RequestCache。当用户成功认证后,RequestCache 被用来重放原始请求。RequestCacheAwareFilter 就是使用RequestCache 来保存 HttpServletRequest 的。

2. 认证

认证机制
  • Username 和 Password - 用用户名/密码进行认证

  • OAuth 2.0 Login - 使用 OpenID Connect 和非标准的OAuth 2.0登录(即GitHub)的OAuth 2.0登录。

  • SAML 2.0 Login - SAML 2.0登录

  • Central Authentication Server (CAS) - 中央认证服务器(CAS)支持。

  • Remember Me - 记住一个过了session有效期的用户。

  • JAAS Authentication - 用JAAS进行认证

  • Pre-Authentication Scenarios - 使用外部机制(如 SiteMinder 或Java EE security)进行认证,但仍使用Spring Security进行授权并保护其免受常见漏洞的侵害。

  • X509 Authentication - X509认证

认证架构
  • SecurityContextHolder:是Spring Security存储认证用户细节的地方
    在这里插入图片描述

  • SecurityContext :是从 SecurityContextHolder 获得的,包含了当前认证用户的 Authentication (认证)

  • Authentication :可以是 AuthenticationManager 的输入,提供用户的认证凭证或来自 SecurityContext 的当前用户

    认证(Authentication包含了:

    principal: 识别用户。当用用户名/密码进行认证时,这通常是 UserDetails 的一个实例。

    credentials: 通常是一个密码。在许多情况下,这在用户被认证后被清除,以确保它不会被泄露。

    authorities: GrantedAuthority 实例是用户被授予的高级权限。两个例子是角色(role)和作用域(scope)

  • GrantedAuthority -:在 Authentication (认证)中授予委托人的一种权限(即role、scope等)

  • AuthenticationManager : 定义SpringSecurity 的 Filter 如何执行认证的API

  • ProviderManager -:最常见的AuthenticationManage 的实现。

  • AuthenticationProvider :由ProviderManager用于执行特定类型的认证

    在这里插入图片描述
    在这里插入图片描述

DaoAuthenticationProvider 支持基于用户名/密码的认证

JwtAuthenticationProvider 支持认证JWT令牌

  • 用AuthenticationEntryPoint 请求凭证 :用于从客户端请求凭证(即重定向到登录页面,发送 WWW-Authenticate 响应等)或用于发送一个要求客户端提供凭证的HTTP响应
  • AbstractAuthenticationProcessingFilter : 用于认证的基本 Filter,被用作验证用户凭证的基础 Filter
    AbstractAuthenticationProcessingFilter 可以对提交给它的任何认证请求进行认证:
    在这里插入图片描述

说明

  1. 当用户提交他们的凭证时,AbstractAuthenticationProcessingFilter 会从 HttpServletRequest 中创建一个要认证的Authentication。创建的认证的类型取决于 AbstractAuthenticationProcessingFilter 的子类。例如,UsernamePasswordAuthenticationFilter从 HttpServletRequest 中提交的 usernamepassword 创建一个 UsernamePasswordAuthenticationToken

  2. 接下来,Authentication 被传入AuthenticationManager,以进行认证。

  3. 如果认证失败,则为 Failure

  1. 如果认证成功,则为 Success
  • SessionAuthenticationStrategy 被通知有新的登录。参见 SessionAuthenticationStrategy 接口。
  • Authentication 是在 SecurityContextHolder 上设置的。后来,如果你需要保存 SecurityContext 以便在未来的请求中自动设置,必须显式调用 SecurityContextRepository#saveContext。参见 SecurityContextHolderFilter 类。
  • RememberMeServices.loginSuccess 被调用。如果没有配置 remember me,这就是一个无用功。请参阅 rememberme 包。
  • ApplicationEventPublisher 发布一个 InteractiveAuthenticationSuccessEvent 事件。
  • AuthenticationSuccessHandler 被调用。参见 AuthenticationSuccessHandler 接口。
用户名/密码

表单登录:UsernamePasswordAuthenticationFilter
在这里插入图片描述

1.当用户提交他们的用户名和密码时,UsernamePasswordAuthenticationFilter 通过从 HttpServletRequest 实例中提取用户名和密码,创建一个 UsernamePasswordAuthenticationToken,这是一种 Authentication 类型。

  1. 接下来,UsernamePasswordAuthenticationToken 被传入 AuthenticationManager 实例,以进行认证。AuthenticationManager 的细节取决于 用户信息的存储方式

  2. 如果认证失败,则为 Failure.

    1. SecurityContextHolder 被清空。
    2. RememberMeServices.loginFail 被调用。如果没有配置remember me,这就是一个无用功。参见Javadoc中的 RememberMeServices 接口。
    3. AuthenticationFailureHandler 被调用。参见Javadoc中的 AuthenticationFailureHandler 类。
  3. 如果认证成功,则 Success

    1. SessionAuthenticationStrategy 被通知有新的登录。参见Javadoc中的 SessionAuthenticationStrategy 接口。
    2. Authentication 被设置在 SecurityContextHolder 上。参见 Javadoc 中的 SecurityContextPersistenceFilter 类。
    3. RememberMeServices.loginSuccess 被调用。如果没有配置remember me,这就是一个无用功。参见Javadoc中的 RememberMeServices 接口。
    4. ApplicationEventPublisher 发布 InteractiveAuthenticationSuccessEvent 事件。
    5. AuthenticationSuccessHandler 被调用。通常,这是一个 SimpleUrlAuthenticationSuccessHandler,当我们重定向到登录页面时,它会重定向到由 ExceptionTranslationFilter 保存的请求。

    Basic认证:BasicAuthenticationFilter

在这里插入图片描述

摘要(Digest)认证

  • 不应该在应用中使用摘要认证,因为它被认为是不安全的

  • DigestAuthenticationFilter

  • Digest认证的核心是 “nonce”,由服务器生成的值,Spring Security的nonce采用了以下格式:

base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
expirationTime:   The date and time when the nonce expires, expressed in milliseconds
key:              A private key to prevent modification of the nonce token
  • 摘要认证demo
@Autowired
UserDetailsService userDetailsService;

DigestAuthenticationEntryPoint entryPoint() {
	DigestAuthenticationEntryPoint result = new DigestAuthenticationEntryPoint();
	result.setRealmName("My App Realm");
	result.setKey("3028472b-da34-4501-bfd8-a355c42bdf92");
}

DigestAuthenticationFilter digestAuthenticationFilter() {
	DigestAuthenticationFilter result = new DigestAuthenticationFilter();
	result.setUserDetailsService(userDetailsService);
	result.setAuthenticationEntryPoint(entryPoint());
}

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
	http
		// ...
		.exceptionHandling(e -> e.authenticationEntryPoint(authenticationEntryPoint()))
		.addFilterBefore(digestFilter());
	return http.build();
}

密码存储机制

  • 使用 内存认证 的 Simple Storage:InMemoryUserDetailsManager

  • 使用 JDBC认证 的关系型数据库
    Spring Security的 JdbcDaoImpl 实现了 UserDetailsService,以提供对基于用户名和密码的认证的支持,该认证通过使用JDBC来检索
    JdbcUserDetailsManager

  • 使用 UserDetailsService 的自定义数据存储
    UserDetails 由 UserDetailsService 返回;

    DaoAuthenticationProvider 验证 UserDetails,然后返回一个 Authentication,该 Authentication 的委托人(principal)是由配置的 UserDetailsService 返回的。

  • DaoAuthenticationProvider:使用UserDetailsService 和PasswordEncoder来验证一个用户名和密码。

在这里插入图片描述

  1. 读取用户名和密码部分的认证 FilterUsernamePasswordAuthenticationToken 传递给 AuthenticationManager,它由 ProviderManager 实现。

  2. ProviderManager 被配置为使用一个 DaoAuthenticationProvider 类型的 AuthenticationProvider

  3. DaoAuthenticationProviderUserDetailsService 中查找 UserDetails

  4. DaoAuthenticationProvider 使用 PasswordEncoder 来验证上一步返回的 UserDetails 上的密码。

  5. 当认证成功时,返回的 AuthenticationUsernamePasswordAuthenticationToken 类型,并且有一个委托人(principal)是由配置的 UserDetailsService 返回的 UserDetails。最终,返回的 UsernamePasswordAuthenticationToken 被认证 Filter 设置在 SecurityContextHolder 上。

  • 使用 LDAP认证 的 LDAP storage:
    LDAP (轻量级目录访问协议):经常被组织用作用户信息的中央存储库和认证服务。bai可以用来存储应用程序用户的角色信息

  • PasswordEncoder:对密码进行加密

持久化认证
  1. SecurityContextRepository的默认实现是DelegatingSecurityContextRepository ,它委托给:
  • HttpSessionSecurityContextRepository
  • RequestAttributeSecurityContextRepository
  1. DelegatingSecurityContextRepository:将 SecurityContext 保存到多个 SecurityContextRepository 委托(delegate),并允许按指定顺序从任何委托(delegate)中检索。

  2. SecurityContextPersistenceFilter:负责使用 SecurityContextRepository 在请求之间持久化 SecurityContext

在这里插入图片描述

解释说明:

  1. 在运行应用程序的其余部分之前,SecurityContextPersistenceFilterSecurityContextRepository 加载 SecurityContext 并将其设置在 SecurityContextHolder 上。

  2. 接下来,应用程序被运行。

  3. 最后,如果 SecurityContext 发生了变化,我们会使用 SecurityContextPersistenceRepository 保存 SecurityContext。这意味着在使用 SecurityContextPersistenceFilter 时,只要设置 SecurityContextHolder 就可以确保使用 SecurityContextRepository 来持久化 SecurityContext。

  4. SecurityContextHolderFilter : 负责使用 SecurityContextRepository 在请求之间加载 SecurityContext
    在这里插入图片描述

    解释说明:

  5. 在运行应用程序的其余部分之前,SecurityContextHolderFilter 从S ecurityContextRepository 加载 SecurityContext 并将其设置在 SecurityContextHolder 上。

  6. 接下来,应用程序被运行。

SecurityContextPersistenceFilter 不同,SecurityContextHolderFilter 只加载 SecurityContext,并不保存 SecurityContext。这意味着在使用 SecurityContextHolderFilter 时,需要明确地保存 SecurityContext

会话管理

会话组件:

  • SecurityContextHolderFilter

  • SecurityContextPersistenceFilter

  • SessionManagementFilter:将 SecurityContextRepository 的内容与 SecurityContextHolder 的当前内容进行对照,以确定用户在当前请求中是否已被认证,通常是通过非交互式认证机制,如预认证或 remember-me

    在 Spring Security 5 中,默认配置依靠 SessionManagementFilter 来检测用户是否刚刚认证,并调用 SessionAuthenticationStrategy。意味着在一个典型的设置中,必须为每个请求读取 HttpSession

    在 Spring Security 6 中,默认的是认证机制本身必须调用 SessionAuthenticationStrategy。意味着不需要检测 Authentication 何时完成,因此不需要为每个请求读取 HttpSession

自定义认证(Authentication)的存储位置:

  • 在HttpSessionSecurityContextRepository实例上调用单个setter

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) {
        SecurityContextRepository repo = new MyCustomSecurityContextRepository();
        http
            // ...
            .securityContext((context) -> context
                .securityContextRepository(repo)
            );
        return http.build();
    }
    
  • 在缓存或数据库中存储 security context,以实现横向扩展

记住我(Remember-Me)认证

通常是通过向浏览器发送一个cookie来实现的,在未来的会话中可以检测到cookie,并导致自动登录的发生;

spring Security为这些操作提供了钩子,有两个具体的“记住我”的实现:

  1. 一个使用散列法来保护基于cookie的令牌的安全性
  2. 使用数据库或其他持久性存储机制来存储生成的令牌

基于哈希简单令牌法
这种方法使用散列法来实现有用的“记住我”的策略。实质上,在成功的交互式认证后,会向浏览器发送一个cookie,cookie的组成如下:

base64(username + ":" + expirationTime + ":" + algorithmName + ":"
algorithmHex(username + ":" + expirationTime + ":" password + ":" + key))

username:          UserDetailsService 的 ID
password:          这与检索到的UserDetails中的内容相匹配。
expirationTime:    令牌过期的日期和时间,以毫秒表示。
key:               用于防止修改“记住我”令牌的私钥
algorithmName:     用于生成和验证 “记住我” 令牌签名的算法。

TokenBasedRememberMeServices:实现支持基于哈希简单令牌法中描述的更简单的方法

持久化令牌法

PersistentTokenBasedRememberMeServices

CAS 认证

单点登录认证,由客户端和服务端组成

退出

默认情况下,Spring Security 会建立一个 /logout 端点,所以不需要额外的代码

认证事件

对于每个认证的成功或失败,分别触发一个 AuthenticationSuccessEvent 或 AuthenticationFailureEvent 事件

3.授权

在确定了用户认证方式 后,还需要配置应用程序的授权规则。

1.授权架构

AuthorizationManager

在这里插入图片描述

2.授权HTTP请求

Request授权组件工作原理:

在这里插入图片描述

解释说明:

匹配请求

  1. 使用Ant进行匹配:Ant 是 Spring Security 用来匹配请求的默认语言
  2. 正则表达式匹配请求
  3. HTTP方法来匹配
  4. 按Dispatcher类型进行匹配

授权请求

一旦一个请求被匹配,可以用已经看到的几种方式来授权它,如 permitAlldenyAllhasAuthority

以下是DSL内置的授权规则:

  • permitAll - 该请求不需要授权,是一个公共端点;请注意,在这种情况下,永远不会从 session 中检索 Authentication
  • denyAll - 该请求在任何情况下都是不允许的;注意在这种情况下,永远不会从 session 中检索 Authentication
  • hasAuthority - 该请求要求 Authentication 有一个符合给定值的 GrantedAuthority
  • hasRole - hasAuthority 的一个快捷方式,其前缀为 ROLE_ 或任何被配置为默认的前缀。
  • hasAnyAuthority - 该请求要求 Authentication 有一个符合任何给定值的 GrantedAuthority
  • hasAnyRole - hasAnyAuthority 的一个快捷方式,其前缀为 ROLE_ 或任何被配置为默认的前缀。
  • access - 该请求使用这个自定义的 AuthorizationManager 来确定访问权限。

用SpEL表示授权

使用授权表达式的字段和方法

  • permitAll - 该请求不需要授权即可调用;注意,在这种情况下,将不会从 session 中检索 Authentication
  • denyAll - 该请求在任何情况下都是不允许的;注意在这种情况下,永远不会从会话中检索 Authentication
  • hasAuthority - 请求要求 AuthenticationGrantedAuthority 符合给定值。
  • hasRole - hasAuthority 的快捷方式,前缀为 ROLE_ 或任何配置为默认前缀的内容。
  • hasAnyAuthority - 请求要 Authentication 具有符合任何给定值的 GrantedAuthority
  • hasAnyRole - hasAnyAuthority 的一个快捷方式,其前缀为 ROLE_ 或任何被配置为默认的前缀。
  • hasPermission - 用于对象级授权的 PermissionEvaluator 实例的 hook。

这里是对最常见字段的简要介绍:

  • authentication - 与此方法调用相关的 Authentication 实例。
  • principal - 与此方法调用相关的 Authentication#getPrincipal

Authorize Requests Using SpEL

<http>
    <intercept-url pattern="/static/**" access="permitAll"/>
    <intercept-url pattern="/admin/**" access="hasRole('ADMIN')"/>
    <intercept-url pattern="/db/**" access="hasAuthority('db') and hasRole('ADMIN')"/>
    <intercept-url pattern="/**" access="denyAll"/>
</http>

Security Matchers

  • RequestMatcher 接口用于确定请求是否符合给定的规则
  • RegexRequestMatcher
  • 如果classpath中包含Spring MVC,使用MvcRequestMatcher,否则,使用AntPathRequestMatcher

自定义RequestMatcher实现

import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
import static org.springframework.security.web.util.matcher.RegexRequestMatcher.regexMatcher;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			.securityMatcher(antMatcher("/api/**"))
			.authorizeHttpRequests(authorize -> authorize
				.requestMatchers(antMatcher("/user/**")).hasRole("USER")
				.requestMatchers(regexMatcher("/admin/.*")).hasRole("ADMIN")
				.requestMatchers(new MyCustomRequestMatcher()).hasRole("SUPERVISOR")
				.anyRequest().authenticated()
			)
			.formLogin(withDefaults());
		return http.build();
	}
}

public class MyCustomRequestMatcher implements RequestMatcher {

    @Override
    public boolean matches(HttpServletRequest request) {
        // ...
    }
}

3.方法安全(Method Security)

  • 通过在任何 @Configuration 类中注解 @EnableMethodSecurity 或在任何 XML 配置文件中添加 <method-security> 来激活方法授权;

  • 方法授权是方法“前”授权和方法“后”授权的结合

    @Service
    public class MyCustomerService {
        @PreAuthorize("hasAuthority('permission:read')")
        @PostAuthorize("returnObject.owner == authentication.name")
        public Customer readCustomer(String id) { ... }
    }
    

    当方法安全被激活时,对 MyCustomerService#readCustomer 的给定调用可能如下:

    在这里插入图片描述

解释说明:

  1. Spring AOP 为 readCustomer 调用了代理方法。在代理的其他 advice 中,它调用了一个 AuthorizationManagerBeforeMethodInterceptor,该方法与 @PreAuthorize pointcut 匹配。
  2. 拦截器调用 PreAuthorizeAuthorizationManager#check
  3. 授权管理器使用 MethodSecurityExpressionHandler 解析注解的 SpEL 表达式,并从包含 SupplierMethodInvocationMethodSecurityExpressionRoot 构建相应的 EvaluationContext
  4. 拦截器使用该上下文来评估表达式;具体地说,它从 Supplier 读取 Authentication,并检查其 权限 集合中是否有 permission:read
  5. 如果评估通过,Spring AOP 将继续调用该方法。
  6. 如果没有,拦截器会发布一个 AuthorizationDeniedEvent,并抛出一个 AccessDeniedExceptionExceptionTranslationFilter 会捕获并向响应返回一个403状态码。
  7. 在方法返回后,Spring AOP 调用一个与 @PostAuthorize pointcut 相匹配的 AuthorizationManagerAfterMethodInterceptor,操作与上面相同,但使用了 PostAuthorizeAuthorizationManager
  8. 如果评估通过(在这种情况下,返回值属于登录的用户),处理继续正常进行。
  9. 如果没有,拦截器会发布一个 AuthorizationDeniedEvent,并抛出一个 AccessDeniedExceptionExceptionTranslationFilter 会捕获并向响应返回一个 403 状态码。
  • 多个注解串联计算
    要对调用进行授权,所有注解检查都需要通过授权
  • 不支持重复注解
    不支持在同一个方法上重复相同的注解; 如:能在同一个方法上重复 @PreAuthorize
  • 每个注解都有自己的方法拦截器

方法拦截器如下:

     1. 对于 @PreAuthorize,Spring Security 使用 AuthenticationManagerBeforeMethodInterceptor#preAuthorize,而它又使用 PreAuthorizeAuthorizationManager
     1. 对于 @PostAuthorize,Spring Security 使用 AuthenticationManagerAfterMethodInterceptor#postAuthorize,而它又使用 PostAuthorizeAuthorizationManager
     1. 对于 @PreFilter,Spring Security 使用 PreFilterAuthorizationMethodInterceptor
     1. 对于 @PostFilter,Spring Security 使用 PostFilterAuthorizationMethodInterceptor
     1. 对于 @Secured,Spring Security 使用 AuthenticationManagerBeforeMethodInterceptor#secured,而它又使用 SecuredAuthorizationManager
     1. 对于 JSR-250 注解,Spring Security 使用 AuthenticationManagerBeforeMethodInterceptor#jsr250,而它又使用 Jsr250AuthorizationManager

一般来说,当你添加 @EnableMethodSecurity 时,将下面的列表视为 Spring Security 发布的拦截器的代表:

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static Advisor preAuthorizeMethodInterceptor() {
    return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
}

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static Advisor postAuthorizeMethodInterceptor() {
    return AuthorizationManagerAfterMethodInterceptor.postAuthorize();
}

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static Advisor preFilterMethodInterceptor() {
    return AuthorizationManagerBeforeMethodInterceptor.preFilter();
}

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static Advisor postFilterMethodInterceptor() {
    return AuthorizationManagerAfterMethodInterceptor.postFilter();
}

  • 将授权优先于复杂的SpEL表达式
@PreAuthorize("hasAuthority('permission:read') || hasRole('ADMIN')")
  • 请求级授权与方法级授权的比较:

    请求级方法级
    授权类型粗粒度细粒度
    配置位置在配置类中声明局部到方法声明
    配置方式DSL注解
    授权的定义编程式SpEL
  • 使用注解授权
    Spring Security 实现方法级授权支持的主要方式是通过注解,可以将注解添加到方法、类和接口中.
    1. 使用 @PreAuthorize 授权方法调用

    @Component
    public class BankService {
    	@PreAuthorize("hasRole('ADMIN')")
    	public Account readAccount(Long id) {
            // ... is only invoked if the `Authentication` has the `ROLE_ADMIN` authority
    	}
    }
    

    2.使用@PostAuthorize 的授权方法结果

    @Component
    public class BankService {
    	@PostAuthorize("returnObject.owner == authentication.name")
    	public Account readAccount(Long id) {
            // ... is only returned if the `Account` belongs to the logged in user
    	}
    }
    

    3.使用@PreFilter过滤方法参数
    @PreFilter 支持数组、集合、map 和 stream

    @Component
    public class BankService {
    	@PreFilter("filterObject.owner == authentication.name")
    public Collection<Account> updateAccounts(Account[] accounts)
    
    @PreFilter("filterObject.owner == authentication.name")
    public Collection<Account> updateAccounts(Collection<Account> accounts)
    
    @PreFilter("filterObject.value.owner == authentication.name")
    public Collection<Account> updateAccounts(Map<String, Account> accounts)
    
    @PreFilter("filterObject.owner == authentication.name")
    public Collection<Account> updateAccounts(Stream<Account> accounts)
    
    }
    

    4. 使用 @PostFilter 过滤方法结果
    @PostFilter 支持数组、集合、map 和 stream(只要 stream 仍然打开)。

    @Component
    public class BankService {
    	@PostFilter("filterObject.owner == authentication.name")
    	public Collection<Account> readAccounts(String... ids) {
            // ... the return value will be filtered to only contain the accounts owned by the logged-in user
            return accounts;
    	}
    }
    

    5. 使用 @Secured 授权方法调用
    @Secured 是授权调用的传统选项。推荐使用 @PreAuthorize 来取代它

    @EnableMethodSecurity(securedEnabled = true)
    

    6. 在类或接口级声明注解

    类级别

    @Controller
    @PreAuthorize("hasAuthority('ROLE_USER')")
    public class MyController {
        @GetMapping("/endpoint")
        public String endpoint() { ... }
    }
    

    类和方法级别

    @Controller
    @PreAuthorize("hasAuthority('ROLE_USER')")
    public class MyController {
        @GetMapping("/endpoint")
        @PreAuthorize("hasAuthority('ROLE_ADMIN')")
        public String endpoint() { ... }
    }
    

    上面实现中:声明注解的方法覆盖类级注解

4.域对象(Domain Object)安全(ACL

Spring Security为应用程序提供了三个主要的ACL相关功能:

  • 一种有效检索所有域对象的ACL条目的方法(以及修改这些ACL)
  • 在方法被调用之前,确保某个委托人(principal)被允许使用你的对象的一种方法
  • 一种确保特定委托人(principal)在方法被调用后被允许处理你的对象(或它们返回的东西)的方式

5.授权事件

对于每个被拒绝的授权,都会触发一个 AuthorizationDeniedEvent 事件。对于被授予的授权,也有可能触发 AuthorizationGrantedEvent 事件

  • AuthorizationEventPublisher

发布授权事件
1.使用Spring的 ApplicationEventPublisher 来发布授权事件

@Bean
public AuthorizationEventPublisher authorizationEventPublisher
        (ApplicationEventPublisher applicationEventPublisher) {
    return new SpringAuthorizationEventPublisher(applicationEventPublisher);
}

2.使用Spring的 @EventListener 发布授权事件

@Component
public class AuthenticationEvents {

    @EventListener
    public void onFailure(AuthorizationDeniedEvent failure) {
		// ...
    }
}

授权批准的事件

自定义事件发布器实现:

@Component
public class MyAuthorizationEventPublisher implements AuthorizationEventPublisher {
    private final ApplicationEventPublisher publisher;
    private final AuthorizationEventPublisher delegate;

    public MyAuthorizationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
        this.delegate = new SpringAuthorizationEventPublisher(publisher);
    }

    @Override
    public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication,
            T object, AuthorizationDecision decision) {
        if (decision == null) {
            return;
        }
        if (!decision.isGranted()) {
            this.delegate.publishAuthorizationEvent(authentication, object, decision);
            return;
        }
        if (shouldThisEventBePublished(decision)) {
            AuthorizationGrantedEvent granted = new AuthorizationGrantedEvent(
                    authentication, object, decision);
            this.publisher.publishEvent(granted);
        }
    }

    private boolean shouldThisEventBePublished(AuthorizationDecision decision) {
        if (!(decision instanceof AuthorityAuthorizationDecision)) {
            return false;
        }
        Collection<GrantedAuthority> authorities = ((AuthorityAuthorizationDecision) decision).getAuthorities();
        for (GrantedAuthority authority : authorities) {
            if ("ROLE_ADMIN".equals(authority.getAuthority())) {
                return true;
            }
        }
        return false;
    }
}

4.spring security代码demo

  1. 引入依赖

          <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-bootstrap</artifactId>
            </dependency>
    
    		   <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
    
  2. 登录校验流程

    在这里插入图片描述

  3. springsecurity完成流程

在这里插入图片描述

  • UsernamePasswordAuthenticationFilter:负责处理登录页面填写了用户名和密码后的请求
  • ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException
  • FilterSecurityInterceptor:负责权限校验的过滤器

通过debug查看当前系统中SpringSecurity过滤器连中有哪些过滤器以及他们的顺序:
在这里插入图片描述

  1. 认证流程

    在这里插入图片描述

  • Authentication接口:表示当前访问系统的用户,封装了用户相关信息
  • AuthenticationManager接口:定义了认证Authentication的方法
  • UserDetailsService接口:加载用户特定数据的核心接口。定义了根据用户名查询用户信息的方法
  • UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetials对象返回。然后将这些信息封装到Authentication对象中.

参考资料

  • security: https://www.bilibili.com/video/BV1mm4y1X7Hc?spm_id_from=333.788.player.switch&vd_source=51c993d69874e13d552c1b91dbf90aec&p=3
  • https://blog.csdn.net/weixin_43847283/article/details/124075302
  • https://blog.csdn.net/m0_74436895/article/details/140998480
  • spring security中文文档:https://springdoc.cn/spring-security/servlet/authentication/architecture.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值