SpringSecurity系列——自定义成功、失败处理day5-2(源于官网5.7.2版本)

614 阅读4分钟

SpringSecurity系列——自定义成功、失败处理day5-2(源于官网5.7.2版本)

前言

技术版本
jdk17
SpringSecurity5.7.2
SpringBoot2.7.2

身份验证事件

对于成功或失败的每个身份验证,将分别触发 AuthenticationSuccessEvent 或 AbstractAuthenticationFailureEvent。

要侦听这些事件,您必须首先发布 AuthenticationEventPublisher。 Spring Security 的 DefaultAuthenticationEventPublisher 可能会做得很好:

@Bean
public AuthenticationEventPublisher authenticationEventPublisher
        (ApplicationEventPublisher applicationEventPublisher) {
    return new DefaultAuthenticationEventPublisher(applicationEventPublisher);
}

然后,您可以使用 Spring 的 @EventListener 支持: ==这里提醒大家注意成功事件,下面实例中给出详细修改和讲解==

@Component
public class AuthenticationEvents {
	@EventListener
    public void onSuccess(AuthenticationSuccessEvent success) {
		// ...
    }

    @EventListener
    public void onFailure(AbstractAuthenticationFailureEvent failures) {
		// ...
    }
}

虽然类似于 AuthenticationSuccessHandler 和 AuthenticationFailureHandler,但它们很好,因为它们可以独立于 servlet API 使用。

添加异常映射

默认情况下,DefaultAuthenticationEventPublisher 将为以下事件发布 AbstractAuthenticationFailureEvent: 在这里插入图片描述 发布者进行精确的异常匹配,这意味着这些异常的子类也不会产生事件。

为此,您可能希望通过 setAdditionalExceptionMappings 方法向发布者提供额外的映射:

@Bean
public AuthenticationEventPublisher authenticationEventPublisher
        (ApplicationEventPublisher applicationEventPublisher) {
    Map<Class<? extends AuthenticationException>,
        Class<? extends AbstractAuthenticationFailureEvent>> mapping =
            Collections.singletonMap(FooException.class, FooEvent.class);
    AuthenticationEventPublisher authenticationEventPublisher =
        new DefaultAuthenticationEventPublisher(applicationEventPublisher);
    authenticationEventPublisher.setAdditionalExceptionMappings(mapping);
    return authenticationEventPublisher;
}

默认事件

并且,您可以提供一个包罗万象的事件以在任何 AuthenticationException 的情况下触发:

@Bean
public AuthenticationEventPublisher authenticationEventPublisher
        (ApplicationEventPublisher applicationEventPublisher) {
    AuthenticationEventPublisher authenticationEventPublisher =
        new DefaultAuthenticationEventPublisher(applicationEventPublisher);
    authenticationEventPublisher.setDefaultAuthenticationFailureEvent
        (GenericAuthenticationFailureEvent.class);
    return authenticationEventPublisher;
}

解释概括

以上就是官网给出的身份验证事件的解释以及说明 针对这个内容我对其进行了实例的修改 首先我们要明确这个新特性

新特性说明

这个新特性其实就是上方添加异常映射的实例 在以前的版本中原始用法中其实并非以处理事件的形式形成 包括我看了很多的视频中其实都是以重写AuthenticationSuccessHandlerAuthenticationFailureHandler直接去进行失败处理的 ==而在包含新特性的版本中我们的失败处理建议为使用身份验证事件进行处理 并且移除了HttpSecurit中的SuccessHandler(),FailureHandler== 接下来我们看一下实例(根据官方进行简单修改)

身份验证失败事件(实例)

注意本实例在day5-1的代码基础上进行增加而不进行任何修改

1. 注入身份验证事件,发布 AuthenticationEventPublisher

在SpringSecurityConfig中

    //身份验证事件,发布 AuthenticationEventPublisher
    @Bean
    public AuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher){
        return new DefaultAuthenticationEventPublisher(applicationEventPublisher);
    }

