Re:从零开始的JWTUtils

184 阅读5分钟

说在前面

用自己的依赖真的很酷!

想了很久还是决定自己写一个JWT的工具类。虽然目前常用的像hutools、sa-token等我都十分满意、也十分敬佩开发者的技术与想法,但对于简单项目来说使用体验都并不“简单”。对我而言,通过注解标记载荷中的字段并使用.yml文件配置签名和过期时间才是理想型,不需要太多功能,也不需要过分的个性化,简单地注入依赖即可生成、校验、解析token才最合适。

编写工具类

首先在Idea中创建一个maven工程,类型随便即可(清除所有自动生成,保留原生maven工程即可),其中pom.xml文件内容如下:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.3</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>4.3.0</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

接下来开始编码,创建一个注解类@Token(当然你也可以叫@Feild或者其他更合适的称谓):

package com.steadon;

import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Token {
    String value() default "";
}

解释一下用到的元注解:

  • @Target(ElementType.FIELD):靶注解,表示自定义注解应该使用在自动上。
  • @Retention(RetentionPolicy.RUNTIME):保留注解,表示自定义注解保留到程序运行时。
  • @Inherited:继承注解,表示被该注解可以随字段继承而继承。

那么此时这个注解已经可以读取标记字段的字段名了,但是想要通过字段名获取属性还需要使用Java的反射来实现,接下来编写工具类:

/**
 * A lightweight tool for generating tokens quickly
 *
 * @author Steadon
 * @version 1.2.0
 */
@Component
public class JWTUtils {
    private String sign;
    private String time;
    private int _time;

    public JWTUtils() {}

    public String getSign() {return sign;}

    public void setSign(String sign) {this.sign = sign;}

    public String getTime() {return time;}

    public void setTime(String time) {
        this.time = time;
        this._time = Integer.parseInt(time);
    }
    ......
}

通过以上代码我们向SpringBoot注册了一个Bean(即属性私有,所有属性都应该有setter和getter方法,并且含有一个无参构造器),其中sign和time是要从配置文件中读取内容的,为了保证time后期的扩展性我们使用String类型,并在类的内部转为整型。

/**
 * The default way is to create a token string
 * The target field is automatically identified based on the incoming object with @Token annotation field
 * And read the signature and expiration _time in the configuration file to generate a token
 *
 * @param t   The object to which the payload field belongs
 * @param <T> Any type
 * @return The token string that is created
 */
public <T> String createToken(T t) {
    ObjectMapper objectMapper = new ObjectMapper();
    JWTCreator.Builder builder = JWT.create();
    Field[] declaredFields = t.getClass().getDeclaredFields();
    for (Field field : declaredFields) {
        if (field.isAnnotationPresent(Token.class)) {
            field.setAccessible(true);
            Object o = null;
            try {
                o = field.get(t);
                builder.withClaim(field.getName(), objectMapper.writeValueAsString(o));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    Calendar instance = Calendar.getInstance();
    instance.add(Calendar.SECOND, this._time);
    return builder.withExpiresAt(instance.getTime()).sign(Algorithm.HMAC256(this.sign));
}

这段代码实现了创建token字符串的功能,该方法获取传入对象的class对象中获取其对应类型的所有字段,并依次检查是否存在@Token注解,发现标记字段后将其设为可见(避免private修饰导致无法读取字段),随后使用field.get(t)方法获取对象中该字段的具体值,并由builder将其放入载荷。所有标记字段的值都放入载荷之后,加上过期时间和签名生成最终token并返回。

最后我们加上读取配置文件的需要的注解和配置文件:

    #src/main/resources/META-INF/spring.factories (工具类在SpringBoot中的可被发现)
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.steadon.JWTUtils
    @Component
    @ConfigurationProperties(prefix = "token")  //将配置文件中token前缀的字段映射到类的属性
    public class JWTUtils {...}

(其他方法例如解析和校验便不再展示,详情请见github.com/steadon/JWT…

添加开源协议

想要将jar包上传到中央仓库就必须得开源,这里演示一下github上的开源流程:

image.png

image.png

接下来自己选择开源协议即可(略),笔者用的是Apache - 2.0 license,如果是第一次开源建议和笔者一致。

发布GPG公钥

gpg其实就是一个非对称加密工具,需要借助工具生成公钥和私钥,并将公钥放发布到中央用于别人对你的项目进行验证。

  • mac : 下载GPG Suite,后续无脑操作。
  • win/linux : 下载GnuPG,后续可采用命令行操作。
    • 生成 : gpg --gen-key
    • 查看 : gpg --list-keys
    • 上传 : (自己想办法吧)

申请Maven仓库

接下来申请maven仓库,进入Sonatype JIRA注册,再去Dashboard新建一个问题,类型选new project,申请时需要域名,可以用github.io,但是需要新建一个公开仓库取名为你的问题编号用来验证,可以参考我的问题,一般五分钟内就会有回复,如果机器人报错需要及时修改重新申请,二次申请大约十分钟(别问我为什么知道 >_< )

配置pom.xml文件

最后就需要配置pom.xml文件了,但这也是笔者认为最难的一步,首先需要在你的maven的setting.xml中添加以下内容以保证中央仓库的访问权:

<server>
  <id>ossrh</id>
  <username>your-username</username>
  <password>your-password</password>
</server>

其次配置pom.xml文件模版如下,也可以参考机器人发的指南读者自己参考写吧,能不能用就随缘了,反正我可以用:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>io.github.steadon</groupId>
    <artifactId>utils</artifactId>
    <version>1.2.0</version>
    <packaging>jar</packaging>

    <name>JWTUtils</name>
    <url>https://github.com/steadon/JWTUtils</url>
    <description>Fast token generation and parsing based on springboot</description>
    <!-- 开源签名证书 -->
    <licenses>
        <license>
            <name>The Apache Software License, Version 2.0</name>
            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
        </license>
    </licenses>
    <!-- 开发人员信息 -->
    <developers>
        <developer>
            <name>steadon</name>
            <email>1473352497@qq.com</email>
            <organization>https://github.com/steadon</organization>
            <timezone>+8</timezone>
        </developer>
    </developers>
    <!-- 仓库信息 -->
    <scm>
        <connection>scm:git:git://github.com/steadon/JWTUtils.git</connection>
        <developerConnection>scm:git:ssh://github.com/steadon/JWTUtils.git</developerConnection>
        <url>http://github.com/steadon/JWTUtils</url>
    </scm>
    <!-- 发布项目到 sonatype -->
    <distributionManagement>
        <snapshotRepository>
            <id>ossrh</id>
            <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
        </snapshotRepository>
        <repository>
            <id>ossrh</id>
            <url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
        </repository>
    </distributionManagement>

    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <gpg.executable>gpg</gpg.executable>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.3.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>2.2.1</version>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar-no-fork</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>2.9.1</version>
                <executions>
                    <execution>
                        <id>attach-javadocs</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-gpg-plugin</artifactId>
                <version>1.6</version>
                <executions>
                    <execution>
                        <id>sign-artifacts</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>sign</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

如果读者成功上传了自己的依赖记得来给我star!!!