Spring Security使用总结
文章目录
[!NOTE]
本文只是作为作者笔记,只作为自己参考加深映像,小伙伴觉得哪里不对可以指出改正😁
使用版本
spring-boot-starter-security: 2.3.12.RELEASE
spring-security-config: 5.3.9.RELEASE
具体可以参考maven仓库里进行查询版本对应关系
首先我们都知道Spring Security两大重要特性 认证(authentication)
、授权(authorization)
如果你想灵活应用Spring Security框架,首先我们需要了解架构结构
[!NOTE]
这里我并没有做高版本使用,因为使用超过
6.2.0
版本需要升级Spring Boot,升级后果就会就是需要更高的JDK,我是用的JDK8(为啥不升级因为大部分公司用的基本上都是JDK8版本我就不特立独行了😂)
FilterChainProxy回顾
我们知道Spring Security认证是基于过滤器来完成的,Spring 给我们提供了DelegatingFilterProxy
,可以通过标准的Servlet容器机制来注册DelegatingFilterProxy
FilterChainProxy
是 Spring Security 提供的一个特殊的 Filter
,允许通过SecurityFilterChain
委托给许多 Filter
实例。由于 FilterChainProxy
是一个Bean,它通常被包裹在DelegatingFilterProxy
中。
我们可以看下FilterChainProxy源码
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (clearContext) {
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
doFilterInternal(request, response, chain);
}
finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
else {
//通过不同FilterChain调用不同Filter#doFilter
doFilterInternal(request, response, chain);
}
}
private void doFilterInternal(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = firewall
.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall
.getFirewalledResponse((HttpServletResponse) response);
List<Filter> filters = getFilters(fwRequest);
if (filters == null || filters.size() == 0) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(fwRequest)
+ (filters == null ? " has no matching filters"
: " has an empty filter list"));
}
fwRequest.reset();
chain.doFilter(fwRequest, fwResponse);
return;
}
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
vfc.doFilter(fwRequest, fwResponse);
}
SecurityFilterChain作用
SecurityFilterChain被 FilterChainProxy用来确定当前请求应该调用哪些 Spring Security Filter
实例。如图
SecurityFilterChain中的[Security Filter](#Security Filter)通常是Bean,但它们是用 FilterChainProxy
而不是 DelegatingFilterProxy 注册的。
Security Filter
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(Customizer.withDefaults())
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults());
return http.build();
}
}
Security Filter我们最常用的Filter
Filter | |
---|---|
CsrfFilter | HttpSecurity#csrf |
UsernamePasswordAuthenticationFilter | HttpSecurity#formLogin |
BasicAuthenticationFilter | HttpSecurity#httpBasic |
AuthorizationFilter | HttpSecurity#authorizeHttpRequests |
- 首先,调用
CsrfFilter
来防止 CSRF 攻击。 - 其次,认证 filter 被调用以认证请求。
- 第三,调用
AuthorizationFilter
来授权该请求。
添加自定义 Filter 到 Filter Chain
自定义Filter不需要通过实现Filter
接口来实现我们可以通过OncePerRequestFilter
中继承,这是一个基类,用于每个请求只调用一次的 filter,并提供一个带有 HttpServletRequest
和 HttpServletResponse
参数的 doFilterInternal
方法。
例如:
public class TenantFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//逻辑处理
}
}
//方法一 当前方法适用与高版(至少在6.2.0以上)本Spring Security比不支持我当前版本
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.addFilterBefore(new TenantFilter(), AuthorizationFilter.class);
return http.build();
}
/*
方法二
或者继承WebSecurityConfigurerAdapter类实现
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.addFilterBefore(new TenantFilter(), AuthorizationFilter.class);
}
}
这里有个小插曲
我们可以使用@Component注入我们的TenantFilter或者通过@Bean方式进行注入,官网说可能会出现注入两次问题,一次是Spring Bean依赖注入,一次是Spring Security依赖注入,不过这个问题不大如果你实在在意注入多次情况官网提供了解决方案
@Bean
public FilterRegistrationBean<TenantFilter> tenantFilterRegistration(TenantFilter filter) {
FilterRegistrationBean<TenantFilter> registration = new FilterRegistrationBean<>(filter);
registration.setEnabled(false);
return registration;
}
处理 Security 异常
ExceptionTranslationFilter
是一种在 Java 的 Spring 框架中使用的过滤器,通常用于处理异常并将它们转换为合适的响应,以便更好地控制错误处理和日志记录。具体来说,ExceptionTranslationFilter
常用于 Spring Security 框架中,来处理与安全相关的异常。
这里有连个非常重要异常一个是AccessDeniedException
(认证对象没有所属权限会抛出)另一个是AuthenticationException
(没有权限),捕获到异常后,ExceptionTranslationFilter
会将这些异常转换为适当的 HTTP 响应码。例如,AccessDeniedException
通常会被转换为 403 Forbidden 响应,而 AuthenticationException
会被转换为 401 Unauthorized 响应。
简单来说就是AuthenticationException
异常表示用户未通过身份验证,通常在用户未登录或提供的凭证无效时抛出。例如,当用户尝试访问需要认证的资源但未提供有效的用户名和密码时,会抛出此异常。AccessDeniedException
异常表示用户已通过身份验证但没有访问某资源所需的权限。换句话说,用户是已认证的,但他们不具有访问该资源的适当授权。
1. 执行过滤链
首先,ExceptionTranslationFilter
会调用 FilterChain.doFilter(request, response)
来执行过滤链中的其他过滤器和目标资源。这一步确保请求能够正常传递到应用程序的其他部分:
2. 处理 AuthenticationException
如果在过滤链的其他部分或目标资源中抛出了 AuthenticationException
异常,说明用户未认证或认证失败。在这种情况下,ExceptionTranslationFilter
会执行以下步骤:
a. 清除 SecurityContextHolder
在处理认证异常之前,ExceptionTranslationFilter
会清除 SecurityContextHolder
中的安全上下文,以确保当前线程没有残留的认证信息:
SecurityContextHolder.clearContext();
b. 保存原始请求
为了在用户成功认证后能够重放原始请求,ExceptionTranslationFilter
会将当前的 HttpServletRequest
保存起来:
c. 使用 AuthenticationEntryPoint 请求凭证
ExceptionTranslationFilter
使用 AuthenticationEntryPoint
来请求客户端提供凭证。例如,可以将用户重定向到登录页面或发送 WWW-Authenticate 头以提示用户进行认证:
authenticationEntryPoint.commence(request, response, authException);
3. 处理 AccessDeniedException
如果在过滤链的其他部分或目标资源中抛出了 AccessDeniedException
异常,说明用户没有足够的权限访问请求的资源。在这种情况下,ExceptionTranslationFilter
会执行以下步骤:
a. 调用 AccessDeniedHandler
ExceptionTranslationFilter
使用 AccessDeniedHandler
来处理访问被拒绝的情况。AccessDeniedHandler
通常会返回一个 403 Forbidden 响应,或者显示一个错误页面:
accessDeniedHandler.handle(request, response, accessDeniedException);
简化流
Request -> ExceptionTranslationFilter -> FilterChain.doFilter() -> 可能的异常 -> 异常处理
| | -> 正常执行目标资源
|
| -> AuthenticationException -> 清除 SecurityContextHolder -> 保存请求 -> 使用 AuthenticationEntryPoint 请求凭证
|
| -> AccessDeniedException -> 调用 AccessDeniedHandler 处理访问被拒绝
总结
我们关注点可以不在乎流程是怎么样,更关注如何使用,大致Spring Security流程,SecurityFilterChain添加流程是怎么走下去的,至少在问题排查可以帮助我们
后续接着聊😁
er -> 保存请求 -> 使用 AuthenticationEntryPoint 请求凭证
|
| -> AccessDeniedException -> 调用 AccessDeniedHandler 处理访问被拒绝
总结
我们关注点可以不在乎流程是怎么样,更关注如何使用,大致Spring Security流程,SecurityFilterChain添加流程是怎么走下去的,至少在问题排查可以帮助我们
后续接着聊😁