当前位置:   article > 正文

JWT使用_java-jwt-4.4.0.jar

java-jwt-4.4.0.jar

学习链接

jwt官网:https://jwt.io

JWT详解:讲解的十分详细

参照:JWT 介绍和使用,对称加密,非对称加密,RSA, Tocken?

1.引入jwt的概念

1.1 有状态登录,无状态登录

有状态登录

服务器当中记录每一次的登录信息,从而根据客户端发送的数据来判断登录过来的用户是否合法。

缺点:

  • 服务器当中需要保存大量的session信息,从而增加了服务器的压力。
  • 客户端请求依赖登录服务器,多个请求过来访问同一个服务器。
  • 服务器拓展困难,需要将session存储到其他服务器当中。

无状态登录

服务器当中不记录用户的登录信息,而是将登录成功后的合法用户信息以token方式保存到客户端当中
,用户在每次请求都携带token信息。

好处:

  • 减少服务器存储session信息的压力。
  • 客户端请求不依赖服务器。

1.2 如何实现无状态登录?

流程

  1. 客户端第一次请求服务器,服务器端对登录用户信息进行认证。

  2. 认证通过后,对客户信息进行加密处理形成token登录凭证,然后返回给客户端。

  3. 以后客户端每一次请求都携带这个jwt信息去请求服务器。
    而服务器端请求信息进行解密处理。判断登录用户是否有效。

授权-鉴权流程图

在这里插入图片描述

2.加密技术的引入

2.1对称加密,非对称加密,不可逆加密方式。

对称加密

将明文分成N个组,然后使用密钥对各个组进行加密,形成各自的密文,最后把所有的分组密文进行合并,形成最终的密文。

  • 优势:加密速度快,加密效率高。
  • 缺点:双方都使用同样的密钥,安全性得不到保证。

非对称加密方式

同时生成两把密钥,公钥和私钥私钥服务器自己保存公钥下发到受信任的客户端

  • 优势:安全性高,只要私钥不暴露出去,信息就不会泄露。
  • 缺点:加密效率低

不可逆加密方式:MD5

加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,无法根据密文推算出明文

严格意义上,这种不属于加密方式。因为加密算法要既能加密也能解密。

3.JWT

概念:全称json wek token 是一种轻量级的身份认证 ,可实现无状态 ,分布式的web应用授权

3.1组成:

JWT由三部分组成分别是:header+payload+签名

header

头部,通常由两部分信息,

  • 声明类型:通常为jwt

  • 签名算法:签名部分使用的算法,需要在header中进行定义。

payload

负载

这里保存着有效的数据部分。

  • jwt当中实际存储数据的部分。

  • 官方规定了7个可以选择的数据。

    • iss:发行人
    • exp:到期时间
    • sub:主题
    • aud:用户
    • nbf:在此之前不可用
    • iat:发布时间
    • jti:JWT ID用于标识该JWT

signature

签名

通常是整个数据的认证信息,一般根据前面两部的数据,再加上服务的密钥,通过加密算法生成,用于验证整个数据的完成性。
在这里插入图片描述
如图所示,这是生成的jwt信息。

3.2jwt系统交互流程

流程图

在这里插入图片描述

交互流程析

  • 通过交互图可以观察到:用户登陆微服务后,还需要拿着jwt到鉴权中心去验证用户的登陆权限,能不能让用户就在服务端就可以完成鉴权的工作,这样就可以减少一次网络请求,加快系统的响应时间。

  • 结论:我们可以使用jwt+rsa的方式,由鉴权中心生成私钥,公钥。在授权中心通过私钥生成jwt信息,然后公钥下发给受信任的服务。再使用公钥再服务器端进行鉴权处理。(如果通过公钥可以获取到jwt当中信息,说明该用户具有对应的权限。可以进行登陆操作。)

3.3 使用jwt+rsa方式的授权+鉴权方式

在这里插入图片描述

4.JWT实例

4.1使用jwt所依赖的maven依赖都有哪些

