欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

从0到1搭建前后端分离的脚手架框架之后端(四) JWT TOKEN 整合

程序员文章站 2022-04-22 07:59:30
...

整合JWT TOKEN

选择的jwt jar包为 jjwt, token采用RSA加密,来提升token的安全性.
首先我们来看一下RSA加密类( RsaHelper), 基本思路就是公钥私钥放到resources目录下,初始化的时候加载秘钥. 其中有一个内部类 RsaConfig对外暴露配置参数,设置了默认值和必填值

@Slf4j
@Component
@DependsOn("applicationContextHelper")
public class RsaHelper {
    private static final String KEY_ALGORITHM = "RSA";
    private static RsaConfig rsaConfig;
    public static  RSAPublicKey PUBLIC_KEY;
    public static RSAPrivateKey PRIVATE_KEY;
    @PostConstruct
    public void init() throws IOException, ClassNotFoundException {
        rsaConfig = ApplicationContextHelper.getBean(RsaConfig.class);
        PUBLIC_KEY = (RSAPublicKey) FileUtils.readObject(rsaConfig.getPublicKeyPath());
        PRIVATE_KEY = (RSAPrivateKey) FileUtils.readObject(rsaConfig.getPrivateKeyPath());

    }
    public static String encrypt(String data) {
        try {
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, PUBLIC_KEY);
            byte[] dataBytes = cipher.doFinal(data.getBytes(rsaConfig.getChatSet()));
            return Base64.getEncoder().encodeToString(dataBytes);
        } catch (Exception e) {
            log.warn("加密数据错误", e);
            throw new RsaException(GlobalCode.RSA_ERROR, "加密数据错误");
        }

    }
    public static String decrypt(String data) {
        try {
            byte[] bytes = Base64.getDecoder().decode(data);
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, PRIVATE_KEY);
            return new String(cipher.doFinal(bytes),rsaConfig.getChatSet());
        } catch (Exception e) {
            log.warn("解密数据错误", e);
            throw new RsaException(GlobalCode.RSA_ERROR, "解密数据错误");
        }

    }

    public static void generateRsaKeyFile(String publicKeyName, String privateKeyName) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGenerator.initialize(rsaConfig.getKeySize());
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        FileUtils.writeObject(keyPair.getPublic(), publicKeyName);
        FileUtils.writeObject(keyPair.getPrivate(),privateKeyName);
    }

    @Data
    @Validated
    @Configuration
    @ConfigurationProperties(prefix = "platform.rsa")
    public static class RsaConfig {
        @NotNull
        private String privateKeyPath;
        @NotNull
        private String publicKeyPath;
        // 秘钥长度
        private Integer keySize = 2048;
        //签名算法
        private String signatureAlgorithm = "SHA256withRSA";
        private String chatSet = "UTF-8";
    }

}

配置参数

platform:
  jwt:
    expiration: 40
  rsa:
    private-key-path: /static/rsa/rsa-private-key.dat
    public-key-path: /static/rsa/rsa-public-key.dat
    key-size: 3072

RsaHelper测试

@SpringBootTest
class RsaHelperTest {

    @Test
    void getKeyPair() throws Exception {
        RsaHelper.generateRsaKeyFile("D:/rsa-public-key.dat", "D:/rsa-private-key.dat");
    }

    @Test
    void testSign() {
        String test = RsaHelper.encrypt("asdfasdfasdfdsf");
        System.out.println(test);
        System.out.println(RsaHelper.decrypt(test));
        System.out.println(RsaHelper.decrypt("xbBilrMKSEJ+DqOCgh+vhb1AW6xxTKG2J+DEMNwrHy91vBy3ck+QbNuotDlAuHHTcqBGKvHMJQUDGoL9oILtft4Di9L1OxnnU8eOsV4p1WGBuW3X9zyYgiMEicqXBF/mXO/3qPuS245Gi3JiS9dm+R0no1TGwBL6IPiwPvbAtSfMaZGpR9Qn76sV5d1zGD58Ts9+c4dr5mqxGlnNILhiAvRMScYyKH0MZSX1E5HTf74Q0VwwUDoc2sJ6tZHopjMoRhJ4yKnpunsbpWJZTZ+4RsDjP4age8L9VGPdg2FzPSkXskOOFhmF+fqeUhXClEb6efvPCi2FF7+7aNVErCNHixp6p3kr5DXG4shyVMI7N/Za0UrjyXebXb3dDwBVcNrgE04pzA2Mn4QMqG9jjdz+AZWX3hr0ldOc21aQ6a7eWeiOPWOSkWt8sjoH8vYauEKE+MULd+os8H28Q7QUgGOFSr3WyOuNRVZqJG+/SoaTQl1Y+vhD4kV0QyQcHup4/jY5"));

    }
}

RSA加解密完成后我们开始整合jwt, JwtTokenHelper设计思路和RSA差不多,这里需要说明一下 parseToken()方法感觉设计的不是太合理,当token过期时直接返回null值, 返回类型为 Pair<String, String>,这里返回两个值 userIdusername,暂时先这样设计,以后感觉不好用在重构.

@Component
@DependsOn({"applicationContextHelper", "rsaHelper"})
public class JwtTokenHelper {
    private static JwtTokenConfig jwtTokenConfig;
    @PostConstruct
    public void init() {
        jwtTokenConfig = ApplicationContextHelper.getBean(JwtTokenConfig.class);
    }

    public static String generateToken(String tokenId, String tokenName) {
        return Jwts.builder()
                .claim(jwtTokenConfig.getTokenId(), tokenId)
                .claim(jwtTokenConfig.getTokenName(), tokenName)
                .setSubject(tokenName)
                .setExpiration(DateUtils.localDateTimeToDateConverter(
                    LocalDateTime.now()
                    .plusMinutes(jwtTokenConfig.getExpiration().toMinutes())
                ))
                .signWith(SignatureAlgorithm.RS256, RsaHelper.PRIVATE_KEY)
                .setIssuedAt(new Date())
                .compact();
    }

    public static Pair<String, String> parseToken(String token) {
        Jws<Claims> claims = Jwts.parser().setSigningKey(RsaHelper.PUBLIC_KEY).parseClaimsJws(token);
        if (claims.getBody().getExpiration().before(new Date())) {
            return null;
        }
        return Pair.of(Objects.toString(claims.getBody().get(jwtTokenConfig.getTokenId())),
                Objects.toString(claims.getBody().get(jwtTokenConfig.getTokenName())));
    }

    public static String getHeader() {
        return jwtTokenConfig.getHeader();
    }

    public static String getPrefix() {
        return jwtTokenConfig.getPrefix();
    }

    @Data
    @Configuration
    @ConfigurationProperties(prefix = "platform.jwt")
    public static class JwtTokenConfig {
        private String header = "Authorization";
        private String prefix = "Bearer ";
        private String tokenId = "userId";
        private String tokenName = "username";
        @DurationUnit(ChronoUnit.MINUTES)
        private Duration expiration = Duration.ofMinutes(120);
    }
}

测试类

@SpringBootTest
class JwtTokenHelperTest {

    @Test
    void generateToken() {
        String token = JwtTokenHelper.generateToken("test", "username");
        Pair<String, String > pair = JwtTokenHelper.parseToken(token);
        Assertions.assertNotNull(pair);
        Assertions.assertEquals(pair.getLeft(), "test");
        Assertions.assertEquals(pair.getRight(), "username");

    }
}

jwt token算是整合完了,明天开始进行鉴权和认证操作,打算写两个版本一个基于 spring security一个自己手动写鉴权和认证操作,感兴趣的可以关注一下我的github.

项目源码