我最近将Spring Boot应用程序从v1.5更新到了v2.0.3。 这是一个应用程序,其方法公开为REST端点,并通过基本HTTP身份验证进行保护。用户名和密码硬编码在应用程序加载的属性文件中。
自更新以来,响应时间增加了近200毫秒,而处理请求的时间的98%花费在BasicAuthenticationFilter.doFilter()中。
更具体地说,花费时间对请求中的密码进行编码以将其与配置提供的密码进行比较。
以下是SecurityConfig类的摘录:
@EnableWebSecurity
@PropertySource("classpath:auth/auth.properties")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${user.name}")
private String userName;
@Value("${user.password}")
private String userPassword;
@Value("${user.roles}")
private String[] userRoles;
@Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
UserDetails user = User.withUsername(userName).password(encoder.encode(userPassword)).roles(userRoles).build();
auth.inMemoryAuthentication().withUser(user);
}
@Override
protected void configure(final HttpSecurity http) throws Exception {
//make sure that the basic authentication header is always required
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
//set the correct authentication entry point to make sure the 401 response is in JSON format
http.exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPoint());
//enable http basic authentication
http.httpBasic();
//make sure authentication is required for all requests except /actuator/info
http.authorizeRequests().antMatchers("/actuator/info").permitAll().anyRequest().authenticated();
http.authorizeRequests().antMatchers("/actuator/**").hasAnyRole("MONITOR", "ACTUATOR");
//disable the form login since this is a REST API
http.formLogin().disable();
//disable csrf since this is a REST API
http.csrf().disable();
}
}
为验证这是由于Spring Boot更新引起的,我在本地恢复了更改并运行了一些测试。响应时间除以4。
我已经尝试了一些方法,但是都没有改善响应时间:
我可以做些什么来加快身份验证筛选器的速度吗?
答案 0 :(得分:4)
bcrypt是一种故意缓慢的哈希函数。虽然这种缓慢 密码哈希听起来很矛盾,这不是因为 好人和坏人都放慢了脚步。这些天最 密码攻击是蛮力字典攻击的某种变体。 这意味着攻击者将通过以下方式尝试许多候选密码 就像好家伙一样对它们进行哈希处理。如果有匹配项, 密码已被破解。
但是,如果坏人每次尝试的速度都变慢, 数以百万计的尝试,常常达到 阻止攻击。但是,好人可能不会注意到 他们尝试登录时进行一次尝试。
因此基本上,Brypt编码器内部有200ms的额外处理时间是有意的,并且是安全的一部分 通过加快速度(例如使用缓存),可以降低应用程序的安全级别。
-编辑:
顺便说一句。仍然有一种快速的 AND 安全方法来评估密码是否匹配:使用某些缓存服务(例如此处的其他答案),但是仅将匹配的值存储在缓存!!!这样,如果用户提供了一个有效的密码,它将仅一次被缓慢地评估一次-第一次-但随后的所有登录都会很快。但是,如果攻击者尝试使用蛮力,那么他的所有尝试将花费200毫秒。
答案 1 :(得分:0)
实际上,一种解决方案是使用另一个密码编码器,例如在SecurityConfig类中:
@Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder());
}
@Bean
public UserDetailsService userDetailsService() {
PasswordEncoder encoder = passwordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
UserDetails user = User.withUsername(userName).password(encoder.encode(password)).roles(userRoles).build();
manager.createUser(user);
return manager;
}
private PasswordEncoder passwordEncoder() {
// return a fast password encoder
}
现在,要使用哪种加密算法是另一个问题。尽管bcrypt似乎是一个安全的选择,但我想知道是否有必要考虑将密码存储在内存中并因此难以访问。
另请参见
答案 2 :(得分:0)
我有一个类似的问题,并通过将缓存包装在密码编码器周围来解决(可以在前面的答案中使用)。假设经常使用相同的用户ID,这应该可以解决问题。它使用Caffeine缓存库。
private static class CachingPasswordEncoder implements PasswordEncoder {
private final PasswordEncoder encoder = new BCryptPasswordEncoder();
private final Cache<CharSequence, String> encodeCache = Caffeine.newBuilder().build();
private final Cache<PasswordMatchKey, Boolean> matchCache = Caffeine.newBuilder().build();
@Override
public String encode(final CharSequence rawPassword) {
return encodeCache.get(rawPassword, s -> encoder.encode(rawPassword));
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
//noinspection ConstantConditions
return matchCache.get(new PasswordMatchKey(rawPassword, encodedPassword),
k -> encoder.matches(rawPassword, encodedPassword));
}
}
private static class PasswordMatchKey {
private final CharSequence rawPassword;
private final String encodedPassword;
public PasswordMatchKey(CharSequence rawPassword, String encodedPassword) {
this.rawPassword = rawPassword;
this.encodedPassword = encodedPassword;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PasswordMatchKey that = (PasswordMatchKey) o;
return Objects.equals(rawPassword, that.rawPassword) &&
Objects.equals(encodedPassword, that.encodedPassword);
}
@Override
public int hashCode() {
return Objects.hash(rawPassword, encodedPassword);
}
}
答案 3 :(得分:0)
加密的密码编码器给您的服务带来延迟和CPU负载。对于固定密码/从prop文件加载等情况,您可以降低安全级别以提高性能。
您可以像这样更新您的配置方法:
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception
{
// we accept low level security for password encoding for better performance
PasswordEncoder encoder = NoOpPasswordEncoder.getInstance();
InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> configurer = auth.inMemoryAuthentication();
configurer.passwordEncoder(encoder);
UserDetails user = User.withUsername(userName).password(encoder.encode(userPassword)).roles(userRoles).build();
configurer.withUser(user);
}
它将直接使用已弃用的NoOpPasswordEncoder,并且不会加载任何基于加密的编码器。不要担心过时警告,因为它还指出他们不打算删除该类,只是警告您有关安全性的问题。
由于您正在从prop文件中加载密码,因此您已经不安全了:)
答案 4 :(得分:0)
Spring Security default behaviour changed
在 5 之前,默认行为只是普通的旧 base64,现在是 bcrypt。使用 NoOpPasswordEncoder 将其更改为旧行为