2.编写自定义AuthenticationEvents

注意点

  1. 需要注册为组件(@Component)
  2. 使用@EventListener注解实现事件监听
package com.example.test1.config;

import org.springframework.context.event.EventListener;
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

@Component
public class AuthenticationEvents {

    @EventListener
    public void onFailure(AbstractAuthenticationFailureEvent failureEvent){
        System.out.println("fail....");
        System.out.println(failureEvent.getAuthentication());
        System.out.println(failureEvent.getException());
    }
}

3. 测试

当我故意输入错误密码时如下所示 在这里插入图片描述

在这里插入图片描述

身份验证成功事件(实例)

这里为什么不是身份验证成功事件呢? 因为实际在我们身份验证成功后直接触发的是ApplicationEventPublisher发布一个 InteractiveAuthenticationSuccessEvent我们目前就是在对事件发布做处理 这也是与之前所不同的地方,所有的视频都会告诉你重写AuthenticationSuccessHandler接口配合HttpSecurity的formLogin处理successHandler方法但显然新特性中并不再提供HttpSecurit中的SuccessHandler()

使用InteractiveAuthenticationSuccessEvent

为什么会使用这个事件呢? 其实在官方给出的这张图中就可以解释 在这里插入图片描述 在我们day2-3中我也做了解释 如果认证成功,则为 Success

  1. SessionAuthenticationStrategy 收到新登录通知。
  2. Authentication 在 SecurityContextHolder 上设置。 稍后SecurityContextPersistenceFilter 将 SecurityContext 保存到 HttpSession。
  3. 调用 RememberMeServices.loginSuccess 如果记住我没有配置,这是一个空操作。
  4. ApplicationEventPublisher 发布一个InteractiveAuthenticationSuccessEvent。
  5. AuthenticationSuccessHandler 被调用。

在第4部中我们显然看到 ApplicationEventPublisher会发布这个事件,所以我想到了监听这个交互的认证成功事件而不是AuthenticationSuccessEvent,经过我的测试,若你使用AuthenticationSuccessEvent事件的监听你仍然无法获取任何事件反馈

而且最重要的是: InteractiveAuthenticationSuccessEvent表示交互式身份验证成功。 ApplicationEvent的source将是Authentication对象。这不会从AuthenticationSuccessEvent扩展以避免重复的AuthenticationSuccessEvent被发送到任何侦听器

源码追踪

以下是AuthenticationSuccessEvent的源码,其实这里直接调用的就是AbstractAuthenticationEvent的方法,然而AbstractAuthenticationEvent是个抽象类,下方实际的实现在调试的时候其实即使你对其进行了监听也不会使用,实际上的实现是InteractiveAuthenticationSuccessEvent中的实现这也解释为什么官方说发布的是InteractiveAuthenticationSuccessEvent而非AuthenticationSuccessEvent

public class AuthenticationSuccessEvent extends AbstractAuthenticationEvent {

	public AuthenticationSuccessEvent(Authentication authentication) {
		super(authentication);
	}

}

自定义AuthenticationEvents中监听InteractiveAuthenticationSuccessEvent

 @EventListener
    public void InteractiveSuccess(InteractiveAuthenticationSuccessEvent event){
        System.out.println("interactive success");
        System.out.println(event.getAuthentication());
    }

测试

在这里插入图片描述

实例完整代码

package com.example.test1.config;

import org.springframework.context.event.EventListener;
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

@Component
public class AuthenticationEvents {
    
    @EventListener
    public void InteractiveSuccess(InteractiveAuthenticationSuccessEvent event){
        System.out.println("interactive success");
        System.out.println(event.getAuthentication());
    }

    @EventListener
    public void onFailure(AbstractAuthenticationFailureEvent failureEvent){
        System.out.println("fail....");
        System.out.println(failureEvent.getAuthentication());
        System.out.println(failureEvent.getException());
    }

}