Vue+Springboot实现接口签名的示例代码

1、实现思路

接口签名目的是为了,确保请求参数不会被篡改,请求的数据是否已超时,数据是否重复提交等。

接口签名示意图

客户端提交请求时,将以下参数按照约定签名方式进行签名,随后将参数和签名一同提交服务端:

1.请求头部分(header)
appid:针对不同的调用方分配不同的appid。
noce:请求的流水号,防止重复提交。
timestamp:请求时间戳,验证请求是否已超时失效。

2.数据部分
Path:按照path中的参数将所有key=value进行拼接。
Query:按照所有key=value进行拼接。
Form:按照所有key=value进行拼接
Body:Json,按照所有key=value进行拼接。String,整个字符串作为一个拼接。

签名

服务端提接收交请求后,同样通过接收的“请求头部分”、“数据部分”的参数进行拼接。随后验证客户端提交的签名是否正确。

2、代码实现

客户端(Vue)首先需要安装“jsrsasign”库,以便实现 RSA 加密、解密、签名、验签等功能。
官方地址:http://kjur.github.io/jsrsasign/
执行以下命令:

npm install jsrsasign -save

安装完成后,封装sign.js

import {KJUR, KEYUTIL, hex2b64, b64tohex} from 'jsrsasign'

// 签名算法
const ALGORITHM = 'SHA256withRSA'

// 私钥签名
const RSA_SIGN = (privateKey, src) => {
    const signature = new KJUR.crypto.Signature({'alg': ALGORITHM})
    // 来解析密钥
    const priKey = KEYUTIL.getKey(privateKey)
    signature.init(priKey)
    // 传入待签明文
    signature.updateString(src)
    const a = signature.sign()
    // 转换成base64,返回
    return hex2b64(a)
}
// 公钥验签
const RSA_VERIFY_SIGN = (publicKey, src, data) => {
    const signature = new KJUR.crypto.Signature({'alg': ALGORITHM, 'prvkeypem': publicKey})
    signature.updateString(src)
    return signature.verify(b64tohex(data))
}

export {
    RSA_SIGN,
    RSA_VERIFY_SIGN
}

客户端(Vue)通过sign.js进行加签、验签。

const src = '我是一段测试字符串2'

const publicKey = '-----BEGIN PUBLIC KEY-----\n' +
            'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC35wxzdTzseajkYL06hEKBCEJu\n' +
            'JQ/nySId2oTnsxbLiSTEjpAESSbML1lqkKaIwjrSFZzyLMH6DirsoEQcATqqoCDU\n' +
            '/H9QNVb5jMSAxxdQusQkTWz6k07bEuy1ppVjpGxNi8o2OGNd+lwPC/hOSDR7lpfm\n' +
            'aXLIjEwKSXzil7YAHQIDAQAB\n' +
            '-----END PUBLIC KEY-----'

const privateKey = '-----BEGIN PRIVATE KEY-----\n' +
            'MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBALfnDHN1POx5qORg\n' +
            'vTqEQoEIQm4lD+fJIh3ahOezFsuJJMSOkARJJswvWWqQpojCOtIVnPIswfoOKuyg\n' +
            'RBwBOqqgINT8f1A1VvmMxIDHF1C6xCRNbPqTTtsS7LWmlWOkbE2LyjY4Y136XA8L\n' +
            '+E5INHuWl+ZpcsiMTApJfOKXtgAdAgMBAAECgYB2PAcGSC7mPoW2ZvfiIlx7hurm\n' +
            '0885D1hu5yohqUOTklXgRWQUTU+zYRHU8LERJgcZQKoKDXqdIPS584Q2mRe0uZMr\n' +
            'vaiaBVEnHQreUJUQ8UN12pPUdBHDZvOk3L7/fZHk6A8uy5e09p2rsn+Vfki3zijp\n' +
            '7Pd758HMtjuiHBb2QQJBAOuN6jdWBr/zb7KwM9N/cD1jJd6snOTNsLazH/Z3Yt0T\n' +
            'jlsFmRJ6rIt/+jaLKG6YTR8SFyW5LIQTbreeQHPw4FECQQDH3Wpd/mBMMcgpxLZ0\n' +
            'F5p1ieza+VA5fbxkQ0hdubEP26B6YwhkTB/xMSOwEjmUI57kfgOTvub36/peb8rI\n' +
            'JdwNAkB3fzwlrGeqMzYkIU15avomuki46TqCvHJ8jOyXHUOzQbuDI5jfDgrAjkEC\n' +
            'MKBnUq41J/lEMueJbU5KqmaqKrWxAkAyexlHnl1iQVymOBpBXkjUET8y26/IpZp0\n' +
            '1I2tpp4zPCzfXK4c7yFOQTQbX68NXKXgXne21Ivv6Ll3KtNUFEPtAkBcx5iWU430\n' +
            '0/s6218/enaa8jgdqw8Iyirnt07uKabQXqNnvbPYCgpeswEcSvQqMVZVKOaMrjKO\n' +
            'G319Es83iq/m\n' +
            '-----END PRIVATE KEY-----\n'

