如何在Grails Spring Security插件中自定义默认认证事件发布器的异常映射
背景
项目中继承DaoAuthenticationProvider实现邮箱OTP双因素认证,自定义了BadOtpException、ExpiredOtpException、ConsumedOtpException等异常(均为BadCredentialsException子类),认证流程正常,但认证失败事件不再发布。
问题核心
替换自定义认证提供者后,原DaoAuthenticationProvider会触发的认证事件无法正常发布。
原因分析
Grails Spring Security Core插件依赖DefaultAuthenticationEventPublisher发布事件,该类通过构造方法中配置的「异常-事件」映射关系决定要发布的事件。由于自定义异常不在默认映射中,导致认证失败时无法触发对应的事件。
尝试过的无效方案
继承DefaultAuthenticationEventPublisher添加额外映射,但注册Bean后无效果,构造方法的打印语句无输出,疑似Bean未成功注册:
自定义发布器代码:
class CustomAuthenticationEventPublisher extends DefaultAuthenticationEventPublisher { CustomAuthenticationEventPublisher() { } CustomAuthenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { super(applicationEventPublisher) println('CustomAuthenticationEventPublisher') Properties exceptionMappings = new Properties() exceptionMappings.setProperty(BadOtpException.class.name, AuthenticationFailureBadCredentialsEvent.class.name) super.setAdditionalExceptionMappings(exceptionMappings) } }
resources.groovy注册代码:
beans = { authenticationEventPublisher(CustomAuthenticationEventPublisher) }
1. 修复自定义发布器的Bean注册问题
Grails Spring Security插件默认已注册authenticationEventPublisher Bean,直接覆盖需要明确配置优先级。修改resources.groovy,强制替换原有Bean:
beans = { authenticationEventPublisher(CustomAuthenticationEventPublisher) { bean -> bean.autowire = true bean.destroyMethod = 'destroy' } }
同时优化自定义发布器代码,统一初始化逻辑并确保无参/有参构造都能完成映射配置:
class CustomAuthenticationEventPublisher extends DefaultAuthenticationEventPublisher { CustomAuthenticationEventPublisher() { super() initExceptionMappings() } CustomAuthenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { super(applicationEventPublisher) initExceptionMappings() } private void initExceptionMappings() { Properties exceptionMappings = new Properties() // 为所有自定义OTP异常添加事件映射 exceptionMappings.setProperty(BadOtpException.class.name, AuthenticationFailureBadCredentialsEvent.class.name) exceptionMappings.setProperty(ExpiredOtpException.class.name, AuthenticationFailureBadCredentialsEvent.class.name) exceptionMappings.setProperty(ConsumedOtpException.class.name, AuthenticationFailureBadCredentialsEvent.class.name) setAdditionalExceptionMappings(exceptionMappings) // 若需要覆盖默认映射而非追加,可调用setExceptionMappings()传入合并后的配置 } }
2. 更简洁的方案:直接修改默认发布器的映射
无需继承类,直接在Spring配置中为默认的authenticationEventPublisher注入额外映射:
beans = { authenticationEventPublisher(DefaultAuthenticationEventPublisher) { bean -> def additionalMappings = new Properties() additionalMappings.setProperty(BadOtpException.class.name, AuthenticationFailureBadCredentialsEvent.class.name) additionalMappings.setProperty(ExpiredOtpException.class.name, AuthenticationFailureBadCredentialsEvent.class.name) additionalMappings.setProperty(ConsumedOtpException.class.name, AuthenticationFailureBadCredentialsEvent.class.name) bean.additionalExceptionMappings = additionalMappings } }
这种方式复用默认发布器,避免继承带来的Bean冲突问题,实现更简洁。
3. 验证Bean是否生效
在BootStrap.groovy中添加验证代码,确认发布器是否正确加载:
class BootStrap { def authenticationEventPublisher def init = { servletContext -> println("当前加载的认证事件发布器:${authenticationEventPublisher.getClass().name}") } }
启动项目后查看控制台输出,确认是否为自定义类或已修改映射的默认类。
内容的提问来源于stack exchange,提问作者Manish Kapoor

