Security中的认证事件发布器

Posted agony-wxl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Security中的认证事件发布器相关的知识,希望对你有一定的参考价值。

AuthenticationEventPublisher

  • AuthenticationEventPublisher
    技术图片
    从上图我们能看出AuthenticationEventPublisher (认证事件发布器)被Security定义为接口的形式,它定义了Spring Security中用户授权成功或失败的通知机制.

    题外话

    对于认证事件发布器是Security是如何加载并使用的,小伙伴们是否有许多的问号?在这里,仅仅告诉小伙伴们我的师傅(大数据大佬)曾经告诉我一个道理,作为我们这一行,看所有代码都需要带着问题去探究,Spring为什么要如此定义它,定义它以后又是如何加载并使用的,这些问题,其实值得我们思考和学习!

运行机制

对于AuthenticationEventPublisher 的运行机制,我进行了深入的探究,接下来带你们来一看究竟

技术图片
上图明确的表示了Spring Security是如何加载AuthenticationEventPublisher 的。有疑问的小伙伴可以自行代码调试!
关于 AbstractApplicationContext 如有不懂的可以看 AbstractApplicationContext 源码分析 这篇文章

Spring Security如何使用认证事件发布器

  • SecurityAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class,WebSecurityEnablerConfiguration.class,
SecurityDataConfiguration.class})
public class SecurityAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(AuthenticationEventPublisher.class)
    public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
        return new DefaultAuthenticationEventPublisher(publisher);
    }

}

SecurityAutoConfigurationDefaultAuthenticationEventPublisher 作为默认的 AuthenticationEventPublisher 注入Spring Ioc容器。

  • DefaultAuthenticationEventPublisher
public class DefaultAuthenticationEventPublisher implements AuthenticationEventPublisher,
          ApplicationEventPublisherAware {
     private final Log logger = LogFactory.getLog(getClass());

     private ApplicationEventPublisher applicationEventPublisher;
     private final HashMap<String, Constructor<? extends AbstractAuthenticationEvent>> exceptionMappings = new HashMap<>();

     public DefaultAuthenticationEventPublisher() {
          this(null);
     }

     public DefaultAuthenticationEventPublisher(
               ApplicationEventPublisher applicationEventPublisher) {
          this.applicationEventPublisher = applicationEventPublisher;

          addMapping(BadCredentialsException.class.getName(),
                    AuthenticationFailureBadCredentialsEvent.class);
          addMapping(UsernameNotFoundException.class.getName(),
                    AuthenticationFailureBadCredentialsEvent.class);
          addMapping(AccountExpiredException.class.getName(),
                    AuthenticationFailureExpiredEvent.class);
          addMapping(ProviderNotFoundException.class.getName(),
                    AuthenticationFailureProviderNotFoundEvent.class);
          addMapping(DisabledException.class.getName(),
                    AuthenticationFailureDisabledEvent.class);
          addMapping(LockedException.class.getName(),
                    AuthenticationFailureLockedEvent.class);
          addMapping(AuthenticationServiceException.class.getName(),
                    AuthenticationFailureServiceExceptionEvent.class);
          addMapping(CredentialsExpiredException.class.getName(),
                    AuthenticationFailureCredentialsExpiredEvent.class);
          addMapping(
                    "org.springframework.security.authentication.cas.ProxyUntrustedException",
                    AuthenticationFailureProxyUntrustedEvent.class);
     }

     public void publishAuthenticationSuccess(Authentication authentication) {
          if (applicationEventPublisher != null) {
               applicationEventPublisher.publishEvent(new AuthenticationSuccessEvent(
                         authentication));
          }
     }

     public void publishAuthenticationFailure(AuthenticationException exception,
               Authentication authentication) {
          Constructor<? extends AbstractAuthenticationEvent> constructor = exceptionMappings
                    .get(exception.getClass().getName());
          AbstractAuthenticationEvent event = null;

          if (constructor != null) {
               try {
                    event = constructor.newInstance(authentication, exception);
               }
               catch (IllegalAccessException | InvocationTargetException | InstantiationException ignored) {
               }
          }

          if (event != null) {
               if (applicationEventPublisher != null) {
                    applicationEventPublisher.publishEvent(event);
               }
          }
          else {
               if (logger.isDebugEnabled()) {
                    logger.debug("No event was found for the exception "
                              + exception.getClass().getName());
               }
          }
     }

     public void setApplicationEventPublisher(
               ApplicationEventPublisher applicationEventPublisher) {
          this.applicationEventPublisher = applicationEventPublisher;
     }

     
     @SuppressWarnings({ "unchecked" })
     public void setAdditionalExceptionMappings(Properties additionalExceptionMappings) {
          Assert.notNull(additionalExceptionMappings,
                    "The exceptionMappings object must not be null");
          for (Object exceptionClass : additionalExceptionMappings.keySet()) {
               String eventClass = (String) additionalExceptionMappings.get(exceptionClass);
               try {
                    Class<?> clazz = getClass().getClassLoader().loadClass(eventClass);
                    Assert.isAssignable(AbstractAuthenticationFailureEvent.class, clazz);
                    addMapping((String) exceptionClass,
                              (Class<? extends AbstractAuthenticationFailureEvent>) clazz);
               }
               catch (ClassNotFoundException e) {
                    throw new RuntimeException("Failed to load authentication event class "
                              + eventClass);
               }
          }
     }

     private void addMapping(String exceptionClass,
               Class<? extends AbstractAuthenticationFailureEvent> eventClass) {
          try {
               Constructor<? extends AbstractAuthenticationEvent> constructor = eventClass
                         .getConstructor(Authentication.class, AuthenticationException.class);
               exceptionMappings.put(exceptionClass, constructor);
          }
          catch (NoSuchMethodException e) {
               throw new RuntimeException("Authentication event class "
                         + eventClass.getName() + " has no suitable constructor");
          }
     }
}

查看源码我们发现,该类内置了一个HashMap<String, Constructor<? extends AbstractAuthenticationEvent>> 用于维护认证异常处理和对应异常事件处理逻辑的映射关系,比如坏的凭证异常 BadCredentialsException 对应认证失败,凭据错误事件AuthenticationFailureBadCredentialsEvent 也就是说当发生不同认证的异常时,Security会采用不同的处理策略。

以上是关于Security中的认证事件发布器的主要内容,如果未能解决你的问题,请参考以下文章

spring security 的jwt认证以及原理解析

多个WebSecurityConfigurerAdapters:spring security中的JWT认证和表单登录

Spring Security(新版本)实现权限认证与授权

在同一个片段中实现多个事件监听器 - Android

Android - 片段中的联系人选择器

Spring-Security:认证后调用方法