console.log('明文:', src)
const data = RSA_SIGN(privateKey, src)
console.log('签名后的结果:', data)

const res = RSA_VERIFY_SIGN(publicKey, src, data)
console.log('验签结果:', res)

服务端(Spring boot)接收请求后,需要对数据和签名,进行验证。

首先引入依赖——hutool工具包,Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。

官网地址:https://www.hutool.cn/

在pom.xml下增加如下配置:

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.3.5</version>
</dependency>

服务端(Spring boot)首先要获取客户端(Vue)请求的数据,上文已经描述了请求的数据有两部分,分别是“请求头部分”、“数据部分”。所以需要配置拦截器,对以上两部分进行获取。

配置拦截器(MyInterceptor.java),代码如下:

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
@Component
public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取请求参数
        String queryString = request.getQueryString();
        log.info("请求参数:{}", queryString);

        // 获取header
        log.info("key:{}",request.getHeader("timestamp"));

        MyHttpServletRequestWrapper myRequestWrapper = new MyHttpServletRequestWrapper(request);
        //获取请求body
        byte[] bodyBytes = StreamUtils.copyToByteArray(myRequestWrapper.getInputStream());
        String body = new String(bodyBytes, request.getCharacterEncoding());

        log.info("请求体:{}", body);

        return true;
    }
}

在获取“请求体body”时,由于“HttpServletRequest”只能读取一次,拦截器读取后,后续Controller在读取时为空,所以需要重写HttpServletRequestWrapper:

import org.springframework.util.StreamUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {

    /**
     * 缓存下来的HTTP body
     */
    private byte[] body;

    public MyHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        body = StreamUtils.copyToByteArray(request.getInputStream());
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        InputStream bodyStream = new ByteArrayInputStream(body);
        return new ServletInputStream(){

            @Override
            public int read() throws IOException {
                return bodyStream.read();
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
}

之后,需要创建过滤器,将“MyHttpServletRequestWrapper” 替换“ServletRequest”,代码如下:

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Slf4j
public class RepeatedlyReadFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if(servletRequest instanceof HttpServletRequest) {
            requestWrapper = new MyHttpServletRequestWrapper((HttpServletRequest) servletRequest);
        }
        if(requestWrapper == null) {
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            filterChain.doFilter(requestWrapper, servletResponse);
        }

    }

    @Override
    public void destroy() {

    }
}

之后创建自定义配置,CorsConfig.java,将过滤器、拦截器加入配置:

import com.xyf.interceptor.MyInterceptor;
import com.xyf.interceptor.RepeatedlyReadFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Configuration
public class CorsConfig extends WebMvcConfigurationSupport {

    private MyInterceptor myInterceptor;

    @Autowired
    public CorsConfig (MyInterceptor myInterceptor){
        this.myInterceptor = myInterceptor;
    }

    // 注册过滤器
    @Bean
    public FilterRegistrationBean<RepeatedlyReadFilter> repeatedlyReadFilter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        RepeatedlyReadFilter repeatedlyReadFilter = new RepeatedlyReadFilter();
        registration.setFilter(repeatedlyReadFilter);
        registration.addUrlPatterns("/*");
        return registration;
    }

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        // addPathPatterns添加需要拦截的命名空间;
        // excludePathPatterns添加排除拦截命名空间

        registry.addInterceptor(myInterceptor).addPathPatterns("/**");
        //.excludePathPatterns("/api/sys/login")
    }

}

最后,完成验签,代码如下:

import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;

byte[] data = "我是一段测试字符串2".getBytes();
        String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC35wxzdTzseajkYL06hEKBCEJu\n" +
                "JQ/nySId2oTnsxbLiSTEjpAESSbML1lqkKaIwjrSFZzyLMH6DirsoEQcATqqoCDU\n" +
                "/H9QNVb5jMSAxxdQusQkTWz6k07bEuy1ppVjpGxNi8o2OGNd+lwPC/hOSDR7lpfm\n" +
                "aXLIjEwKSXzil7YAHQIDAQAB";

Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA,null,publicKey);

//客户端传来的签名
String qm = "IhY3LNuFn0isud1Pk6BL2eJV3Jl/UzDCYsdG9CYyJwOGqwnzStsv/RiYLnVP4bnQh1NRPMazY6ux/5Zz5Ypcx6RI5W1p5BDbO2afuIZX7x/eIu5utwsanhbxEfvm3XOsyuTbnMDh6BQUrXb4gUz9qgt9IXWjQdqnQRRv3ywzWcA=";
byte[] signed = Base64.decode(qm);

