跳到主要内容
  1. Posts/

生成 ClientId 与 ClientSecret:从随手一写到性能优化

··326 字·2 分钟·

在对接 API 网关(如 Kong、Nginx)或开发 OAuth2 服务时,我们经常需要为应用生成唯一的 ClientId 和高强度的 ClientSecret

乍看之下,这似乎是一个非常简单的需求:UUID 生成 ID,随机数生成 Secret。但如果在高并发场景下,代码写得不够讲究,可能会埋下性能隐患。

今天记录一下我在项目中对一个工具类的重构过程,主要涉及 SecureRandom 的正确使用姿势和字符串操作的细节优化。

原始版本 (V1.0) #

这是最初的实现版本,逻辑跑通完全没问题,但在 Code Review 时发现了一些改进空间。

package org.xwls.common.kong.utils;

import java.security.SecureRandom;
import java.util.Base64;
import java.util.UUID;

public class KongUtils {

    /**
     * 生成 ClientId
     */
    public static String generateClientId(String prefix) {
        // 问题点1:replaceAll 使用了正则,效率一般
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        return (prefix != null ? prefix : "") + uuid;
    }

    /**
     * 生成 ClientSecret
     */
    public static String generateClientSecret(int length) {
        // 问题点2:每次调用都 new 一个 SecureRandom,高并发下性能杀手
        SecureRandom secureRandom = new SecureRandom();
        byte[] randomBytes = new byte[length];
        secureRandom.nextBytes(randomBytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes);
    }
    
    // 省略重载方法...
}

优化分析:为什么要改? #

经过分析,V1.0 版本存在以下三个主要问题:

1. 性能隐患:SecureRandom 的实例化 #

generateClientSecret 方法中,每次调用都执行了 new SecureRandom()

  • 原因SecureRandom 的初始化成本很高,因为它需要从操作系统获取足够的熵(Entropy)来保证随机性。
  • 后果:在高并发场景下,频繁创建实例会导致显著的性能下降,甚至可能因为熵池耗尽导致阻塞。
  • 解决SecureRandom 是线程安全的,应该声明为 static final 全局复用。

2. 效率问题:replaceAll vs replace #

在处理 UUID 去除横杠时,使用了 replaceAll("-", "")

  • 原因replaceAll 的第一个参数是正则表达式。即使只是替换一个字符,JVM 也要启动正则引擎。
  • 解决:使用 replace("-", "")。在 Java 中,replace 接受字符串参数但不使用正则,直接进行字符序列替换,效率更高。

3. 代码规范:工具类的封装 #

  • 原因:原始类是 public 的且有默认构造函数,意味着外部可以 new KongUtils(),这对于全是静态方法的工具类来说是不合理的。
  • 解决:添加 private 构造函数,并将类声明为 final

优化版本 (V2.0) #

结合上述分析,优化后的代码如下:

package org.xwls.common.kong.utils;

import java.security.SecureRandom;
import java.util.Base64;
import java.util.UUID;

/**
 * Kong 工具类
 * 用于生成 ClientId 和 ClientSecret
 */
public final class KongUtils {

    /**
     * 优化1:全局复用 SecureRandom 实例
     * 避免每次调用重新初始化的昂贵开销,SecureRandom 是线程安全的。
     */
    private static final SecureRandom SECURE_RANDOM = new SecureRandom();

    /**
     * 优化2:私有构造方法,防止工具类被实例化
     */
    private KongUtils() {
        throw new UnsupportedOperationException("Utility class cannot be instantiated");
    }

    /**
     * 生成 ClientId
     * @param prefix ClientId 的前缀,可以为空
     */
    public static String generateClientId(String prefix) {
        // 优化3:使用 replace 替代 replaceAll,避免正则表达式的开销
        String uuid = UUID.randomUUID().toString().replace("-", "");
        
        if (prefix == null || prefix.isEmpty()) {
            return uuid;
        }
        return prefix + uuid;
    }

    public static String generateClientId() {
        return generateClientId(null);
    }

    /**
     * 生成 ClientSecret
     * @param length ClientSecret 的字节长度,推荐32
     */
    public static String generateClientSecret(int length) {
        // 优化4:增加参数防御性校验
        if (length <= 0) {
            throw new IllegalArgumentException("Length must be greater than 0");
        }

        byte[] randomBytes = new byte[length];
        
        // 直接使用静态实例生成随机数
        SECURE_RANDOM.nextBytes(randomBytes);

        // 使用 URL-safe 的 Base64 编码,适合作为 API Key
        return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes);
    }

    public static String generateClientSecret() {
        return generateClientSecret(32);
    }
}

总结 #

这次重构虽然代码量不大,但涉及的点都很实用:

  1. SecureRandom:在涉及安全随机数生成时,切记单例复用
  2. 字符串操作:简单的字符替换,优先用 replace 而非 replaceAll
  3. 工具类规范:记得 final 类 + private 构造器。