为什么 Spring boot Security 基本身份验证很慢?

Ket*_*tan 6 java spring-security bcrypt basic-authentication spring-boot

我有一个 Spring boot 2.0.1 服务,我向其中添加了使用 BCrypt 进行哈希处理的基本身份验证。但这项服务在添加基本身份验证之前平均需要 400 毫秒,现在需要 1 秒以上。我正在使用用户详细信息服务,它在哈希映射中查找发送的用户名并返回用户详细信息。我尝试将 BCrypt 轮数减少到 4,但这并没有产生太大影响。

早些时候,我启用了无状态身份验证,后来将其禁用,但性能仍然很差。该服务托管在 Docker 容器中。

以下是我的安全配置。

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private UserDetailsService userDetailsService;

    @Autowired
    public SecurityConfig(UserDetailsServiceImpl service) {
        this.userDetailsService = service;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        Map encoders = new HashMap<>();
        encoders.put(BCRYPT_ID, new BCryptPasswordEncoder(BCRYPT_ROUNDS));
        return new DelegatingPasswordEncoder(BCRYPT_ID,encoders);
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.cors()
            .and()
            .csrf().disable()
            .httpBasic();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

        auth.userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我遗漏了什么,请告诉我。

更新:我运行了基准测试,看起来 BCrypt 编码器使应用程序变慢。我发现一些 Stack Overflow 答案讨论 BCrypt 哈希计算是阻塞调用。

关于硬件:服务主机采用Intel Xeon E5,16GB内存。它托管 4 个 Spring boot 服务,每个服务分配 2 GB 空间,在 Docker 容器内运行。

Div*_*gya 7

因此,在搜索了大量有关 BCrypt 编码和解码的信息后,我终于找到了保持 Spring Boot 项目性能的解决方案,而不会因 BCrypt 编码和解码而造成重大延迟。

因此 BCrypt 哈希算法在某些轮次上起作用。您在 BCrypt 编码中使用的轮数越多,您的项目将消耗更多的空间和内存来进行编码和解码(密码)。

话虽如此,我还想提一下,编码密码时使用的轮数越多,密码就越安全。

我们大多数人都会使用下面的代码行在我们的代码中生成 bycrypt 凭证

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16); // here no of rounds is 16
String result = encoder.encode("password");
system.out.println("encoded password" + result );
Run Code Online (Sandbox Code Playgroud)

该java代码用于生成BCrypt编码密码的轮数为16,这太高了。有助于实现时间、内存和安全性之间平衡的标准整数是 10。

因此,如果我们必须更改下面的 BCrypt 编码中的轮数,则您需要设置

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(10);
Run Code Online (Sandbox Code Playgroud)

当我使用 16 轮时,我花了 6 秒才能通过基本身份验证访问服务(编码和解码 BCrypt 密码平均需要 3 到 4 秒)

当我使用 10 发子弹时,我能够在平均 1 到 1.5 秒之间的时间内击中服务

您应该选择的回合数没有标准。您应该在应用程序中使用性能方面可容忍的最大轮数。轮数是一个减速因素,您使用它的基础是在正常使用条件下,这种减速对您的影响可以忽略不计(用户不会看到它,额外的CPU成本并不意味着购买更大的服务器,并且很快)。这在很大程度上取决于操作环境:涉及哪些机器、每秒有多少用户身份验证......因此没有一刀切的响应。


Ren*_*ink 5

不久前,我在为客户开发一个项目时遇到了同样的服务器问题。我用jvisualvm分析了该服务。结果真的很明显:

bcrypt jvisualvm 采样

我使用 jmeter 对我的服务 api 发出了 5000 个请求。我还为每次测量重新启动服务,并始终在每次测量之前运行 5000 个样本来预热 JIT 编译器。

我的结果是这样的:

Samples     |    avg [ms]|    min [ms]|    max [ms]| throughput [requests/s]
------------+------------+------------+------------+------------------------
    5000    |         125|          71|         363|                    78.1    
------------+------------+------------+------------+------------------------
Run Code Online (Sandbox Code Playgroud)

我分析的服务是一个非常简单的数据访问服务,仅通过 REST 接口提供对数据库的访问。因此我可以通过 jmeter 测试了解 bcrypt 的影响有多大。

然后,我使用不同的密码编码器(例如 MD5、SHA-256、bcrypt、scrypt 和 pbkdf2)运行多个 jmeter 测试。

以下是我的一些测量结果,可能会帮助您做出决定或进行进一步调查:

algorithm   |    avg [ms]|    min [ms]|    max [ms]| throughput [requests/s]
------------+------------+------------+------------+------------------------
MD5         |           5|           1|          61|                    1443    
------------+------------+------------+------------+------------------------
SHA-256     |           5|           2|          34|                    1464    
------------+------------+------------+------------+------------------------
bcrypt      |         125|          71|         363|                    78.1    
------------+------------+------------+------------+------------------------
scrypt      |         122|          54|        1232|                    79.2    
------------+------------+------------+------------+------------------------
pbkdf2      |         833|         421|        1606|                      12    
------------+------------+------------+------------+------------------------
Run Code Online (Sandbox Code Playgroud)

我分析的服务没有暴露在互联网上。它运行在安全的网络区域,性能比安全性更重要。因此我们决定使用 SHA-256 代替 bcrypt。

我觉得:

  1. 如果性能和安全性很重要:选择最安全的算法并以可以通过添加更多机器来扩展的方式设计您的服务。
  2. 如果性能比安全性更重要:选择足够快以满足您的要求的算法。就我而言,SHA-256。

但你应该始终努力实现 1。

我所显示的测量结果应被解释为相对值,而不是被视为绝对值。

PS:我知道最好执行单独测试加密 api 的性能测试,而不是通过服务的 REST 接口进行测试,但我还没有时间设置此类测试。希望我将来有更多的时间来调查它,如果是这样,我会回来更新这个答案。


Mar*_*cki 3

您正在创建一个BCryptPasswordEncoder实例而不传递SecureRandom. 因此,每次您encode的密码BCrypt都会创建一个新实例SecureRandom(这是一个 CPU 密集型操作,并且需要生成盐)。你可以检查一下BCrypt.class源代码。

public static String gensalt(int log_rounds) {
    return gensalt(log_rounds, new SecureRandom());
}

public static String gensalt() {
    return gensalt(10);
}

public static String gensalt(int log_rounds, SecureRandom random) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

BCryptPasswordEncoder.class

    if (this.random != null) {
        salt = BCrypt.gensalt(this.strength, this.random);
    } else {
        salt = BCrypt.gensalt(this.strength);
    }
Run Code Online (Sandbox Code Playgroud)

因此,请使用public BCryptPasswordEncoder(int strength, SecureRandom random)构造函数,但请记住,SecureRandom每次创建实例比始终使用同一个实例更安全。

  • 作为补充说明。安全的哈希算法必须很慢,因为这种慢可以防止暴力攻击。 (3认同)