<!--json web token相关坐标-->
 <dependency>
     <groupId>io.jsonwebtoken</groupId>
     <artifactId>jjwt-api</artifactId>
     <version>0.10.5</version>
 </dependency>
 <dependency>
     <groupId>io.jsonwebtoken</groupId>
     <artifactId>jjwt-impl</artifactId>
     <version>0.10.5</version>
     <scope>runtime</scope>
 </dependency>
 <dependency>
     <groupId>io.jsonwebtoken</groupId>
     <artifactId>jjwt-jackson</artifactId>
     <version>0.10.5</version>
     <scope>runtime</scope>
 </dependency>
 
 <dependency>
     <groupId>joda-time</groupId>
     <artifactId>joda-time</artifactId>
     <version>2.10.10</version>
 </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

4.2RSA工具类:用于生成私钥和公钥

RsaUtils

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * Rsa工具类
 */
public class RsaUtils {

    private static final int DEFAULT_KEY_SIZE = 2048;

    /**
     * 从文件中读取公钥
     *
     * @param filename 公钥保存路径,相对于classpath
     * @return  PublicKey 公钥对象
     * @throws Exception
     */
    public static PublicKey getPublicKey(String filename) throws Exception {
        byte[] bytes = readFile (filename);
        return getPublicKey (bytes);
    }

    /**
     * 从文件中读取密钥
     *
     * @param filename 私钥保存路径,相对于classpath
     * @return  PrivateKey 私钥对象
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String filename) throws Exception {
        byte[] bytes = readFile (filename);
        return getPrivateKey (bytes);
    }

    /**
     * 获取公钥
     * 公钥的字节形式。
     * @param bytes 公钥的字节形式
     * @return
     * @throws Exception
     */
    private static PublicKey getPublicKey(byte[] bytes) throws Exception {
        bytes = Base64.getDecoder ( ).decode (bytes);
        X509EncodedKeySpec spec = new X509EncodedKeySpec (bytes);
        KeyFactory factory = KeyFactory.getInstance ("RSA");
        return factory.generatePublic (spec);
    }

    /**
     * 获取密钥
     *
     * @param bytes 私钥的字节形式
     * @return
     * @throws Exception
     */
    private static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
        bytes = Base64.getDecoder ( ).decode (bytes);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec (bytes);
        KeyFactory factory = KeyFactory.getInstance ("RSA");
        return factory.generatePrivate (spec);
    }

    /**
     * 根据密文,生存rsa公钥和私钥,并写入指定文件
     *
     * @param publicKeyFilename  公钥文件路径
     * @param privateKeyFilename 私钥文件路径
     * @param secret             生成密钥的密文
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret, int keySize) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance ("RSA");
        SecureRandom secureRandom = new SecureRandom (secret.getBytes ( ));
        keyPairGenerator.initialize (Math.max (keySize, DEFAULT_KEY_SIZE), secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair ( );
        // 获取公钥并写出
        byte[] publicKeyBytes = keyPair.getPublic ( ).getEncoded ( );
        publicKeyBytes = Base64.getEncoder ( ).encode (publicKeyBytes);
        writeFile (publicKeyFilename, publicKeyBytes);
        // 获取私钥并写出
        byte[] privateKeyBytes = keyPair.getPrivate ( ).getEncoded ( );
        privateKeyBytes = Base64.getEncoder ( ).encode (privateKeyBytes);
        writeFile (privateKeyFilename, privateKeyBytes);
    }

    private static byte[] readFile(String fileName) throws Exception {
        return Files.readAllBytes (new File (fileName).toPath ( ));
    }

    private static void writeFile(String destPath, byte[] bytes) throws IOException {
        File dest = new File (destPath);
        if (!dest.exists ( )) {
            dest.createNewFile ( );
        }
        Files.write (dest.toPath ( ), bytes);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103

RsaUtilsTest

import org.junit.Test;

import java.security.PrivateKey;
import java.security.PublicKey;

public class RsaUtilsTest {
    //生成私钥,公钥地址
    private String privateFilePath = "D:\\Projects\\demo-springboot222\\src\\main\\resources\\id_rsa";
    private String publicFilePath = "D:\\Projects\\demo-springboot222\\src\\main\\resources\\id_rsa_pub";

    @Test
    public void testRSA() throws Exception {
        // 生成密钥对
        RsaUtils.generateKey(publicFilePath, privateFilePath, "hello", 2048);

        // 获取私钥
        PrivateKey privateKey = RsaUtils.getPrivateKey(privateFilePath);
        System.out.println("privateKey = " + privateKey);
        // 获取公钥
        PublicKey publicKey = RsaUtils.getPublicKey(publicFilePath);
        System.out.println("publicKey = " + publicKey);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

在这里插入图片描述

4.3 jwt当中的载荷信息部分

Payload

import lombok.Data;
import java.util.Date;

@Data
public class Payload<T> {

    // jwt的id
    private String id;
    
    // 用户信息
    private T userInfo;
    
    // 过期时间
    private Date expiration;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

UserInfo

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
/**
 * 载荷 :UserInfo
 */
