type
status
date
slug
summary
tags
category
icon
password
概览
Spring Security的核心功能主要包括身份验证(Authentication)和授权(Authorization)。
- 身份验证(Authentication): Spring Security 支持多种身份验证方式,如基于表单、HTTP基本认证、LDAP认证等。开发者可以通过配置或自定义实现来满足应用程序的身份验证需求。
- 授权(Authorization): Spring Security 的授权机制允许开发者定义谁可以访问应用程序的哪些部分或资源。这可以通过注解或配置文件来实现,确保只有经过授权的用户可以执行特定的操作或访问特定的资源。
这两个核心功能协同工作,确保应用程序在访问控制方面具有高度的灵活性和安全性。
通过将身份验证和授权集成到应用程序中,Spring Security 帮助开发者处理安全性方面的复杂性,提供一致且可扩展的解决方案。
安装
在Spring Boot项目中集成Spring Security通常是相对简单的。
添加依赖:
基本配置
- 启用: 通过
@EnableWebSecurity
注解,指定了应用程序的Web安全性配置。
- 过滤器链:
filterChain
方法通过HttpSecurity
对象定义了安全过滤器链。具体配置包括: - CSRF防护:
http.csrf(Customizer.withDefaults())
配置启用了CSRF防护 - 请求授权:
authorizeHttpRequests
配置,任何请求都要求经过身份验证 - HTTP基本认证:
httpBasic
配置启用HTTP基本认证,添加了 BasicAuthenticationFilter。 - 表单登录:
formLogin
配置启用表单登录,添加了UsernamePasswordAuthenticationFilter。
过滤器
Spring Security 提供了一系列的过滤器实现不同的开箱即用的安全功能,这些过滤器在请求处理过程中按照特定的顺序进行执行,与书写顺序无关。
FilterOrderRegistration
的源码中定义了这些过滤器的默认顺序。以下是一些在Spring Security中常用的核心过滤器:UsernamePasswordAuthenticationFilter
:处理基于用户名和密码的身份验证。当用户提交登录表单时,该过滤器会拦截请求,尝试进行身份验证。
BasicAuthenticationFilter
:处理基本的HTTP身份验证。如果客户端通过HTTP基本认证发送凭证,该过滤器将尝试验证这些凭证。
ExceptionTranslationFilter
:处理安全性异常,将它们转换为适当的HTTP响应。例如,当访问被拒绝时,该过滤器负责生成相应的错误页面或重定向。
AuthorizationFilter
: Spring Security的核心授权过滤器,负责决定是否允许访问受保护的资源。它基于访问控制规则,检查当前用户是否有足够的权限。
CsrfFilter
:处理跨站请求伪造(CSRF)攻击的过滤器。它负责在请求中添加和验证CSRF令牌,以确保请求是合法的。
这些过滤器一起构成了Spring Security的核心安全性机制。开发者可以通过配置和定制这些过滤器,以满足其应用程序特定的安全性需求。
核心
Spring Securiy 的基本配置都是通过自定义
SecurityFilterChain
Bean来实现。SecurityFilterChain
SecurityFilterChain
的调用在 Spring Security 中由 FilterChainProxy
决定,用于确定针对当前请求应该调用哪些 Spring Security 过滤器实例。如下图所示:当配置多个
SecurityFilterChain
时,只有匹配到的第一个 SecurityFilterChain
会被调用。FilterChainProxy
FilterChainProxy
是 Spring Security 中的一个特殊过滤器,通过 SecurityFilterChain
允许配置多个过滤器实例。它通常被包装在 DelegatingFilterProxy
中,用于在 Servlet 环境中管理和调度这些过滤器实例。DelegatingFilterProxy
DelegatingFilterProxy
是 Spring 提供的 Filter 实现,用于在 Servlet 容器和 Spring 的 ApplicationContext 之间建立桥梁。通过这个机制,可以在 Servlet 容器中注册 Filter 实例,同时将实际的工作委托给实现了 Filter 接口的 Spring Bean。这样做的好处是可以利用 Spring 的 IoC 容器管理和配置 Filter,从而更好地集成 Spring 和 Servlet 环境。
认证核心
- SecurityContextHolder - 存储已认证用户详细信息的地方。
- SecurityContext - 从
SecurityContextHolder
获取,包含当前经过身份验证用户的Authentication
对象。
- Authentication - 表示当前认证用户的对象,可以作为输入提供给
AuthenticationManager
,以用户提供的凭据进行身份验证
- GrantedAuthority - 授予
Authentication
权限,例如角色、范围等。
- AuthenticationManager - 定义 Spring Security 过滤器执行身份验证的 API。
- ProviderManager -
AuthenticationManager
的最常见实现。
- AuthenticationProvider - 被
ProviderManager
使用,执行特定类型的身份验证。
- Request Credentials with AuthenticationEntryPoint - 用来向客户端发送一个请求凭证的HTTP响应,例如重定向到登录页面,发送WWW-Authenticate头等。
- AbstractAuthenticationProcessingFilter - 一个基础的过滤器,用来认证用户的凭证。在认证之前,通常需要通过AuthenticationEntryPoint来请求凭证。认证成功或失败后,会调用相应的处理器。
Authentication
Authentication
对象是Spring Security中表示认证信息的核心接口。它代表了在应用程序中进行身份验证的结果,包含有关已认证主体(用户)的信息。其中包括一些必要的属性:
- Principal(主体):
- 通过
getPrincipal()
方法获取主体对象,通常是实现了Principal
接口的对象。在Spring Security中,主体通常是UserDetails
对象,包含了有关用户的详细信息,如用户名、密码等。
- Credentials(凭证):
- 通过
getCredentials()
方法获取凭证,这通常是用户的密码、令牌或其他用于进行身份验证的信息。
- Authorities(权限):
- 通过
getAuthorities()
方法获取主体被授予的权限列表,这是一个包含GrantedAuthority
对象的集合。权限表示主体在应用程序中被授予的操作权限。
- Authenticated(认证状态):
- 通过
isAuthenticated()
方法获取认证状态,返回一个布尔值,指示认证是否成功。如果已认证,返回true
;否则返回false
。
这些属性是
Authentication
接口的核心属性,用于表示身份验证的结果。具体的实现类可能还包含其他属性,例如:- Details(详细信息):
- 通过
getDetails()
方法获取与认证请求相关的详细信息。这是一个可选的属性,用于存储额外的上下文信息,如IP地址、设备信息等。
在使用过程中,通常会根据具体的认证方式和需求,使用不同的
Authentication
实现类,以满足特定场景的要求。例如,用户名密码认证可能使用UsernamePasswordAuthenticationToken
,记住我认证可能使用RememberMeAuthenticationToken
。ProviderManager
ProviderManager 是 AuthenticationManager 接口的常用实现之一。
ProviderManager 其主要作用是委托给一组
AuthenticationProvider
实例来执行身份验证操作。按顺序调用每个 AuthenticationProvider
进行特定类型的身份验证,直到找到一个可以成功验证身份的 provider。如果没有提供者能够成功验证,就会抛出 ProviderNotFoundException
异常。ProviderManager 还允许配置一个可选的父
AuthenticationManager
,在没有 AuthenticationProvider
能够执行身份验证的情况下进行咨询。父级可以是任何类型的
AuthenticationManager
,但通常是 ProviderManager
的实例。实际上,多个
ProviderManager
实例可以共享相同的父 AuthenticationManager
。在存在多个 SecurityFilterChain
实例并具有某些公共身份验证的情况下,这是比较常见的,这些公共身份验证在共享的父 AuthenticationManager
中,同时也存在不同的身份验证机制(不同的 ProviderManager
实例)。默认情况下,
ProviderManager
尝试清除成功认证请求返回的 Authentication
对象中的任何敏感凭据信息以提高安全性。这可以防止敏感信息在HttpSession中保留的时间过长。但在使用缓存时,可能需要谨慎考虑这一特性,以避免对缓存的影响。AuthenticationProvider
AuthenticationProvider
是 Spring Security 中的接口,用于执行身份验证操作。它定义了一个单一的方法:authenticate
方法接收一个 Authentication
对象,该对象包含了用户提供的身份验证信息,例如用户名和密码。AuthenticationProvider
的责任是根据这些信息进行验证,并返回一个完整填充的 Authentication
对象表示验证成功,或者抛出 AuthenticationException
表示验证失败。Spring Security提供了多个实现了
AuthenticationProvider
接口的类,每个类负责不同类型的身份验证。例如:DaoAuthenticationProvider
用于验证用户名/密码。
LdapAuthenticationProvider
用于验证LDAP身份。
JwtAuthenticationProvider
用于验证JSON Web Token(JWT)。
- 等等。
在配置Spring Security时,可以通过
ProviderManager
将多个 AuthenticationProvider
组合在一起,以便按照配置的顺序逐个尝试进行身份验证。这样可以支持多种身份验证机制,并且每个 AuthenticationProvider
可以专注于特定类型的身份验证。例如:AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter
是用于协调认证流程,处理认证成功和失败的情况,并提供了可定制和与 Spring Security 框架中其他组件集成的扩展点。流程如下:- 凭证提交:
- 用户提交其凭证(例如,用户名和密码),
AbstractAuthenticationProcessingFilter
从HttpServletRequest
中创建一个Authentication
对象,用于表示用户的凭证。 - 创建的
Authentication
对象的具体类型取决于AbstractAuthenticationProcessingFilter
的子类。例如,UsernamePasswordAuthenticationFilter
从提交的用户名和密码创建一个UsernamePasswordAuthenticationToken
。
- 认证过程:
- 创建的
Authentication
对象随后传递给AuthenticationManager
进行认证。AuthenticationManager
可能使用各种认证提供者来验证凭证的有效性。
- 认证失败处理:
- 如果认证失败,会执行以下操作:
SecurityContextHolder
被清空。- 调用
RememberMeServices.loginFail
方法(如果启用了记住我功能)。 - 调用配置的
AuthenticationFailureHandler
处理认证失败。
- 认证成功处理:
- 如果认证成功,执行以下步骤:
- 通知
SessionAuthenticationStrategy
关于新登录,允许执行会话固定保护等策略。 - 认证通过的
Authentication
对象被设置到SecurityContextHolder
。 - 调用
RememberMeServices.loginSuccess
方法(如果启用了记住我功能)。 - 通过
ApplicationEventPublisher
发布InteractiveAuthenticationSuccessEvent
事件。 - 调用配置的
AuthenticationSuccessHandler
处理认证成功。
- 其他注意事项:
- 如果需要将
SecurityContext
保存以便在将来的请求中自动设置,必须显式调用SecurityContextRepository#saveContext
方法。 - 通过可配置的组件提供了处理记住我服务、认证成功和失败的钩子。
用户名密码认证(内置)
当用户提交用户名和密码进行认证时。以下是简化的流程:
- 用户提交认证请求:用户在应用程序的登录页面输入用户名和密码,然后提交认证请求。
- UsernamePasswordAuthenticationFilter拦截请求:默认情况下,Spring Security使用
UsernamePasswordAuthenticationFilter
过滤器来处理基于用户名和密码的身份验证。这个过滤器会拦截认证请求。
- AuthenticationManager处理认证请求:
UsernamePasswordAuthenticationFilter
将认证请求传递给AuthenticationManager
进行处理。AuthenticationManager
是Spring Security中负责处理身份验证的核心接口。
- ProviderManager委托给AuthenticationProvider:
AuthenticationManager
通常会包含一个或多个AuthenticationProvider
,每个AuthenticationProvider
负责特定类型的身份验证。例如,对于用户名密码认证,通常会有DaoAuthenticationProvider
。ProviderManager
将认证请求委托给适当的AuthenticationProvider
。
- DaoAuthenticationProvider验证用户名和密码:
DaoAuthenticationProvider
从用户存储(如数据库)中检索用户信息,然后验证输入的用户名和密码是否匹配。如果匹配,它创建一个包含用户权限信息的Authentication
对象。
- Authentication对象存储在SecurityContext中:通过
SecurityContextHolder
,Authentication
对象最终存储在SecurityContext
中。SecurityContext
是一个线程本地的上下文对象,用于存储与当前线程相关的安全信息。
- 认证成功:如果认证成功,
UsernamePasswordAuthenticationFilter
会生成一个成功的认证响应,例如重定向到之前受保护资源的页面。
自定义认证(按需)
定义Filter
定义Token
定义Provider
调用
- 自动调用
将自定义filter加入
securityFilterChain
,在filter的 attemptAuthentication
方法中写认证逻辑- 手动调用
在controller或service层中写认证逻辑
核心逻辑:
用户实体
Spring Security 需要用户类来表示系统中的用户信息,以便进行身份验证(Authentication)和授权(Authorization),它提供了一个默认的
User
类,它实现了UserDetails
接口。User
类包含了一些基本的用户属性,如用户名、密码、账户是否启用、账户是否未过期、密码是否未过期、账户是否未锁定以及用户的权限集合。然而,在实际的应用程序中,特别是在复杂的系统中,你可能会需要自定义用户信息的表示方式,以及在
UserDetails
接口中定义的各种方法。在这种情况下,需要创建一个自定义的
UserDetails
实现类,以满足应用程序的需求。创建自定义用户信息类:
src\main\java\com\youlai\system\core\security\model\SysUserDetails.java
用户信息加载
通常情况下,当自定义了用户类后,也会需要自定义实现
UserDetailsService
。UserDetailsService
是 Spring Security 中用于加载用户信息的核心接口。它的主要职责是根据用户名(通常是用户在登录时输入的用户名)从数据源中加载用户信息,并返回一个实现了 UserDetails
接口的对象。当你自定义了用户类,例如实现了自己的
SysUserDetails
类,你需要告诉 Spring Security 如何从数据源中加载和构建这个类的实例。这时就需要创建一个实现了 UserDetailsService
接口的类,并在其中实现 loadUserByUsername
方法。创建用户服务类:
src\main\java\com\youlai\system\core\security\service\SysUserDetailsService.java
loadUserByUsername
方法是由DaoAuthenticationProvider
(身份验证提供者)自动调用的。DaoAuthenticationProvider
是Spring Security中处理基于用户名和密码的身份验证的一个核心组件。当用户尝试登录并提供了用户名和密码时,
DaoAuthenticationProvider
会调用配置中指定的UserDetailsService
,并传递用户提供的用户名作为参数。loadUserByUsername
方法负责从数据源中加载与该用户名关联的用户信息,通常是一个实现了UserDetails
接口的对象。加载的用户信息将与用户提供的密码进行比对,以确定用户是否有效。详细配置
登录
通过上述配置,就可以进行登录业务编写:
- 创建一个
UsernamePasswordAuthenticationToken
对象,该对象携带用户提供的用户名和密码信息。
- 调用
authenticationManager.authenticate(authenticationToken)
来触发 Spring Security 的身份验证流程。在这一步,DaoAuthenticationProvider
会自动调用你的UserDetailsService
的loadUserByUsername
方法,加载用户信息,然后进行身份验证。
- 如果用户名和密码验证成功,将创建一个包含用户信息的
Authentication
对象。如果验证失败,将抛出AuthenticationException
异常。
- 通过成功的
Authentication
对象,调用jwtTokenProvider.createToken(authentication)
来生成一个 JWT(JSON Web Token)。这个 JWT 将被包含在你的LoginResult
对象中返回给客户端。
权限加载
在
loadUserByUsername
认证查询用户信息过程中,同时将用户对应角色的所有权限集合存入用户信息配置授权服务类
src\main\java\com\youlai\system\core\security\service\PermissionService.java
- 权限初始化: 在
@PostConstruct
注解的方法initPermissionCache
中,通过调用refreshPermissionCache
方法,从数据库中加载角色和权限的对应关系,并将其缓存到 Redis 中,键为角色代码,值为权限集合。
- 权限刷新: 提供
refreshPermissionCache
方法,用于手动刷新权限缓存,可以刷新所有角色的权限或指定角色的权限。
hasPerm
方法: 通过遍历当前登录用户的每个角色,匹配相应的权限集合,来判断用户是否拥有指定的操作权限。
权限匹配
Spring Security提供了
@PreAuthorize
注解,它允许在方法级别进行安全性控制,即授权。这个注解可以直接放在方法上,用于限制哪些用户可以调用这个方法。要使用@PreAuthorize
注解,需要确保在Spring Security的配置类添加了@EnableGlobalMethodSecurity
来开启。在使用
@PreAuthorize
注解时,可以在注解中指定一个SpEL(Spring Expression Language)表达式,用于描述访问该方法的条件。这样,便可以更灵活地基于用户的角色、权限等信息进行授权控制。例如:- Author:风之旅人
- URL:https://www.hrmi.fun//article/spring-security
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!