//验证签名
boolean verify = sign.verify(data, signed);

3、公钥、私钥生成

可通过一些网站在线生成公钥、私钥
网址:https://www.bejson.com/enc/rsa/

bejson在线生成公钥、私钥

4、其他问题

由于客户端加签、服务端验签。所以加签、验签的方式务必一致,否则将无法验证签名。Vue、Java有不同的签名工具库,使用前要做好测试。

到此这篇关于Vue+Springboot实现接口签名的示例代码的文章就介绍到这了,更多相关Springboot 接口签名内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot使用Feign调用其他服务接口

    使用SpringCloud的Feign组件能够为服务间的调用节省编码时间并提高开发效率,当服务本身不复杂时可以单独将该组件拿出使用. 引入依赖 <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign --> <dependency> <groupId>org.springframework.cloud</groupId>

  • 基于注解实现 SpringBoot 接口防刷的方法

    该示例项目通过自定义注解,实现接口访问次数控制,从而实现接口防刷功能,项目结构如下: 一.编写注解类 AccessLimit package cn.mygweb.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Targ

  • 使用SpringBoot跨系统调用接口的方案

    一.简介 项目开发中存在系统之间互调问题,又不想用dubbo,这里提供几种springboot方案: 1.使用Feign进行消费(推荐) 2.使用原始httpClient请求 3.使用RestTemplate方法 二.方案 方案一:使用Feign进行消费(推荐) 1.在maven中添加依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-st

  • 教你利用springboot集成swagger并生成接口文档

    效果图 实现步骤 1.maven中引入jar包,不同版本的swagger可能页面效果不一样. <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.1</version> </dependency> <dependency> <groupId&g

  • springboot运行时新增/更新外部接口的实现方法

    最近有个需求:需要让现有springboot项目可以加载外部的jar包实现新增.更新接口逻辑.本着拿来主义的思维网上找了半天没有找到类似的东西,唯一有点相似的还是spring-loaded但是这个东西据我网上了解有如下缺点: 1.使用java agent启动,个人倾向于直接使用pom依赖的方式 2.不支持新增字段,新增方法,估计也不支持mybatis的xml加载那些吧,没了解过 3.只适合在开发环境IDE中使用,没法生产使用 无奈之下,我只能自己实现一个了,我需要实现的功能如下 1.加载外部扩展

  • SpringBoot同一接口多个实现类配置的实例详解

    SpringBoot项目中可能出现一个接口有多个实现类的情况,如果不进行配置,注入接口时编译器不知道要注入哪个实现类就会报错,因此需要进行配置.以下进行举例: 接口如下: public interface NoticeService { public String noticeUser(Long id); } 两个实现类如下: @Service public class NoticeServiceImpl1 implements NoticeService { public String not

  • SpringBoot 防止接口恶意多次请求的操作

    前言 刚写代码不就,还不能做深层次安全措施,今天研究了一下基本的防止接口多次恶意请求的方法. 思路 1:设置同一IP,一个时间段内允许访问的最大次数 2:记录所有IP单位时间内访问的次数 3:将所有被限制IP存到存储器 4:通过IP过滤访问请求 该demo只有后台Java代码,没有前端 代码 首先是获取IP的工具类 public class Ipsettings { public static String getRemoteHost(HttpServletRequest request) {

  • SpringBoot集成Spring security JWT实现接口权限认证

    1.添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjw

  • 使用springboot暴露oracle数据接口的问题

    新建一个Spring Initializr项目 2.把pom.xml文件中的oracle依赖换成自己的oracle版本依赖: 原来的: 现在的: <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> <version>11.2.0.1.0</version> </dependency> 3.编辑application

  • Springboot添加支付接口

    1. 支付宝支付接口(沙箱实现) 1.1 支付宝沙箱账号获取 官网 此处作者已经申请了一个沙箱账号,申请过程就不再赘述 如下图: 此处可以自行设置账户金额 1.2 下载客户端(目前好像只支持Android) 下载完成后根据官方提供的账号以及密码登录手机端支付宝账号 如图(商家账号): 1.3 代码配置 工具类AlipayConfig public class AlipayConfig { //↓↓↓↓↓↓↓↓↓↓请在这里配置您的基本信息↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ // 应用ID,您的APPI

  • SpringBoot可视化接口开发工具magic-api的简单使用教程

    目录 magic-api简介 使用 在SpringBoot中使用 增删改查 参数验证 结果转换 使用事务 集成Swagger 总结 参考资料 magic-api简介 magic-api是一个基于Java的接口快速开发框架,编写接口将通过magic-api提供的UI界面完成,自动映射为HTTP接口,无需定义Controller.Service.Dao.Mapper.XML.VO等Java对象. 使用 下面我们来波实战,熟悉下使用magic-api来开发API接口. 在SpringBoot中使用 m

随机推荐