本文使用的JWT是比较新的,不是引入单个坐标那个,而且本文的用法有BUG,总是提示token过期,然后过期时间根本没到。我的解决办法是换成较老版本的引入单个坐标的JWT
一、JWT(JSON Web Token)
JWT为一个字符串,共有三部分:头部 + 载荷 + 签名。
1.1 头部(Header)
存放基本信息,例如类型以及算法
{"typ":"JWT","alg":"HS256"}
进行base64编码
JTdCJTIydHlwJTIyJTNBJTIySldUJTIyJTJDJTIyYWxnJTIyJTNBJTIySFMyNTYlMjIlN0Q=
JDK中有BASE64Encoder和BASE64Decoder可用于编码与解码
1.2 载荷(payload)
存放有效信息。有效信息包含三种声明(Claims)
Claims是JWT的主体,包含JWT创建者希望提供给JWT接收者的信息。
(1)标准中注册是声明(Standard Claims)
iss:jwt签发者
sub:jwt所面向的用户
and:接收jwt的一方
exp:jwt的过期时间
nbf:定义在什么时间之前,该jwt都是不可用的
iat:jwt的签发时间
jti:jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
(2)公共的声明
就是想声明什么写什么,但是该部分可解密。
(3)私有的声明
提供者与消费者共同定义的声明,不建议存放敏感信息。
定义一个payload
{"sub":"1234567890", "name":"John Doe", "admin", true}
进行Base64编码
JTdCJTIyc3ViJTIyJTNBJTIyMTIzNDU2Nzg5MCUyMiUyQyUyMCUyMm5hbWUlMjIlM0ElMjJKb2huJTIwRG9lJTIyJTJDJTIwJTIyYWRtaW4lMjIlMkMlMjB0cnVlJTdE
1.3 签证(signature)
存放签证信息。签证信息有三部分组成:
header(base64后的)+payload(base64后的)+secret
注意:secret泄露后token将没有意义
二、JJWT(Java JSON Web Token)
(1) 快速开始实例:
导入包:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.1</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
<version>0.11.1</version>
<scope>runtime</scope>
</dependency>
Test
@Test
public void createJwt() {
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
//key没重启一次服务器都会变得不同,jws就会不可信,所以这里用同一个key
String jws = Jwts.builder()
.setSubject("Joe")
.signWith(key).compact();
System.out.println(jws);
Claims body = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(jws).getBody();
System.out.println(body.toString());
assert body.getSubject().equals("Joe");
}
1.1 设置Header
不需要设置alg或zip标头参数,因为jjwt会根据前面算法
(1)setHeaderParam方法设置
String jws = Jwts.builder()
.setHeaderParam("kid", "myKeyId")
// ... etc ...
(2)header方法设置
Header header = Jwts.header();
populate(header); //implement me
String jws = Jwts.builder()
.setHeader(header)
// ... etc ...
(3)header Map设置header
Map<String,Object> header = getMyHeaderMap(); //implement me
String jws = Jwts.builder()
.setHeader(header)
// ... etc ...
1.2 设置载荷payload
(1)标准中注册是声明(建议但不强制使用)(Standard Claims)
setIssuer: sets the iss (Issuer) Claim
setSubject: sets the sub (Subject) Claim
setAudience: sets the aud (Audience) Claim
setExpiration: sets the exp (Expiration Time) Claim
setNotBefore: sets the nbf (Not Before) Claim
setIssuedAt: sets the iat (Issued At) Claim
setId: sets the jti (JWT ID) Claim
例子:
String jws = Jwts.builder()
.setIssuer("me")
.setSubject("Bob")
.setAudience("you")
.setExpiration(expiration) //a java.util.Date
.setNotBefore(notBefore) //a java.util.Date
.setIssuedAt(new Date()) // for example, now
.setId(UUID.randomUUID()) //just an example id
/// ... etc ...
(2)Custom Claims
举个栗子:
String jws = Jwts.builder()
.claim("hello", "world")
// ... etc ...
(3)一次性设置claim
Claims claims = Jwts.claims();
populate(claims); //implement me
String jws = Jwts.builder()
.setClaims(claims)
// ... etc ...
(4)Map设置claim
Map<String,Object> claims = getMyClaimsMap(); //implement me
String jws = Jwts.builder()
.setClaims(claims)
// ... etc ...
1.3 设置key
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
String jws = Jwts.builder()
// ... etc ...
.signWith(key) // <---
.compact();
(1)将生成的key转换成字符串
SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
String secretKeyString Encoders.BASE64.encode(secretKey.getEncoded())
不建议将secreKeystring直接存入数据库,Base64可被解码,不够安全
(2)将String等转换成Key
-
byte[]
SecretKey key = Keys.hmacShaKeyFor(encodedKeyBytes);
-
Base64-encoded string:
SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));
-
Base64URL-encoded string:
SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(secretString));
-
未编码字符串
SecretKey key = Keys.hmacShaKeyFor(secretString.getBytes(StandardCharsets.UTF_8));
1.4读取JWT
Jws<Claims> jws;
try {
jws = Jwts.parserBuilder() // (1)
.setSigningKey(key) // (2)
.build() // (3)
.parseClaimsJws(jwsString); // (4)
// JWT可信
catch (JwtException ex) { // (5)
// JWT不可信
}
三、JWTUtil
package util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import javax.crypto.SecretKey;
import java.util.Calendar;
import java.util.Date;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
public class JwtUtil {
@Autowired
private RedisTemplate redisTemplate;
private static Long ttl;
static {
ttl = 1000 * 60 * 60 * 24L;//一天
}
/**
* 生成JWT
*
* @param id
* @param subject
* @param roles
* @return
*/
public String createJWS(String id, String subject, String roles) {
SecretKey secretKey = null;
String secretKeyString = (String) redisTemplate.opsForValue().get("secretKey");
if (secretKeyString == null) {
secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
String encode = parseSecretKeyToBase64(secretKey);
//建议要这么操作的话还需要在加密一遍,Base64并不安全
redisTemplate.opsForValue().set("secretKey", encode, 1, TimeUnit.DAYS);
} else {
secretKey = parseBase64ToSecretKey(secretKeyString);
}
Long timeMillis = System.currentTimeMillis();
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timeMillis);
Date now = calendar.getTime();
JwtBuilder builder = Jwts.builder()
.setId(id)
.setSubject(subject)
.setIssuedAt(now)
.signWith(secretKey)
.claim("roles", roles);
Optional.ofNullable(ttl)
.filter(t -> t > 0)
.ifPresent(t -> {
calendar.setTimeInMillis(timeMillis + t);
Date afterTtlTime = calendar.getTime();
builder.setExpiration(afterTtlTime);
});
return builder.compact();
}
/**
* 解析JWT
*
* @param jws
* @return
*/
public Claims parseJWT(String jws) {
String secretKey = String.valueOf(redisTemplate.opsForValue().get("secretKey"));
return Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(jws)
.getBody();
}
public String parseSecretKeyToBase64(SecretKey secretKey) {
return Encoders.BASE64.encode(secretKey.getEncoded());
}
public SecretKey parseBase64ToSecretKey(String encode) {
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(encode));
}
}