Spring Security之安全异常处理

前言

在我们的安全框架中,不管是什么框架(包括通过过滤器自定义)都需要处理涉及安全相关的异常,例如:登录失败要跳转到登录页,访问权限不足要返回页面亦或是json。接下来,我们就看看Spring Security是怎么处理异常的!

什么是异常处理

在Spring Security中,特指对于安全异常的处理。

我们知道Spring Security主要是基于过滤器来实现的,因此每个安全过滤器都可能发生安全异常,所以处理逻辑会被散落在各个过滤器中。

Spring自然是不能忍受这种设计,于是就有了专门的安全异常处理。

注:下文我们都用异常处理来代指安全异常处理。

异常处理设计

Spring Security将安全异常分为两类。

  • AuthenticationException —— 认证异常
    认证异常

    认证异常 触发原因 描述
    BadCredentialsException 无法识别凭证 可能是没有凭证/无法解密/格式不对等
    UsernameNotFoundException 没有找到用户 用户名没有对应的账号
    SessionAuthenticationException 认证过程中与session相关的校验。例如,控制多点登录时,当某用户多点登录超过规定数量就会发生 session认证异常
    AuthenticationServiceException 认证服务遇到无法处理的情况是触发 认证服务异常
    ProviderNotFoundException ProviderManager没有配置任何的Provider 没有Provider
    PreAuthenticatedCredentialsNotFoundException 与第三方认证系统集成时,发现客户端没有传凭证 前认证凭证没有找到
    AuthenticationCredentialsNotFoundException 这个主要是鉴权的时候发现没有认证,就会抛出 没有找到认证凭证。
    RememberMeAuthenticationException - 主要与记住我功能,恢复登录态有关
    NonceExpiredException - 这个主要与Digest认证方式有关
    AccountStatusException 校验账号状态时触发 账号状态异常
  • AccessDeniedException —— 访问拒绝异常
    访问拒绝异常

    访问拒绝异常 触发原因 描述
    AuthorizationServiceException 遇到无法处理的鉴权时触发 例如配置错误,数据类型错误
    CrsfException 防御Crsf时触发 这里有两个,分别对应WebFlux和WebMvc

    其实对于鉴权来说,只要发现权限不满足,都是直接抛出AccessDeniedException的。

认证异常和访问拒绝异常的区别

与访问拒绝异常相比,认证异常要复杂不少。这是由认证过程和认证方式的多样性导致的。

  • 认证过程:
    一个完整的用户密码认证过程各组件的调用关系和简化
    一个完整的用户密码认证过程各组件的调用关系和简化组件都有不少,更何况要捋清楚调用关系。上面也只能是给大家看看认证过程中需要干啥,有哪些组件负责。
  • 认证方式:
    这个就不多啰嗦,前面说认证过滤器的时候有说过。

异常处理器

异常 类定义 异常处理器
认证异常 AuthenticationException AuthenticationFailureHandler
访问异常 AccessDeniedException AccessDeniedHandler

为什么要搞两个异常,还要搞两个组件来处理呢?

  1. 从安全业务上说,本来就是两种业务,访问跟认证是两个事情。
  2. 从单一职责原则来说,肯定要进行拆分,因为这两个组件处理的是不同的异常。
  3. 一般而言,登录异常我们是需要重定向到登录页面的,而接口访问异常则不然,一般通过返回错误拒绝请求。

实际上,这两个组件定义的可以说一模一样:

public interface AuthenticationFailureHandler {
   
	void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
			throws IOException, ServletException;
}

public interface AccessDeniedHandler {
   
	void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)
			throws IOException, ServletException;
}

除了方法名,和入参的异常不同,其他的都是一样的。甚至,如果我们进一步看看异常的定义的话,连异常定义也是类似的,都是继承于RuntimeException,没有任何其他多余的字段和逻辑。

AuthenticationFailureHandler的实现

AuthenticationFailureHandler 描述
AuthenticationEntryPointFailureHandler 通过AuthenticationEntryPoint组件处理
SimpleUrlAuthenticationFailureHandler 重定向到指定URL,如果没有指定,则退化返回401
ForwardAuthenticationFailureHandler 重定向到指定的URL,必须指定URL
ExceptionMappingAuthenticationFailureHandler 通过匹配异常寻找对应的处理器,一般由用户自行配置。
DelegatingAuthenticationFailureHandler 委托其他的处理器处理