public class UserInfo {

    private Long id;

    private String username;

    private String role;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

4.4 jwtutils工具类,用于生成jwt信息,解析jwt信息

JwtUtils

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;


import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;
import java.util.UUID;

public class JwtUtils {

    private static final String JWT_PAYLOAD_USER_KEY = "user";

    /**
     *
     * 私钥加密token
     *
     * @param userInfo   载荷中的数据
     * @param privateKey 私钥
     * @param expire     过期时间,单位分钟
     * @return JWT
     */
    public static String generateTokenExpireInMinutes(Object userInfo, PrivateKey privateKey, int expire) {
        return Jwts.builder()
                .claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(userInfo))
                .setId(createJTI())
                .setExpiration(DateTime.now().plusMinutes(expire).toDate())
                .signWith(privateKey, SignatureAlgorithm.RS256)
                .compact();
    }

    /**
     * 私钥加密token
     *
     * @param userInfo   载荷中的数据
     * @param privateKey 私钥
     * @param expire     过期时间,单位秒
     * @return JWT
     */
    public static String generateTokenExpireInSeconds(Object userInfo, PrivateKey privateKey, int expire) {
        return Jwts.builder()
                .claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(userInfo))
                .setId(createJTI())
                .setExpiration(DateTime.now().plusSeconds(expire).toDate())
                .signWith(privateKey, SignatureAlgorithm.RS256)
                .compact();
    }

    /**
     * 公钥解析token
     *
     * @param token     用户请求中的token
     * @param publicKey 公钥
     * @return Jws<Claims>
     */
    private static Jws<Claims> parserToken(String token, PublicKey publicKey) {
        return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
    }

    private static String createJTI() {
        return new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes()));
    }

    /**
     * 获取token中的用户信息
     *
     * @param token     用户请求中的令牌
     * @param publicKey 公钥
     * @return 用户信息
     */
    public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey, Class<T> userType) {
        Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        Payload<T> claims = new Payload<>();
        claims.setId(body.getId());
        claims.setUserInfo(JsonUtils.toBean(body.get(JWT_PAYLOAD_USER_KEY).toString(), userType));
        claims.setExpiration(body.getExpiration());
        return claims;
    }

    /**
     * 获取token中的载荷信息
     *
     * @param token     用户请求中的令牌
     * @param publicKey 公钥
     * @return 用户信息
     */
    public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey) {
        Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        Payload<T> claims = new Payload<>();
        claims.setId(body.getId());
        claims.setExpiration(body.getExpiration());
        return claims;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99

测试

public static void main(String[] args) throws Exception {
     String privateFilePath = "D:\\Projects\\demo-springboot222\\src\\main\\resources\\id_rsa";
     String publicFilePath = "D:\\Projects\\demo-springboot222\\src\\main\\resources\\id_rsa_pub";

     PrivateKey privateKey = RsaUtils.getPrivateKey(privateFilePath);
     PublicKey publicKey = RsaUtils.getPublicKey(publicFilePath);

     UserInfo userInfo = new UserInfo();
     userInfo.setId(1L);
     userInfo.setRole("admin");
     userInfo.setUsername("zzhua");

     ObjectMapper mapper = new ObjectMapper();

     String token = Jwts.builder()
             .claim("user", mapper.writeValueAsString((userInfo)))
             .setId(createJTI())
             .setExpiration(DateTime.now().plusMinutes(10).toDate())
             .signWith(privateKey, SignatureAlgorithm.RS256) // 私钥加密 
                                  // 注意这里的算法只能选RS开头的,比如还可以选SignatureAlgorithm.RS384
             .compact();

     Jws<Claims> claimsJws = Jwts.parser()
             .setSigningKey(publicKey) // 公钥解密
             .parseClaimsJws(token);   // token

     Claims body = claimsJws.getBody();
     System.out.println(body.get("user").toString());
 }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

解密结果
在这里插入图片描述

5. jwt一些简单demo

jjwt

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.6.0</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

Base64加密、解密

@Test
public void testGenJwt() {
    JwtBuilder jwtBuilder = Jwts.builder()
                                .setSubject("zzhua")
                                .setId("001")
                                .signWith(SignatureAlgorithm.HS256, "YOUR_secret");
    String token = jwtBuilder.compact();
    System.out.println(token);
    // eyJhbGciOiJIUzI1NiJ9.
    // eyJzdWIiOiJ6emh1YSIsImp0aSI6IjAwMSJ9.
    // yipfa-gsDGg-5KYqeCVo_oiOnUx2D0Sp589gCUXCap8
}

@Test
public void testParseToken() throws IOException {
    BASE64Decoder decoder = new BASE64Decoder();
    // 使用Base64解析jwtToken的负载部分(头部和负载部分都是使用Base64加密的,所以可以解密)
    String data = new String(decoder.decodeBuffer("eyJzdWIiOiJ6emh1YSIsImp0aSI6IjAwMSJ9"));
    System.out.println(data); // 解析结果:{"sub":"zzhua","jti":"001"}

    BASE64Encoder encoder = new BASE64Encoder();
    // 使用Base64加密
    String encode = encoder.encode("{\"sub\":\"zzhua\",\"jti\":\"001\"}".getBytes());
    System.out.println(encode); // 与上面完全符合:eyJzdWIiOiJ6emh1YSIsImp0aSI6IjAwMSJ9
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

用法

//生成token
public class CreateJwtTest {
    public static void main(String[] args) {
        JwtBuilder builder= Jwts.builder().setId("888")
            .setSubject("小白")
            .setIssuedAt(new Date())
            .signWith(SignatureAlgorithm.HS256,"itcast");
        System.out.println( builder.compact() );
    }
}

// 解析token
public class ParseJwtTest {
    public static void main(String[] args) {

        String token ="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiO"+
            "jE1MjM0MTM0NTh9.gq0J-cOM_qCNqU_s-d_IrRytaNenesPmqAIhQpYXHZk";

        Claims claims = Jwts.parser().setSigningKey("itcast").parseClaimsJws(token).getBody();

        System.out.println("id:"+claims.getId());
        System.out.println("subject:"+claims.getSubject());
        System.out.println("IssuedAt:"+claims.getIssuedAt());

    }
}

// 自定义claims数据
public class CreateJwtTest3 {
    public static void main(String[] args) {
        //为了方便测试,我们将过期时间设置为1分钟
        long now = System.currentTimeMillis();//当前时间
        long exp = now + 1000*60;//过期时间为1分钟
        new HashMap<String,Object>()
        JwtBuilder builder= Jwts.builder().setId("888")
            .setSubject("小白")
            .setIssuedAt(new Date())
            .signWith(SignatureAlgorithm.HS256,"itcast")
            .setExpiration(new Date(exp))
            .claim("roles","admin") //自定义claims存储数据
            .claim("logo","logo.png");
        System.out.println( builder.compact() );
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

对称签名

public class JwtUtils {
    // token时效:24小时
    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    // 签名哈希的密钥,对于不同的加密算法来说含义不同
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

    /**
     * 根据用户id和昵称生成token
     * @param id  用户id
     * @param nickname 用户昵称
     * @return JWT规则生成的token
     */
    public static String getJwtToken(String id, String nickname){
        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                .setSubject("baobao-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .claim("id", id)
                .claim("nickname", nickname)
            	// HS256算法实际上就是MD5加盐值,此时APP_SECRET就代表盐值
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();

        return JwtToken;
    }

    /**
     * 判断token是否存在与有效
     * @param jwtToken token字符串
     * @return 如果token有效返回true,否则返回false
     */
    public static boolean checkToken(String jwtToken) {
        if(StringUtils.isEmpty(jwtToken)) return false;
        try {
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 判断token是否存在与有效
     * @param request Http请求对象
     * @return 如果token有效返回true,否则返回false
     */
    public static boolean checkToken(HttpServletRequest request) {
        try {
            // 从http请求头中获取token字符串
            String jwtToken = request.getHeader("token");
            if(StringUtils.isEmpty(jwtToken)) return false;
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根据token获取会员id
     * @param request Http请求对象
     * @return 解析token后获得的用户id
     */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token");
        if(StringUtils.isEmpty(jwtToken)) return "";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("id");
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76

非对称签名

生成jwt串的时候需要指定私钥,解析jwt串的时候需要指定公钥

private static final String RSA_PRIVATE_KEY = "...";
private static final String RSA_PUBLIC_KEY = "...";

/**
     * 根据用户id和昵称生成token
     * @param id  用户id
     * @param nickname 用户昵称
     * @return JWT规则生成的token
     */
public static String getJwtTokenRsa(String id, String nickname){
    // 利用hutool创建RSA
    RSA rsa = new RSA(RSA_PRIVATE_KEY, null);
    RSAPrivateKey privateKey = (RSAPrivateKey) rsa.getPrivateKey();
    String JwtToken = Jwts.builder()
        .setSubject("baobao-user")
        .setIssuedAt(new Date())
        .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
        .claim("id", id)
        .claim("nickname", nickname)
        // 签名指定私钥
        .signWith(privateKey, SignatureAlgorithm.RS256)
        .compact();
    return JwtToken;
}

/**
     * 判断token是否存在与有效
     * @param jwtToken token字符串
     * @return 如果token有效返回true,否则返回false
     */
public static Jws<Claims> decodeRsa(String jwtToken) {
    RSA rsa = new RSA(null, RSA_PUBLIC_KEY);
    RSAPublicKey publicKey = (RSAPublicKey) rsa.getPublicKey();
    // 验签指定公钥
    Jws<Claims> claimsJws = Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(jwtToken);
    return claimsJws;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

java-jwt

引入依赖

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.0</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

示例工具类1

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

public class JWTUtil {

    /**
     * 过期时间3小时
     */
    private static final long EXPIRE_TIME = 3 * 60 * 60 * 1000;

    /**
     * 校验token是否正确
     *
     * @param token  密钥
     * @param secret 用户的密码
     * @return 是否正确
     */
    public static boolean verify(String token, String username, String secret) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("username", username)
                    .build();
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception exception) {
            return false;
        }
    }

    /**
     * @return token中包含的用户名
     */
    public static String getUsername(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 获取当前用户
     *
     * @param token jwt加密信息
     * @return 解析的当前用户信息
     */
    public static Principal getPrincipal(String token) {
        try {
            Principal principal = new Principal();
            DecodedJWT jwt = JWT.decode(token);
            principal.setUserId(jwt.getClaim("userId").asString());
            principal.setUserName(jwt.getClaim("username").asString());
            String[] roleArr = jwt.getClaim("roles").asArray(String.class);
            if (roleArr != null) {
                principal.setRoles(Arrays.asList(roleArr));
            }
            return principal;
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 获取角色组
     *
     * @param token
     * @return
     */
    public static String[] getRoles(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("roles").asArray(String.class);
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 生成签名
     *
     * @param username 用户名
     * @param userId   用户id
     * @param secret   用户的密码
     * @return 加密的token
     */
    public static String sign(String username, String userId, List<String> roles, String secret) {
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        Algorithm algorithm = Algorithm.HMAC256(secret);
        String[] roleArr = new String[roles.size()];
        roleArr = roles.toArray(roleArr);
        // 附带username信息
        return JWT.create()
                .withClaim("userId", userId)
                .withClaim("username", username)
                .withArrayClaim("roles", roleArr)
                .withExpiresAt(date)
                .sign(algorithm);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108

对称签名

public class JWTUtils {
    // 签名密钥
    private static final String SECRET = "!DAR$";

    /**
     * 生成token
     * @param payload token携带的信息
     * @return token字符串
     */
    public static String getToken(Map<String,String> payload){
        // 指定token过期时间为7天
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, 7);

        JWTCreator.Builder builder = JWT.create();
        // 构建payload
        payload.forEach((k,v) -> builder.withClaim(k,v));
        // 指定过期时间和签名算法
        String token = builder.withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(SECRET));
        return token;
    }


    /**
     * 解析token
     * @param token token字符串
     * @return 解析后的token
     */
    public static DecodedJWT decode(String token){
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
        DecodedJWT decodedJWT = jwtVerifier.verify(token);
        return decodedJWT;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

非对称签名

生成jwt串的时候需要指定私钥,解析jwt串的时候需要指定公钥。

private static final String RSA_PRIVATE_KEY = "..."; // 需要生成
private static final String RSA_PUBLIC_KEY = "..."; // 需要生成

/**
     * 生成token
     * @param payload token携带的信息
     * @return token字符串
     */
public static String getTokenRsa(Map<String,String> payload){
    // 指定token过期时间为7天
    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.DATE, 7);

    JWTCreator.Builder builder = JWT.create();
    // 构建payload
    payload.forEach((k,v) -> builder.withClaim(k,v));

    // 利用hutool创建RSA
    RSA rsa = new RSA(RSA_PRIVATE_KEY, null);
    // 获取私钥
    RSAPrivateKey privateKey = (RSAPrivateKey) rsa.getPrivateKey();
    // 签名时传入私钥
    String token = builder.withExpiresAt(calendar.getTime()).sign(Algorithm.RSA256(null, privateKey));
    return token;
}

/**
     * 解析token
     * @param token token字符串
     * @return 解析后的token
     */
public static DecodedJWT decodeRsa(String token){
    // 利用hutool创建RSA
    RSA rsa = new RSA(null, RSA_PUBLIC_KEY);
    // 获取RSA公钥
    RSAPublicKey publicKey = (RSAPublicKey) rsa.getPublicKey();
    // 验签时传入公钥
    JWTVerifier jwtVerifier = JWT.require(Algorithm.RSA256(publicKey, null)).build();
    DecodedJWT decodedJWT = jwtVerifier.verify(token);
    return decodedJWT;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

6. Double Token解决方案

参照:JavaWeb单点登录的实践

流程图

  • 用户登录成功后,后台生成token,但是不直接把此token返回给客户端,而是使用md5对该token加密,并将MD5(token)作为key,token作为value,存入redis,这样避免用户信息被泄露(因为jwt的头部和负载仅仅是Base64加密,很容易被解析出来,因此敏感信息不能放进去,使用md5加密后,这样就解析不出来了),将解密后的结果返回给客户端(可以用cookie的形式写给客户端,让客户端自动携带cookie过来,比较方便)。

  • 客户端访问时携带MD5(token),后台从redis中拿到真正的token,解析出来

在这里插入图片描述

在这里插入图片描述

为什么要用双Token形式

假设我们只用一个token

生成1个token——>有效期:2小时——>每次访问就更新有效期为2小时——>2个小时没有访问的话,就要重新登录了

生成一个token—>有效期:30天——> 每次访问就更新有效期为30天——>用户一个月没有登录,没有访问,就会过期——>如果这个token长期保存在redis里,就会占用资源。这个用户一个月才访问1次,这样太浪费资源了

生成1个token——>有效期2个小时,保存在redis中——>过期了,我们就通过refreshToken来创建新的token——>redis里,如果2个小时不访问,那么redis里的token就会被干掉,这样就不占用redis的资源了。如果再访问,就通过refreshToken来创建新的token。如果该用户一个月都没有访问,就要重新登录,因为refreshToken的有效期有30天。

redis的特性–>noSQL,内存型数据库,存储大部分数据在内存里,快照会保存到磁盘里。mysql关系型数据库,数据保存在磁盘上。当然,现在的mysql也有缓存的功能。速度上来说,redis应该要更快的,所以会配合着使用。

如果网站用户有100万,我们直接把一个长期的token保存在redis里,用户不用了,那不是很占用空间吗?所以我们会生成一个2小时的token。放在redis里,生成一个30天的refreshToken放在mysql里。

平时用户访问则取用redis里的token,如果过期了,通过已失效的token找到有效时间更长的refreshToken,来创建新的token,同时也更新refreshToken。

这样子就可以减少mysql的token查询了,速度上会快一点。而且用户超过2个小时没有登录,redis里的空间就释放了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/544400
推荐阅读
相关标签
  

闽ICP备14008679号