为多个客户端保持 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
我有多个应用程序(每个都可以被认为是一个 资源服务器 )和一个 授权服务器 。 授权服务器 在成功验证时发出 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