文章目录
- 1.架构图
- 2. 特性
- 3.项目模块
- 1.Core — spring-security-core.jar
- 2. Remoting — spring-security-remoting.jar
- 3. Web — spring-security-web.jar
- 4. Config — spring-security-config.jar
- 5.LDAP — spring-security-ldap.jar
- 6. spring-security-oauth2-core.jar
- 7. OAuth 2.0 Client — spring-security-oauth2-client.jar
- 8. spring-security-oauth2-jose.jar
- 9. spring-security-oauth2-resource-server.jar
- 10. spring-security-acl.jar
- 11. spring-security-cas.jar
- 12. spring-security-test.jar
- 13. spring-security-taglibs.jar
- 4.spring security demo
- 3.授权
- 4.spring security代码demo
- 参考资料
1.架构图
核心组件构成
- Security Filter Chain
- AuthenticationManager
- 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
- 重定向到 HTTPS
- 严格的传输安全(Strict Transport Security)
- 代理服务器配置: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
与其他组件的关系:
流程说明:
-
首先,
ExceptionTranslationFilter
调用FilterChain.doFilter(request, response)
来调用应用程序的其他部分 -
如果用户没有被认证或者是一个
AuthenticationException
那么就 开始认证
[SecurityContextHolder 被清理掉
HttpServletRequest
被保存起来,这样一旦认证成功,它就可以用来重放原始请求
AuthenticationEntryPoint
用于请求客户的凭证。例如,它可以重定向到一个登录页面或发送一个WWW-Authenticate
头 -
否则,如果是
AccessDeniedException
,那么就是 Access Denied。AccessDeniedHandler
被调用来处理拒绝访问(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 可以对提交给它的任何认证请求进行认证:
说明
-
当用户提交他们的凭证时,
AbstractAuthenticationProcessingFilter
会从HttpServletRequest
中创建一个要认证的Authentication
。创建的认证的类型取决于AbstractAuthenticationProcessingFilter
的子类。例如,UsernamePasswordAuthenticationFilter从HttpServletRequest
中提交的 username 和 password 创建一个UsernamePasswordAuthenticationToken
。 -
接下来,Authentication 被传入AuthenticationManager,以进行认证。
-
如果认证失败,则为 Failure。
- SecurityContextHolder 被清空。
RememberMeServices.loginFail
被调用。如果没有配置记住我(remember me),这就是一个无用功。请参阅rememberme
包。AuthenticationFailureHandler
被调用。参见AuthenticationFailureHandler
接口。
- 如果认证成功,则为 Success。
SessionAuthenticationStrategy
被通知有新的登录。参见SessionAuthenticationStrategy
接口。- Authentication 是在 SecurityContextHolder 上设置的。后来,如果你需要保存
SecurityContext
以便在未来的请求中自动设置,必须显式调用SecurityContextRepository#saveContext
。参见SecurityContextHolderFilter
类。 RememberMeServices.loginSuccess
被调用。如果没有配置 remember me,这就是一个无用功。请参阅rememberme
包。ApplicationEventPublisher
发布一个InteractiveAuthenticationSuccessEvent
事件。AuthenticationSuccessHandler
被调用。参见AuthenticationSuccessHandler
接口。
用户名/密码
表单登录:UsernamePasswordAuthenticationFilter
1.当用户提交他们的用户名和密码时,UsernamePasswordAuthenticationFilter
通过从 HttpServletRequest
实例中提取用户名和密码,创建一个 UsernamePasswordAuthenticationToken
,这是一种 Authentication
类型。
-
接下来,
UsernamePasswordAuthenticationToken
被传入AuthenticationManager
实例,以进行认证。AuthenticationManager
的细节取决于 用户信息的存储方式。 -
如果认证失败,则为 Failure.
- SecurityContextHolder 被清空。
RememberMeServices.loginFail
被调用。如果没有配置remember me,这就是一个无用功。参见Javadoc中的RememberMeServices
接口。AuthenticationFailureHandler
被调用。参见Javadoc中的AuthenticationFailureHandler
类。
-
如果认证成功,则 Success。
SessionAuthenticationStrategy
被通知有新的登录。参见Javadoc中的SessionAuthenticationStrategy
接口。- Authentication 被设置在 SecurityContextHolder 上。参见 Javadoc 中的
SecurityContextPersistenceFilter
类。 RememberMeServices.loginSuccess
被调用。如果没有配置remember me,这就是一个无用功。参见Javadoc中的RememberMeServices
接口。ApplicationEventPublisher
发布InteractiveAuthenticationSuccessEvent
事件。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来验证一个用户名和密码。
-
读取用户名和密码部分的认证
Filter
将UsernamePasswordAuthenticationToken
传递给AuthenticationManager
,它由ProviderManager
实现。 -
ProviderManager
被配置为使用一个DaoAuthenticationProvider
类型的 AuthenticationProvider。 -
DaoAuthenticationProvider
从UserDetailsService
中查找UserDetails
。 -
DaoAuthenticationProvider
使用PasswordEncoder
来验证上一步返回的UserDetails
上的密码。 -
当认证成功时,返回的
Authentication
是UsernamePasswordAuthenticationToken
类型,并且有一个委托人(principal)是由配置的UserDetailsService
返回的UserDetails
。最终,返回的UsernamePasswordAuthenticationToken
被认证 Filter 设置在SecurityContextHolder
上。
-
使用 LDAP认证 的 LDAP storage:
LDAP (轻量级目录访问协议):经常被组织用作用户信息的中央存储库和认证服务。bai可以用来存储应用程序用户的角色信息 -
PasswordEncoder:对密码进行加密
持久化认证
- SecurityContextRepository的默认实现是DelegatingSecurityContextRepository ,它委托给:
- HttpSessionSecurityContextRepository
- RequestAttributeSecurityContextRepository
-
DelegatingSecurityContextRepository:将
SecurityContext
保存到多个SecurityContextRepository
委托(delegate),并允许按指定顺序从任何委托(delegate)中检索。 -
SecurityContextPersistenceFilter:负责使用 SecurityContextRepository 在请求之间持久化 SecurityContext
解释说明:
-
在运行应用程序的其余部分之前,
SecurityContextPersistenceFilter
从SecurityContextRepository
加载SecurityContext
并将其设置在SecurityContextHolder
上。 -
接下来,应用程序被运行。
-
最后,如果
SecurityContext
发生了变化,我们会使用SecurityContextPersistenceRepository
保存SecurityContext
。这意味着在使用SecurityContextPersistenceFilter
时,只要设置SecurityContextHolder
就可以确保使用SecurityContextRepository
来持久化SecurityContext。
-
SecurityContextHolderFilter : 负责使用 SecurityContextRepository 在请求之间加载 SecurityContext
解释说明:
-
在运行应用程序的其余部分之前,
SecurityContextHolderFilter
从SecurityContextRepository
加载SecurityContext
并将其设置在SecurityContextHolder
上。 -
接下来,应用程序被运行。
与 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为这些操作提供了钩子,有两个具体的“记住我”的实现:
- 一个使用散列法来保护基于cookie的令牌的安全性
- 使用数据库或其他持久性存储机制来存储生成的令牌
基于哈希简单令牌法
这种方法使用散列法来实现有用的“记住我”的策略。实质上,在成功的交互式认证后,会向浏览器发送一个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授权组件工作原理:
解释说明:
-
- 首先,
AuthorizationFilter
构造一个Supplier
,从 SecurityContextHolder 中检索一个 Authentication。
- 首先,
-
- 其次,它将
Supplier<Authentication>
和HttpServletRequest
传递给AuthorizationManager
。AuthorizationManager
将请求与authorizeHttpRequests
中的模式相匹配,并运行相应的规则。
- 其次,它将
-
- 如果授权被拒绝,会发布一个
AuthorizationDeniedEvent
,并抛出一个AccessDeniedException
。在这种情况下,ExceptionTranslationFilter
会处理AccessDeniedException
。
- 如果授权被拒绝,会发布一个
-
- 如果访问被授权,就会 发布一个
AuthorizationGrantedEvent
,AuthorizationFilter
继续进行 FilterChain,允许应用程序正常处理。
- 如果访问被授权,就会 发布一个
匹配请求
- 使用Ant进行匹配:Ant 是 Spring Security 用来匹配请求的默认语言
- 正则表达式匹配请求
- HTTP方法来匹配
- 按Dispatcher类型进行匹配
授权请求
一旦一个请求被匹配,可以用已经看到的几种方式来授权它,如 permitAll
、denyAll
和 hasAuthority
以下是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
- 请求要求Authentication
的GrantedAuthority
符合给定值。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 的给定调用可能如下:
解释说明:
- Spring AOP 为
readCustomer
调用了代理方法。在代理的其他 advice 中,它调用了一个AuthorizationManagerBeforeMethodInterceptor
,该方法与@PreAuthorize
pointcut 匹配。 - 拦截器调用
PreAuthorizeAuthorizationManager#check
。 - 授权管理器使用
MethodSecurityExpressionHandler
解析注解的 SpEL 表达式,并从包含Supplier
和MethodInvocation
的MethodSecurityExpressionRoot
构建相应的EvaluationContext
。 - 拦截器使用该上下文来评估表达式;具体地说,它从
Supplier
读取 Authentication,并检查其 权限 集合中是否有permission:read
。 - 如果评估通过,Spring AOP 将继续调用该方法。
- 如果没有,拦截器会发布一个
AuthorizationDeniedEvent
,并抛出一个AccessDeniedException
,ExceptionTranslationFilter
会捕获并向响应返回一个403状态码。 - 在方法返回后,Spring AOP 调用一个与
@PostAuthorize
pointcut 相匹配的AuthorizationManagerAfterMethodInterceptor
,操作与上面相同,但使用了PostAuthorizeAuthorizationManager
。 - 如果评估通过(在这种情况下,返回值属于登录的用户),处理继续正常进行。
- 如果没有,拦截器会发布一个
AuthorizationDeniedEvent
,并抛出一个AccessDeniedException
,ExceptionTranslationFilter
会捕获并向响应返回一个 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
-
引入依赖
<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>
-
登录校验流程
-
springsecurity完成流程
- UsernamePasswordAuthenticationFilter:负责处理登录页面填写了用户名和密码后的请求
- ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException
- FilterSecurityInterceptor:负责权限校验的过滤器
通过debug查看当前系统中SpringSecurity过滤器连中有哪些过滤器以及他们的顺序:
-
认证流程
- 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