这里有一个特殊的,他使用另一个组件AuthenticationEntryPoint进行处理。

AuthenticationEntryPoint

  • Http403ForbiddenEntryPoint
    他是处理登录异常的通用的可选方案,通常是AbstractPreAuthenticatedProcessingFilter(基于外部认证服务器进行认证)。核心逻辑:总是返回403。
    这个实现是用来兜底的,如果找不到其他的,那就会用他。

  • HttpStatusEntryPoint
    他是一种可选方案,直接返回一个用户指定的http状态,response.setStatus(this.httpStatus.value())。

  • LoginUrlAuthenticationEntryPoint
    如果我们使用的是UsernamePasswordAuthenticationFilter,那么默认使用的就是这个。其核心逻辑也比较简单明了,就是重定向到登录页面。如果我们往上一层对比到SimpleUrlAuthenticationFailureHandler 、ForwardAuthenticationFailureHandler ,他的区别在于如果我们指定了loginPage,那么就会使用他。他会自动识别是绝对地址还是相对地址进行拼接。

  • DigestAuthenticationEntryPoint
    显然,他是为DigestAuthenticationFilter服务的。他会设置一些与Digest相关的请求头,然后调用response.sendError方法处理。

  • BasicAuthenticationEntryPoint
    为BasicAuthenticationFilter服务。核心逻辑与Digest类似,也是设置相关请求头,通过response.sendError方法处理。

  • DelegatingAuthenticationEntryPoint
    委托。没有自己的逻辑,而是交给别的AuthenticationEntryPoint。

AccessDeniedHandler的实现

  • AccessDeniedHandlerImpl
    基础实现,也是默认实现。设置HTTP错误码-403,并转发到错误页面。
  • InvalidSessionAccessDeniedHandler
    显然是为了处理session失效异常的。不过有趣的是,官方在CsrfConfigurer中引入这个。并且是为了处理MissingCsrfTokenException的。并且为了单一职责,还构建了下面的委托处理器。
  • DelegatingAccessDeniedHandler
    委托处理器。他管理着哪些异常对应哪个处理器,并将当前异常的处理交付给对应的处理器处理。故而得名“委托”处理器,算是个代理人吧。当然,他要求必须有个兜底的默认处理器。
  • RequestMatcherDelegatingAccessDeniedHandler
    他也是委托处理器,不同点在于,他是RequestMatcherDelegating,也即基于RequestMatcher进行Request匹配处理器。
  • ObservationMarkingAccessDeniedHandler
    他是用来统计数据的,观察标记。
  • CompositeAccessDeniedHandler
    组合模式的实现,用来管理多个处理器。目前看的话,主要是为了统计服务,因为他会调用每一个处理器,这可能会出现问题。只有统计这个处理器,需要其他的处理器来实现真正的处理,需要配合。

到这里问一句,这里我们看到了几种设计模式?策略模式、委托模式、组合模式。可以看到Spring对于代码的追求,这也是我们阅读源码的目的之一,学习好的设计。而这背后都是设计原则。

异常处理原理

前面我们大概了解了异常处理的来龙去脉,知道了其核心组件。现在我们来深入了解其原理。

认证异常处理原理

要理解这个,就必须回顾一下认证流程(这里以默认的用户密码登录为例):

AbstractAuthenticationProcessingFilter#doFilter
> UsernamePasswordAuthenticationFilter#attemptAuthentication
|-> ProviderManager#authenticate
|-|-> AbstractUserDetailsAuthenticationProvider#authenticate
|-|-|->DaoAuthenticationProvider#retrieveUser
|-|-|-|->JdbcDaoImpl#loadUserByUsername
|-|-|->DaoAuthenticationProvider#additionalAuthenticationChecks
|-|-|->DaoAuthenticationProvider#createSuccessAuthentication
|-|-|->AbstractUserDetailsAuthenticationProvider#createSuccessAuthentication
// 同层级的表示顺序调用,不同层级的:上层方法调用下层方法,是递进关系。层级减少表示方法返回

负责处理认证请求的AbstractAuthenticationProcessingFilter#doFilter方法中会捕获异常,并交给unsuccessfulAuthentication方法处理。

public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
		implements ApplicationEventPublisherAware, MessageSourceAware {
   
	
	protected void unsuccessfulAuthentication(HttpServletRequest request, HttpSer
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值