Authentication Events
Spring Security对于成功或失败的每个身份验证,分别会触发AuthenticationSuccessEvent或AbstractAuthenticationFailureEvent。要侦听这些事件,你必须首先发布一个AuthenticationEventPublisher。Spring Security提供了一个默认的AuthenticationEventPublisher实现,DefaultAuthenticationEventPublisher。
今天讲一下如何添加一个自定义的Exception Mapping。
Adding Exception Mappings
在讲之前我们看一下DefaultAuthenticationEventPublisher中已经提供了哪些Exception Mappings。
Exception | Event |
---|---|
BadCredentialsException | AuthenticationFailureBadCredentialsEvent |
UsernameNotFoundException | AuthenticationFailureBadCredentialsEvent |
BadCredentialsException | AuthenticationFailureBadCredentialsEvent |
AccountExpiredException | AuthenticationFailureExpiredEvent |
ProviderNotFoundException | AuthenticationFailureProviderNotFoundEvent |
DisabledException | AuthenticationFailureDisabledEvent |
LockedException | AuthenticationFailureLockedEvent |
AuthenticationServiceException | AuthenticationFailureServiceExceptionEvent |
CredentialsExpiredException | AuthenticationFailureCredentialsExpiredEvent |
InvalidBearerTokenException | AuthenticationFailureBadCredentialsEvent |
也就是说在默认的情况下Spring Security在身份验证异常时将会触发上述对应的事件。我们针对上面的事件可以进行继承实现自己的一些逻辑。
但如果我们要添加一个自定义的Exception Mapping我们可以这样做:
假设我们定义一个密码为空的异常,并且我认为它属于AuthenticationFailureBadCredentialsEvent的一种。
定义CustomNullPasswordException类,我这里继承的是UsernameNotFoundException ,UsernameNotFoundException实际上继承的是AuthenticationException,这里因为自己的逻辑关系,我想在UserDetailsService的实现类里针对密码为空时抛出自己自定义的异常,主要逻辑就是在UserDetailsService 的loadUserByUsername(String username) 接口里根据username查询用户信息然后判断密码是否为空。你不必按我的逻辑来你可以直接继承AuthenticationException 根据自己的实际情况来定义异常。
public class CustomNullPasswordException extends UsernameNotFoundException {
private static final long serialVersionUID = 1L;
private Map<String, Object> map;
public CustomNullPasswordException(String msg) {
super(msg);
}
public CustomNullPasswordException(Map<String, Object> map, String msg) {
super(msg);
this.map = map;
}
public Map<String, Object> getMap() {
return map;
}
public void setMap(Map<String, Object> map) {
this.map = map;
}
}
UserDetailsServiceImpl 这里我就一笔代理,知道这个异常是在哪抛出的就行了。
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String agentCode) throws UsernameNotFoundException {
......
if(null == userDetails.getPassword() || "" == userDetails.getPassword()){
throw new CustomNullPasswordException(map, "密码为空,请设置密码!");
}
......
}
}
然后我们添加Exception 和 Event 的映射关系,我这里是通过Properties 对象的put方法把 CustomNullPasswordException 全限定类名,和AuthenticationFailureBadCredentialsEvent全限定类名 作为key和value 添加到Properties中然后在通过DefaultAuthenticationEventPublisher 的setAdditionalExceptionMappings方法进行映射的,下面是大体的代码实例。如果抛出CustomNullPasswordException异常就会对应到AuthenticationFailureBadCredentialsEvent事件上来。
@Configuration
@EnableWebSecurity(debug = false)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
......
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
/**
* 默认DefaultAuthenticationEventPublisher添加事件
*
* @return
*/
public AuthenticationEventPublisher authenticationEventPublisher() {
DefaultAuthenticationEventPublisher event = new DefaultAuthenticationEventPublisher(applicationEventPublisher);
Properties additionalExceptionMappings = new Properties();
// 处理密码为空的情况
additionalExceptionMappings.put("com.xxxxx.CustomNullPasswordException",
"org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent");
// 设置自定义异常事件
event.setAdditionalExceptionMappings(additionalExceptionMappings);
return event;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationEventPublisher(authenticationEventPublisher());
}
......
}
当然官方也给了Adding Exception Mappings的介绍,大家也可以参考官方的方式来配置。
https://docs.spring.io/spring-security/site/docs/current/reference/html5/#adding-exception-mappings
配置例子如下:
@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;
}