Java开发一个接口提供给第三方调用

1. 环境

基于SpringBoot编写一个接口,提供给第三方调用。类似于我们使用阿里的语音识别功能,我们可以调用阿里封装好的api,也就是通过发送HTTP请求的方式来做语音识别。本篇文章主要记录在SpringBoot中我们是如何开发接口并让别人可以安全调用的。

使用到的依赖:pom.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://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>3.2.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>top.lukeewin</groupId>
    <artifactId>Signature</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Signature</name>
    <description>Signature</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

2. 加密算法的选择

使用MD5这种算法加密是不太安全的,所以这里我们使用hash算法中的HmacSHA256加密算法来生成签名,当我们请求接口时,我们使用把签名和时间戳带上,为啥还要带上时间戳呢,是因为我们之后要控制签名的过期时间需要根据这个前端传递过来的时间戳来计算过期时间。

下面是加密工具类:

java 复制代码
public class SignatureUtil {
    public static String getSignature(String timestamp, String apiKey, String apiSecret) {
        // 构建签名字符串
        String signatureString = apiKey + timestamp;

        String signature = null;

        // 计算签名
        try {
            Mac sha256Hmac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            sha256Hmac.init(secretKey);
            byte[] signatureBytes = sha256Hmac.doFinal(signatureString.getBytes(StandardCharsets.UTF_8));
            signature = Base64.getEncoder().encodeToString(signatureBytes);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            throw new RuntimeException(e);
        }
        return signature;
    }
}

3. 编写一个接口

这里简单编写一个音频转码的接口,来模拟我们开发接口的整个过程。

java 复制代码
@RestController
public class TransferController {

    @RequestMapping("/transfer")
    public BaseResponse transfer() {
        return BaseResponse.success("转码成功");
    }

    @RequestMapping("/ban")
    public BaseResponse ban() {
        return BaseResponse.error(ErrorCode.VERIFY_NO_PASS);
    }
}

4. 自定义一个拦截器

自定义拦截器,对全面请求进行拦截判断是否传递了签名和时间戳,并且判断传递过来的签名和后端计算出来的签名一不一致,还需判断传递到后端时这个签名有没有过期,如果上面这些条件有一个不成立就进行拦截,否则放行。

java 复制代码
@Component
public class SignatureInterceptor implements HandlerInterceptor {
    @Value("${apiKey}")
    private String apiKey;

    @Value("${apiSecret}")
    private String apiSecret;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String sign = request.getParameter("sign");
        String timestamp = request.getParameter("timestamp");
        if (StringUtils.isNotBlank(sign) && StringUtils.isNotBlank(timestamp)) {
            String signature = SignatureUtil.getSignature(timestamp, apiKey, apiSecret);
            if (StringUtils.isNotBlank(signature) && signature.equals(sign) && System.currentTimeMillis() - Long.parseLong(timestamp) < 50 * 1000) {
                return true;
            } else {
                request.getRequestDispatcher("/ban").forward(request, response);
                return false;
            }
        } else {
            request.getRequestDispatcher("/ban").forward(request, response);
            return false;
        }
    }
}

注意点:

  1. 必须要把该类交给Spring IOC容器进行管理,也就是需要在类上面添加一个注解@Component
  2. 拦截后需要给调用方一个提示,否则调用方不知道是否被拦截,所以这里需要使用request.getRequestDispatcher("/ban").forward(request, response);
  3. 必须要放行拦截的URL,如果不放行,会产生死循环,在这里也就是需要放行/ban接口

5. 编写一个拦截器的配置类

编写拦截器配置类,把自定义的拦截器添加到配置类中。

java 复制代码
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Resource
    private SignatureInterceptor signatureInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(signatureInterceptor).addPathPatterns("/**").excludePathPatterns("/ban");
    }
}

注意点:

  1. 必须要有@Configuration注解
  2. 不能使用new 自定义拦截器类的方式添加进来,必须要使用注入的方式注入进来。也就是不能写成这样registry.addInterceptor(new SignatureInterceptor()).addPathPatterns("/**")

6. 统一接口的响应格式

创建两个工具类,一个是响应基类,一个是错误类。

响应基类:BaseResponse

java 复制代码
@Data
public class BaseResponse<T> implements Serializable {
    private static final long serialVersionUID = 4L;
    private Integer code;
    private String message;
    private Long timestamp = System.currentTimeMillis();
    private T data;

    public static <T> BaseResponse<T> success(T data) {
        BaseResponse<T> resultData = new BaseResponse<>();
        resultData.setCode(200);
        resultData.setMessage("OK");
        resultData.setData(data);
        return resultData;
    }

    public static BaseResponse error(ErrorCode errorCode) {
        BaseResponse resultData = new BaseResponse();
        resultData.setCode(errorCode.getCode());
        resultData.setMessage(errorCode.getMessage());
        return resultData;
    }
}

这里用到了@Data注解,是lombok提供的一个注解,所以你需要在pom.xml中引入lombok依赖。

编写错误码类:ErrorCode

java 复制代码
public enum ErrorCode {
    VERIFY_NO_PASS(300, "签名验证未通过");
    private final Integer code;
    private final String message;

    ErrorCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

7. 配置文件

在自定义拦截器中,我们通过@Value注解从项目的配置文件application.yml中获取apiKeyapiSecret

application.yml文件如下:

yaml 复制代码
apiKey: dhkadj123fda
apiSecret: hgjdakf12314sdf

对应的视频教程已经上传到B站中,如果不喜欢看文字内容,也可以看视频

相关推荐
哎呦没13 分钟前
SpringBoot框架下的资产管理自动化
java·spring boot·后端
m0_571957582 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
一点媛艺3 小时前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风3 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
奋斗的小花生4 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功4 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2344 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨4 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程5 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk6 小时前
Go-性能调优实战案例
开发语言·后端·golang