Spring Security使用总结

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

image-20240529140221617

FilterChainProxy是 Spring Security 提供的一个特殊的 Filter,允许通过SecurityFilterChain委托给许多 Filter 实例。由于 FilterChainProxy 是一个Bean,它通常被包裹在DelegatingFilterProxy 中。

image-20240529140634248

我们可以看下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 实例。如图

image-20240529145054161

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
CsrfFilterHttpSecurity#csrf
UsernamePasswordAuthenticationFilterHttpSecurity#formLogin
BasicAuthenticationFilterHttpSecurity#httpBasic
AuthorizationFilterHttpSecurity#authorizeHttpRequests
  1. 首先,调用 CsrfFilter 来防止 CSRF 攻击
  2. 其次,认证 filter 被调用以认证请求。
  3. 第三,调用 AuthorizationFilter 来授权该请求。

添加自定义 Filter 到 Filter Chain

自定义Filter不需要通过实现Filter接口来实现我们可以通过OncePerRequestFilter中继承,这是一个基类,用于每个请求只调用一次的 filter,并提供一个带有 HttpServletRequestHttpServletResponse 参数的 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 异常表示用户已通过身份验证但没有访问某资源所需的权限。换句话说,用户是已认证的,但他们不具有访问该资源的适当授权。

image-20240529162645546

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添加流程是怎么走下去的,至少在问题排查可以帮助我们

后续接着聊😁

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值