为多个客户端保持 JWT 分离的更好方法

Better way to keep JWTs separate for multiple clients

我有多个应用程序(每个都可以被认为是一个 资源服务器 )和一个 授权服务器 授权服务器 在成功验证时发出 JWT。在此 RFC 中,我了解了 aud 声明,这是通知客户令牌不适合它的好方法,即接收者可以检查 JWT 中的 aud 声明并丢弃JWT 不适合它。

但是,我想知道的是如何为每个 资源服务器 维护一个单独的 key/secret ,这些服务器对其他资源服务器隐藏但 [=30] =]授权服务器.

我想在资源服务器上保留key/secret的原因是因为我想避免每次都向授权服务器发送请求来验证令牌。(性能命中).

我想单独保留一个 key/secret 的原因是安全。

我正在使用 this jsonWebToken 库。

可以这样做吗?

首先你可以使用缓存来避免每次都检查授权服务器。您可以在令牌生命周期内缓存它。

然后您可以更改为 RS256 并在资源服务器可以访问的端点上公开 public 密钥,这些资源服务器将在启动时缓存并在资源服务器中进行检查。但为了性能,缓存验证结果也很好。

关于如何处理签名和验证的快速示例:

public class JwtFactory {

    private Map<String, Key> keys = new HashMap<>();

    public JwtFactory(String keyStore, String keyStorePassword, Map<String, String> resourcesPassword) {
        try {
            KeyStore keystore = KeyStore.getInstance("PKCS12");
            try (InputStream is = Files.newInputStream(Paths.get("jwt.pkcs12"))) {
                keystore.load(is, keyStorePassword.toCharArray());
            }
            for (Map.Entry<String, String> resource : resourcesPassword.entrySet()) {
                keys.put(resource.getKey(), keystore.getKey(resource.getKey(), resource.getValue().toCharArray()));
            }
        } catch (Exception e) {
            throw new RuntimeException("Unable to load key", e);
        }
    }

    public String createToken(String resourceId, String subject, String id, Map<String, Object> claims) {
        return Jwts.builder()
                .setSubject(subject)
                .setId(id)
                .setIssuedAt(Date.from(Instant.ofEpochSecond(System.currentTimeMillis() - 3600)))
                .setExpiration(Date.from(Instant.ofEpochSecond(System.currentTimeMillis() + 3600)))
                .addClaims(claims)
                .signWith(SignatureAlgorithm.RS256, keys.get(resourceId))
                .compact();
    }

    public static void main(String[] args) throws Exception {
        final Map<String, String> resources = new HashMap<>();
        resources.put("resource1", "password");
        resources.put("resource2", "password");

        final Map<String, Object> claims = new HashMap<>();
        claims.put("name", "John Doe");
        claims.put("admin", true);


        JwtFactory factory = new JwtFactory("jwt.pkcs12", "password", resources);
        String token1 = factory.createToken("resource1", "1234567890", "a8070da2-3497-4a51-a932-daa9ae53bddd", claims);
        String token2 = factory.createToken("resource2", "1234567890", "a8070da2-3497-4a51-a932-daa9ae53bddd", claims);

        System.out.println(token1);
        System.out.println(token2);

        final String resource1Public = new String(Files.readAllBytes(Paths.get("resource1.pem")), StandardCharsets.ISO_8859_1)
            .replaceAll("-----BEGIN PUBLIC KEY-----\n", "")
            .replaceAll("-----END PUBLIC KEY-----", "");

        final X509EncodedKeySpec specKey1 = new X509EncodedKeySpec(Base64.decodeBase64(resource1Public.getBytes(StandardCharsets.ISO_8859_1)));

        Jwt jwt = Jwts.parser().setSigningKey(KeyFactory.getInstance("RSA").generatePublic(specKey1)).parse(token1);
        System.out.println("Validation Ok with resource 1");
        System.out.println(jwt);
        try {
            Jwts.parser().setSigningKey(KeyFactory.getInstance("RSA").generatePublic(specKey1)).parse(token2);
        } catch (Exception e) {
            System.out.println("Validation fail with resource 2");
        }
    }
}

生成密钥对:

keytool -genkeypair -alias resource1 -keyalg RSA -keypass password -keystore jwt.pkcs12 -storepass password

提取public密钥:

keytool -list -rfc -keystore jwt.pkcs12 -alias resource1 | openssl x509 -inform pem -pubkey -noout