Spring Security修改默认的Bad Credentials提示 及其 原理(Spring Boot 2.0)

 

Spring Security的各种提示内容来源于Spring的国际化资源文件类MessageSource,当我们想要修改默认提示时可能理所当然的会想到通过自己配置一个MessageSource的方式来实现,但是这种做法并不适用于Spring Security,原因如下,我们跟踪它的代码看看:

 

Spring Security的登录密码验证有一个默认的实现类叫做DaoAuthenticationProvider,如下:

 

它的登录验证方法是这个additionalAuthenticationChecks,如下:

 

可以看到当密码校验未通过时,它通过messages.getMessage()这个方法来获取提示信息,这个messages对象是一个MessageSourceAccessor,来源于它的一个抽象父类AbstractUserDetailsAuthenticationProvider,如下:

 

可以看到这个messages对象由SpringSecurityMessageSource.getAccessor()初始化 ,那么我们继续看看这个SpringSecurityMessageSource,如下:

 

到这步就很明了了, SpringSecurityMessageSource实际上继承自ResourceBundleMessageSource,它的getAccessor()是一个static方法。方法里new了一个自己,然后设置basename到了org.springframework.security.messages这个路径,这个路径实际上就是security-core包里面的一个路径。然后再new了一个新的MessageSourceAccessor。所以无论我们怎么配置我们自己的MessageSource都没有作用,因为Spring Security最后用到的这个MessageSourceAccessor是它自己new出来的,而不是Spring托管的

 

那么,我们要怎样去修改这个Spring Security默认的提示呢?这里我的一个思路是:

① 我们自己去写一个AuthenticationProvider去继承这个Spring Security的登录校验父类DaoAuthenticationProvider

② 自己去配置一个MessageSource

③ 把我们自己配置的MessageSource赋值给父类DaoAuthenticationProvider的messages对象

④ 把自己的AuthenticationProvider设置给Spring Security

 

下面我们来看看具体步骤:

先写个简单的AuthenticationProvider(这里我的叫LoginAuthenticationProvider)如下:

@Component
public class LoginAuthenticationProvider extends DaoAuthenticationProvider{
	
	@Autowired
	private JdbcUserDetailsService jdbcUserDetailsService;
	
	@Autowired
	private PasswordEncoder passwordEncoder;
	
	@Autowired
	private void setJdbcUserDetailsService() {
		setUserDetailsService(jdbcUserDetailsService);
	}
	
	@PostConstruct
	public void initProvider() {
		ReloadableResourceBundleMessageSource localMessageSource = new ReloadableResourceBundleMessageSource();
		localMessageSource.setBasenames("messages_zh_CN");
		messages = new MessageSourceAccessor(localMessageSource);
	}
	
	@Override
	protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		if (authentication.getCredentials() == null) {
			logger.debug("Authentication failed: no credentials provided");

			throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}

		String presentedPassword = authentication.getCredentials().toString();

		if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
			logger.debug("Authentication failed: password does not match stored value");

			throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
	}
	
}

注意LoginAuthenticationProvider需要一个UserDetailsService,否则会启动失败,这里我用setter注入了一个我自己的UserDetailsService叫做JdbcUserDetailsService。记住一定要给自己的JdbcUserDetailsService打上@Component和@Order(0)注解,否则Spring在加载的时候有可能会先加载我们自己写的LoginAuthenticationProvider然后找不到JdbcUserDetailsService导致启动失败。

 

然后用@PostConstruct去写一个简单的初始化,在初始化的时候将一个我们自己配置的MessageSource(这里我使用的是ReloadableResourceBundleMessageSource)赋值给父类的messages对象。注意setBasenames()方法会自己查找src/main/resources下的.properties文件。这里我新建了一个文件叫messages_zh_CN.properties放在了src/main/resources下面,并且修改了里面的默认提示,如图:

最后在Spring Security的WebSecurityConfigurerAdapter里面配置我们自己的 LoginAuthenticationProvider即可,代码如下:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	
    @Autowired
    private LoginAuthenticationProvider loginAuthenticationProvider;
	
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .anyRequest().authenticated()
            .and().csrf().disable();
    }
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    	auth.authenticationProvider(loginAuthenticationProvider);
    	super.configure(auth);
    }
	
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
    	return super.authenticationManagerBean();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {   	
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}

 

本文参考:

https://www.jianshu.com/p/955e30866121

https://www.jianshu.com/p/46a4355ad0a3?from=groupmessage

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值