嗨,你好呀,我是猿java
Spring Security 是一个帮助保护企业应用程序的框架,通过与 Spring MVC、Spring Webflux 或 Spring Boot 集成,创建一个功能强大且高度可定制的身份验证和访问控制框架。在本文中,我们将解释核心概念,并仔细研究 Spring Security 提供的默认配置以及它们的工作原理。
什么是Spring Security? {#什么是Spring-Security?}
Spring Security 是一个用于保护Spring应用程序的框架,它提供了全面的安全服务,包括身份验证、授权、加密、以及防止常见攻击(如CSRF和会话固定攻击)。Spring Security 的设计使其易于扩展,可以根据特定需求进行高度定制。
Spring Security的核心组件 {#Spring-Security的核心组件}
要了解 Spring Security 默认配置的工作原理,我们首先需要了解其三大核心组件:
- Servlet Filters
- Authentication
- Authorization
Servlet Filters {#Servlet-Filters}
当 DefaultSecurityFilterChain 请求到达 DispatcherServlet 之前触发了一连串过滤器。DispatcherServlet 是 Web 框架中的一个关键组件,用于处理传入的 Web 请求并将其分派给适当的处理程序进行处理。
为了理解 FilterChain 是如何工作的,让我们看一下 Spring Security 文档中的流程图:
接下来,让我们看一下参与 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}
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 文档中定义的身份验证架构图:
ProviderManager 是 AuthenticationManager 最常见的实现,如图所示,ProviderManager 将请求委托给已配置的 AuthenticationProvider 列表,查询每个 AuthenticationProvider 以查看它是否可以执行身份验证。如果身份验证失败并显示 ProviderNotFoundException(一种特殊类型的 AuthenticationException),则表明 ProviderManager 不支持传递的身份验证类型。此体系结构允许我们在同一应用程序中配置多种身份验证类型。
AuthenticationEntryPoint 是一个接口,它充当身份验证的入口点,用于确定客户端在请求资源时是否包含了有效的凭据,否则,将使用接口的适当实现从客户端请求凭据。
那么,Authentication 对象是如何绑定整个身份验证过程?身份验证接口用于以下用途:
- 向 AuthenticationManager 提供用户凭据。
- 表示 SecurityContext 中当前经过身份验证的用户。每个身份验证实例都必须包含
- principal - 这是标识用户的 UserDetails 实例。
- credentials
- authorities - GrantedAuthority GrantedAuthority 的实例在授权过程中起着重要作用。
Authorization {#Authorization}
Authorization(授权)是确保访问资源的用户或系统具有有效权限的过程。
在 Spring 安全过滤器链中,FilterSecurityInterceptor 触发授权检查。从过滤器执行的顺序可以看出,身份验证在授权之前运行。在用户成功通过身份验证后,此筛选器将检查有效权限。如果授权失败,将引发 AccessDeniedException。
授予权限 {#授予权限}
如上一部分所示,每个用户实例都包含一个 GrantedAuthority(授予权限)对象的列表。GrantedAuthority 是一个具有单一方法的接口:
|---------------|-------------------------------------------------------------------------------------------|
| 1 2 3
| 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 为例:
|-----------------|--------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
|
接着,编写一个测试的 Controller,如下:
|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10
| @RestController @RequestMapping public class TestController { @GetMapping("/login") public String login(){ // 业务逻辑 return "success"; } }
|
最后,使用浏览器访问地址:127.0.0.1:8080/login,会弹出一个登录页面,如下图:
通过上面 3步,我们在没有写一行前端代码的前提下实现了一个登录功能,默认情况下 Spring Security 会生成默认的密钥,我们也可以在 application.yml 中配置用户名和秘密,配置如下:
|-------------------|-----------------------------------------------------------|
| 1 2 3 4 5
| spring: security: user: name: test password: test
|
上述示例,我们展示了 Spring Security 最简单的一个使用,另外它还支持自定义配置,基于 Java 的配置是 Spring Security推荐的配置方式。通过扩展 WebSecurityConfigurerAdapter,可以覆盖 configure方法来配置认证和授权规则,如下示例:
|------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @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使用场景}
Spring Security 的使用场景非常多,下面列举一些常见的使用场景:
认证和授权 {#认证和授权}
- 用户认证:确保用户是系统中合法的用户,常用的认证方式包括表单登录、HTTP Basic 认证、OAuth2 等。
- 用户授权:根据用户角色或权限,控制用户对特定资源或操作的访问。
保护 REST API {#保护-REST-API}
- 保护 RESTful 服务,确保只有经过认证和授权的用户才能访问 API 端点。
- 使用 OAuth2 或 JWT(JSON Web Token)进行令牌认证。
方法级别安全 {#方法级别安全}
- 使用注解(如 @PreAuthorize、@Secured 等)在服务层或控制层的方法上进行安全控制。
- 根据用户角色或权限动态控制方法的访问。
会话管理 {#会话管理}
- 处理用户会话,包括会话超时、并发会话控制等。
- 防止会话固定攻击(Session Fixation Attack)。
防止跨站请求伪造(CSRF) {#防止跨站请求伪造(CSRF)}
- 动生成和验证 CSRF 令牌,防止恶意网站伪造请求。
安全事件监控 {#安全事件监控}
- 记录和监控安全事件,如登录尝试、登录失败、访问被拒绝等。
- 集成日志系统,帮助分析和审计安全事件。
单点登录(SSO) {#单点登录(SSO)}
- 实现跨多个应用程序的单点登录,使用 OAuth2、OpenID Connect 等协议。
加密和解密 {#加密和解密}
- 处理敏感数据的加密和解密,如用户密码的存储和校验。
总结 {#总结}
在本文中,我们介绍了 Spring Security 的基本概念以及其三大核心组件,此外,我们还解释了 Spring 提供的默认配置以及如何覆盖它们。
Spring Security 的功能很强大也很灵活性,但是入门有难度,初学者一定要抓大放小,抓住主要流程,其核心原理是通过一些列的 Servlet Filters来实现。
参考资料 {#参考资料}
Getting started with Spring Security and Spring Boot