java.lang.IllegalArgumentException:如果指定的 JWT 是数字签名的,则必须指定签名密钥
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/41661821/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
java.lang.IllegalArgumentException: A signing key must be specified if the specified JWT is digitally signed
提问by Les Hazlewood
I'm looking to implement JWT
in my application for that I'm doing some R&D on it by taking a reference from : https://stormpath.com/blog/jwt-java-create-verify. I was successfully able to implement the generateToken()
method, when I am trying to verifyToken()
by extracting claim sets. I dont understand from where apiKey.getSecret()
is came from. Could you please guide me on this?
我希望JWT
在我的应用程序中实现,因为我正在通过参考以下内容对其进行一些研发:https: //stormpath.com/blog/jwt-java-create-verify。generateToken()
当我试图verifyToken()
通过提取声明集时,我成功地实现了该方法。我不明白apiKey.getSecret()
从哪里来。你能指导我吗?
The code below for reference:
以下代码供参考:
public class JJWTDemo {
private static final String secret = "MySecrete";
private static String generateToken(){
String id = UUID.randomUUID().toString().replace("-", "");
Date now = new Date();
Date exp = new Date(System.currentTimeMillis() + (1000*30)); // 30 seconds
String token = Jwts.builder()
.setId(id)
.setIssuedAt(now)
.setNotBefore(now)
.setExpiration(exp)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
return token;
}
private static void verifyToken(String token){
Claims claims = Jwts.parser().
setSigningKey(DatatypeConverter.parseBase64Binary(apiKey.getSecret()))
.parseClaimsJws(token).getBody();
System.out.println("----------------------------");
System.out.println("ID: " + claims.getId());
System.out.println("Subject: " + claims.getSubject());
System.out.println("Issuer: " + claims.getIssuer());
System.out.println("Expiration: " + claims.getExpiration());
}
public static void main(String[] args) {
System.out.println(generateToken());
String token = generateToken();
verifyToken(token);
}
}
I see the below error is coming:
我看到以下错误即将来临:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4N2E5NmYwNTcyN2M0ZDY0YjZmODlhNDAyOTQ2OTZiNyIsImlhdCI6MTQ4NDQ4NjYyNiwibmJmIjoxNDg0NDg2NjI2LCJleHAiOjE0ODQ0ODY2NTZ9.ycS7nLWnPpe28DM7CcQYBswOmMUhBd3wQwfZ9C-yQYs
Exception in thread "main" java.lang.IllegalArgumentException: A signing key must be specified if the specified JWT is digitally signed.
at io.jsonwebtoken.lang.Assert.notNull(Assert.java:85)
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:331)
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:481)
at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:541)
at io.jsonwebtoken.jjwtfun.service.JJWTDemo.verifyToken(JJWTDemo.java:31)
at io.jsonwebtoken.jjwtfun.service.JJWTDemo.main(JJWTDemo.java:41)
Maven dependency:
Maven 依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<jjwt.version>0.7.0</jjwt.version>
回答by Les Hazlewood
apiKey.getSecret()
in the blog article is a reference to the secure, randomly-generated & Base64-encoded secret key (like a password) assigned to the API Key that Stormpath provides every customer. Stormpath customers use this API key to authenticate every request into the Stormpath REST API. Because every Stormpath customer has an API Key (and the key is accessible to your application), the API Key secret is an ideal 'default' for signing and verifying JWTs specific to your application.
apiKey.getSecret()
博客文章中引用了分配给 Stormpath 为每个客户提供的 API 密钥的安全、随机生成和 Base64 编码的密钥(如密码)。Stormpath 客户使用此 API 密钥对每个进入 Stormpath REST API 的请求进行身份验证。由于每个 Stormpath 客户都有一个 API 密钥(并且您的应用程序可以访问该密钥),因此 API 密钥密钥是签名和验证特定于您的应用程序的 JWT 的理想“默认值”。
If you don't have a Stormpath API Key, any sufficiently strong secure-random byte array will be just fine for signing and verifying JWTs.
如果您没有 Stormpath API 密钥,任何足够强大的安全随机字节数组都可以用于签名和验证 JWT。
In your above example, the following is shown as a test key:
在上面的示例中,以下内容显示为测试密钥:
private static final String secret = "MySecrete";
This is not a valid (JWT-compliant) key, and it cannot be used for JWT HMAC algorithms.
这不是有效的(符合 JWT 的)密钥,不能用于 JWT HMAC 算法。
The JWT RFC requiresthat you MUSTuse a byte array key length equal to or greater than the hash output length.
JWT RFC要求您必须使用等于或大于哈希输出长度的字节数组键长度。
This means that if you use HS256, HS384, or HS512, your key byte arrays must be 256 bits (32 bytes), 384 bits (48 bytes), or 512 bits (64 bytes) respectively. I go into more detail on this in another StackOverflow answer- see the data there, and the MacProvider
examples that can generate you a spec-compliant and secure key.
这意味着如果您使用 HS256、HS384 或 HS512,您的关键字节数组必须分别为 256 位(32 字节)、384 位(48 字节)或 512 位(64 字节)。我在另一个 StackOverflow 答案中对此进行了更详细的介绍- 请参阅那里的数据,以及MacProvider
可以为您生成符合规范的安全密钥的示例。
Based on this, here is that code sample, rewritten to a) generate a valid key and b) reference that key as a Base64-encoded string:
基于此,这里是代码示例,重写为 a) 生成有效密钥和 b) 将该密钥作为 Base64 编码字符串引用:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.crypto.MacProvider;
import java.security.Key;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
public class JJWTDemo {
private static final Key secret = MacProvider.generateKey(SignatureAlgorithm.HS256);
private static final byte[] secretBytes = secret.getEncoded();
private static final String base64SecretBytes = Base64.getEncoder().encodeToString(secretBytes);
private static String generateToken() {
String id = UUID.randomUUID().toString().replace("-", "");
Date now = new Date();
Date exp = new Date(System.currentTimeMillis() + (1000 * 30)); // 30 seconds
String token = Jwts.builder()
.setId(id)
.setIssuedAt(now)
.setNotBefore(now)
.setExpiration(exp)
.signWith(SignatureAlgorithm.HS256, base64SecretBytes)
.compact();
return token;
}
private static void verifyToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(base64SecretBytes)
.parseClaimsJws(token).getBody();
System.out.println("----------------------------");
System.out.println("ID: " + claims.getId());
System.out.println("Subject: " + claims.getSubject());
System.out.println("Issuer: " + claims.getIssuer());
System.out.println("Expiration: " + claims.getExpiration());
}
public static void main(String[] args) {
System.out.println(generateToken());
String token = generateToken();
verifyToken(token);
}
}
Note that Base64-encoded byte arrays are notencrypted (text encoding != encryption), so ensure that if you Base64-encode your secret key bytes that you still keep that Base64 string safe/hidden.
请注意,Base64 编码的字节数组未加密(文本编码!= 加密),因此请确保如果您对您的密钥字节进行 Base64 编码,您仍然保持该 Base64 字符串安全/隐藏。
Finally, the above static final constants (named secret
, secretBytes
and base64SecretBytes
) are there for this simple test demonstration only - one should never hard code keys into source code, let alone make them static constants, as they can easily be decompiled.
最后,上面的静态最终常量(名为secret
,secretBytes
和base64SecretBytes
)仅用于这个简单的测试演示 - 永远不要将键硬编码到源代码中,更不用说将它们设为静态常量,因为它们很容易被反编译。
回答by Ash_P
I am 100% agree on Les Hazlewood. But we should always send the Subject
, Issuer
and Audience
to identify the current login users more details. The code can be modified like below:
我 100% 同意 Les Hazlewood。但是我们应该始终发送Subject
,Issuer
并Audience
识别当前登录用户的更多详细信息。代码可以修改如下:
import java.security.Key;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.crypto.MacProvider;
public class TokenUtil {
private static final Key secret = MacProvider.generateKey(SignatureAlgorithm.HS256);
private static final byte[] secretBytes = secret.getEncoded();
private static final String base64SecretBytes = Base64.getEncoder().encodeToString(secretBytes);
private static String generateToken(String subject, String issuer, String audience) {
String id = UUID.randomUUID().toString().replace("-", "");
Date now = new Date();
Date exp = new Date(System.currentTimeMillis() + (1000 * 30)); // 30 seconds
String token = Jwts.builder()
.setId(id)
.setIssuedAt(now)
.setNotBefore(now)
.setExpiration(exp)
.setSubject(subject)
.setIssuer(issuer)
.setAudience(audience)
.signWith(SignatureAlgorithm.HS256, base64SecretBytes)
.compact();
return token;
}
private static void verifyToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(base64SecretBytes)
.parseClaimsJws(token).getBody();
System.out.println("----------------------------");
System.out.println("ID: " + claims.getId());
System.out.println("Subject: " + claims.getSubject());
System.out.println("Issuer: " + claims.getIssuer());
System.out.println("Expiration : " + claims.getExpiration());
System.out.println("Not Before : "+claims.getNotBefore());
System.out.println("Audience :: "+claims.getAudience());
}
public static void main(String[] args) {
String token = generateToken("MySubject", "AH", "MyAudience");
System.out.println("TOKEN :: "+token);
verifyToken(token);
}
}