JWT&JJWT

本文使用的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));
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值