数字签名的应用示例

 

 

 

 

 

 

最近要开发一个对外部系统提供服务的接口,计划用数字签名进行接口的安全性校验,网上查找资料后,使用签名算法写了一个示例程序如下

目录

前言

数字签名简介

数字签名流程

公私钥对的生成

服务架构示意图

服务端和客户端约定

服务端项目digital-signature-server

客户端项目digital-signature-client

测试结果



  • 前言

示例项目中用到的算法及jar包版本如下:

消息摘要算法为MD5

签名算法为SHA256withRSA

项目基于springboot-2.2.5.RELEASE

json采用albaba的fastjson-1.2.48


  • 数字签名简介

数字签名是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。它是一种类似写在纸上的普通的物理签名,但是使用了公钥加密领域的技术来实现的,用于鉴别数字信息的方法。一套数字签名通常定义两种互补的运算,一个用于签名,另一个用于验证。数字签名是非对称密钥加密技术与数字摘要技术的应用。数字签名具有不可篡改性和不可抵赖性等特征,常用的数字签名算法有RSA,DSA,ECDSA等等。


  • 数字签名流程

  • 消息发送:

(1)消息发送者A使用消息摘要算法生成原文的数字摘要。

(2) 使用A自己的私钥加密数字摘要。

(3)将原文和加密之后的摘要发送给接收者B。

  • 消息接收:

(1)消息接收者B接收到消息之后,使用A的公钥对摘要解密。(A的公钥是公开的)

(2)B使用同样的摘要算法将所接收到的原文作为输入,生成数字摘要。

(3)将第2步所生成的摘要与第1步解密得到的摘要进行比较。如果相同,表示原文没有被篡改;如果不同,则被篡改了。


  • 公私钥对的生成

公私钥对的生成有多种方式,本次采用java代码生成的方式,具体代码如下:

Base64工具类:

import org.apache.commons.codec.binary.Base64;

public class Base64Util {

    public static byte[] decodeBase64(String str){
        Base64 base64=new Base64();
        return base64.decode(str.getBytes());
    }

    public static String encodeBase64(byte[] b){
        Base64 base64=new Base64();
        return new String(base64.encode(b));
    }
}

 公私钥对生成工具类:

package com.qianglittle.problem;

import com.qianglittle.problem.util.Base64Util;

import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.Map;

public class KeyGenerator {
    private static final String KEY_ALGORITHM="RSA";
    private static final String PUBLIC_KEY="publicKey";
    private static final String PRIVATE_KEY="privateKey";
    private static Map<String,Key> keyMap;
    public static void initKey() throws Exception{
        KeyPairGenerator keyPairGenerator=KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGenerator.initialize(1024);
        KeyPair keyPair=keyPairGenerator.generateKeyPair();
        RSAPublicKey publicKey=(RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey=(RSAPrivateKey) keyPair.getPrivate();
        keyMap= new HashMap<>(2);
        keyMap.put(PUBLIC_KEY,publicKey);
        keyMap.put(PRIVATE_KEY,privateKey);
    }

    public static String getPublicKeyStr(){
        Key key=keyMap.get(PUBLIC_KEY);
        return Base64Util.encodeBase64(key.getEncoded());
    }

    public static String getPrivateKeyStr(){
        Key key=keyMap.get(PRIVATE_KEY);
        return Base64Util.encodeBase64(key.getEncoded());
    }

    public static void main(String[] args) throws Exception {
        initKey();
        String privateKey=getPrivateKeyStr();
        String publicKey=getPublicKeyStr();
        System.out.println("privateKey:"+privateKey);
        System.out.println("publicKey:"+publicKey);
    }
}

运行main方法即可生成公私钥对,效果如下:


  • 服务架构示意图


  • 服务端和客户端约定

系统对接前

服务端生成自己的公私钥对,保存自己的私钥,然后将公钥发送给客户端

客户端也生成自己的公私钥对,保存自己的私钥,然后将公钥发送给服务端

服务端和客户端使用统一的类RequestBody和ResponseBody来处理请求及响应,请求、响应、签名分别封装进request、response、signature变量中代码如下:

public class RequestBody {
    private String request;
    private String signature;

    public String getRequest() {
        return request;
    }

    public void setRequest(String request) {
        this.request = request;
    }

    public String getSignature() {
        return signature;
    }

    public void setSignature(String signature) {
        this.signature = signature;
    }
}
public class ResponseBody {
    private String response;
    private String signature;

    public ResponseBody(String response, String signature) {
        this.response = response;
        this.signature = signature;
    }

    public String getResponse() {
        return response;
    }

    public void setResponse(String response) {
        this.response = response;
    }

    public String getSignature() {
        return signature;
    }

    public void setSignature(String signature) {
        this.signature = signature;
    }
}

客户端发送请求时,进行如下操作:

1.将请求参数由Java类转换为json字符串(即RequestBody中的request)

2.使用MD5算法生成request的摘要(digestMessage)

3.客户端使用自己的私钥生成签名(即RequestBody中的signature)

4.发送RequestBody

5.接收服务端响应并转换为ResponseBody类型的变量

6.使用服务端公钥验签

客户端发送请求示例代码:

public void sendRequestDemo(Person person) {
        //将参数转换为json字符串
        String reqStr = JSON.toJSONString(person);
        //生成摘要
        String digestMessage = messageDigestService.digest(reqStr);
        //生成数字签名
        String signature = digitalSignService.sign(digestMessage);
        //将请求和签名封装进RequestBody类型的变量中
        RequestBody requestBody = new RequestBody();
        requestBody.setRequest(reqStr);
        requestBody.setSignature(signature);
        try {
            //发送请求
            URI uri = new URI("http://localhost:8080/person/test");
            RestTemplate tlp = new RestTemplate();
            String responseBodyStr = tlp.postForObject(uri, requestBody, String.class);
            //将响应转换为ResponseBody类型
            ResponseBody responseBody = JSON.parseObject(responseBodyStr, ResponseBody.class);
            String responseStr = responseBody.getResponse();
            System.out.println("客户端responseStr:" + responseStr);
            String responseSignature = responseBody.getSignature();
            System.out.println("客户端responseSignature:" + responseSignature);
            String responseDigest = messageDigestService.digest(responseStr);
            //使用服务端公钥对响应进行验签
            boolean verify = digitalSignService.verify(responseSignature, responseDigest);
            System.out.println("客户端验签结果:" + verify);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

服务端接收请求后,对请求进行如下操作:

1.获取request和signature

2.使用MD5算法生成request的摘要(digestMessage)

3.服务端使用客户端的公钥验签

4.若验签失败则返回“验签失败”

5.若验签成功,则对请求进行处理

6.将要返回的结果由java类转换为json字符串(即ResponseBody中的response)

7.使用MD5算法生成response的摘要(digestMessage)

8.服务端使用自己的私钥生成签名(即ResponseBody中的signature)

9.返回ResponseBody


  • 服务端项目digital-signature-server

创建一个spirngboot项目,作为服务端

项目文件清单如下

pom.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>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xxx.xxx</groupId>
    <artifactId>digital-signature-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>digital-signature-server</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.6</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.48</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

secretKey.properties保存服务端私钥和客户端公钥

clientPublicKey=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCcfg5Ng2vd9XiSXFVSgGIuH4MIm+i+WH56grv2DtcoXxNb/kxbjCQGfZnqNERhk58Qt2ly+IlIKMil9HMFVd42bOmN3ngGOFcFLidaeaWiMvPFWut854zr4lLb0Gzu16iT/u4tF2/Oq6nItOMnQaJStr5jLKyFzgdwgN6876GdawIDAQAB
serverPrivateKey=MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJx+Dk2Da931eJJcVVKAYi4fgwib6L5YfnqCu/YO1yhfE1v+TFuMJAZ9meo0RGGTnxC3aXL4iUgoyKX0cwVV3jZs6Y3eeAY4VwUuJ1p5paIy88Va63znjOviUtvQbO7XqJP+7i0Xb86rqci04ydBolK2vmMsrIXOB3CA3rzvoZ1rAgMBAAECgYA7NiL5RzmgIQn+7vrFnZgIdZnhvwQgSWGJvz+ZSWI1f0vW6fBAT1UuM4XyLNaWyQFNlOhMPSfMasoIqOaAZU4PWCEP+kVA9iDRCCFxF2fJfMCOnrOx5o79S5yH/AoUWAc5kABaNG4Dg1koIg+0UV+sFZ42925mvSpkXw/dhVypUQJBANHm948fB/HvduC/UgZ3rJh74DHj54I3Fnle6twI/K6DMKCjaUtwKjvvQxtMzgk4rvomhD1WYIIPKXixhDW2u4kCQQC+3E4nPLbCBjtCDjvqcRC9OmxZIAYw/p6S68ohk8BrXhdCO5YwPaCPryvHxx1Hht84N8/YWhPKwWP0Yif6iFBTAkAGJxAAmPdBpzRD2DfOSrm7an4i2DxT+8tj2V1m/7hwYRYOz1tpw6rpQNUlurWbXZb7bB+aMKr5hPpBOGrYrDeJAkAu9QzVYn6kZdwWeGINYBv6MnGNy+86BqsFArYMZMmmoNOgHADrhX4HW9WtpTNy8Z/huPmOBTtxWvs4mR206ey5AkBprGTeD+iONcsn975sbiGXRSqgZ+LmbDw//J3CVUzV7tKCtULukz8HA8GhNNH//H13iPkd9KTGkmYkqnxYFlQ6

 KeyConfig读取secretKey.properties配置

@Configuration
@PropertySource(value="classpath:secretKey.properties")
public class KeyConfig {


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

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

    public String getServerPrivateKey() {
        return serverPrivateKey;
    }

    public void setServerPrivateKey(String serverPrivateKey) {
        this.serverPrivateKey = serverPrivateKey;
    }

    public String getClientPublicKey() {
        return clientPublicKey;
    }

    public void setClientPublicKey(String clientPublicKey) {
        this.clientPublicKey = clientPublicKey;
    }
}

 PublicAndPrivateKeyBuilder初始化服务端私钥和客户端公钥对并生成相应的bean

@Component
public class PublicAndPrivateKeyBuilder {

    @Resource
    KeyConfig keyConfig;

    @Bean
    public PrivateKey initServerPrivateKey() {
        try {
            String privateKey = keyConfig.getServerPrivateKey();
            if (privateKey != null && privateKey.trim().length() != 0) {
                byte[] privateKeys;
                privateKeys = Base64Util.decodeBase64(privateKey);
                PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeys);
                KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                return keyFactory.generatePrivate(privateKeySpec);
            }
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Bean
    public PublicKey initClientPublicKey() {
        try {
            String publicKey = keyConfig.getClientPublicKey();
            if (publicKey != null && publicKey.trim().length() != 0) {
                byte[] publicKeys;
                publicKeys = Base64Util.decodeBase64(publicKey);
                X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeys);
                KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                return keyFactory.generatePublic(publicKeySpec);
            }
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

 MessageDigestBuilder生成MD5消息摘要算法MessageDigest对应的bean

@Component
public class MessageDigestBuilder {

    @Bean
    public MessageDigest messageDigestInit(){
        try{
            return MessageDigest.getInstance("MD5");
        }catch(NoSuchAlgorithmException e){
            e.printStackTrace();
            return null;
        }
    }
}

消息摘要和签名服务MessageDigestService,DigitalSignService

@Service
public class MessageDigestServiceImpl implements MessageDigestService {

    private String CHARSET_UTF8 = "UTF-8";

    @Resource
    MessageDigest messageDigest;

    @Override
    public String digest(String srcMessage) {
        try {
            byte[] digestByteArray = messageDigest.digest(srcMessage.getBytes(CHARSET_UTF8));
            return Base64Util.encodeBase64(digestByteArray);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }
}
@Service
public class DigitalSignServiceImpl implements DigitalSignService {

    /**
     * 默认编码
     */
    private static final String DEFAULT_ENCODING = "UTF-8";

    /**
     * 签名算法
     */
    private static final String SIG_ALGORITHM="SHA256withRSA";

    @Resource
    PublicKey publicKey;

    @Resource
    PrivateKey privateKey;

    @Override
    public String sign(String unsigned) {
        String signed=null;
        try{
            byte sigData[];
            byte sourceData[] = unsigned.getBytes(DEFAULT_ENCODING);
            Signature sig=Signature.getInstance(SIG_ALGORITHM);
            sig.initSign(privateKey);
            sig.update(sourceData);
            sigData=sig.sign();
            signed=Base64Util.encodeBase64(sigData);
        }catch(Exception e){
            e.printStackTrace();
        }
        return signed;
    }

    @Override
    public boolean verify(String signature, String unsigned) {
        boolean valid=false;
        try{
            byte sourceData[] = unsigned.getBytes(DEFAULT_ENCODING);
            byte sigData[]=Base64Util.decodeBase64(signature);
            Signature sig=Signature.getInstance(SIG_ALGORITHM);
            sig.initVerify(publicKey);
            sig.update(sourceData);
            valid=sig.verify(sigData);
        }catch(Exception e){
            e.printStackTrace();
        }
        return valid;
    }
}

SignatureRequestWrapper:请求包装类,重写getInputStream方法,将RequestBody的request部分转发给controller 

public class SignatureRequestWrapper extends HttpServletRequestWrapper {
    private final RequestBody requestBody;

    public RequestBody getRequestBody() {
        return requestBody;
    }

    public SignatureRequestWrapper(final HttpServletRequest req) throws IOException {
        super(req);
        StringBuilder sb = new StringBuilder();
        String line;
        BufferedReader reader = req.getReader();
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
        requestBody = JSONObject.parseObject(sb.toString(), RequestBody.class);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody.getRequest().getBytes());
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

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

            @Override
            public void setReadListener(ReadListener readListener) {
            }

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

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

 SignatureResponseWrapper,响应包装类,重写getOutputStream方法,获取controller返回的原始响应数据,由SignatureFilter对响应数据进行加签操作


public class SignatureResponseWrapper extends HttpServletResponseWrapper {

    private ByteArrayOutputStream buffer;

    private ServletOutputStream out;

    public SignatureResponseWrapper(HttpServletResponse httpServletResponse) {
        super(httpServletResponse);
        buffer = new ByteArrayOutputStream();
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        out = new ServletOutputStream() {
            @Override
            public void write(int b) throws IOException {
                buffer.write(b);
            }

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

            @Override
            public void setWriteListener(WriteListener writeListener) {
            }
        };
        return out;
    }

    @Override
    public void flushBuffer() throws IOException {
        if (out != null) {
            out.flush();
        }
    }

    public byte[] getContent() throws IOException {
        return buffer.toByteArray();
    }
}

 SignatureFilter,签名过滤器,对请求进行验签,对响应进行加签操作

@WebFilter(urlPatterns = {"/person/test"})
public class SignatureFilter implements Filter {
    @Resource
    DigitalSignService digitalSignService;

    @Resource
    MessageDigestService messageDigestService;


    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response=(HttpServletResponse) servletResponse;
        try {
            SignatureRequestWrapper signatureRequestWrapper = new SignatureRequestWrapper(request);
            SignatureResponseWrapper signatureResponseWrapper=new SignatureResponseWrapper(response);
            String requestStr = signatureRequestWrapper.getRequestBody().getRequest();
            String requestSignature = signatureRequestWrapper.getRequestBody().getSignature();
            System.out.println("服务端filter接收到requestStr:" + requestStr);
            System.out.println("服务端filter接收到requestSignature:" + requestSignature);
            String requestDigest = messageDigestService.digest(requestStr);
            //对请求验签
            boolean verify = digitalSignService.verify(requestSignature, requestDigest);
            System.out.println("服务端验签结果:" + verify);
            if (verify) {
                filterChain.doFilter(signatureRequestWrapper, signatureResponseWrapper);
                String responseStr=new String(signatureResponseWrapper.getContent(),"UTF-8");
                System.out.println("服务端filter获取到responseStr: " +responseStr);
                //对响应加签
                String responseDigest = messageDigestService.digest(responseStr);
                String responseSignature = digitalSignService.sign(responseDigest);
                ResponseBody responseBody=new ResponseBody(responseStr,responseSignature);
                System.out.println("服务端filter生成的responseSignature: " +responseSignature);
                response.setContentLength(JSON.toJSONBytes(responseBody).length);
                response.getOutputStream().write(JSON.toJSONBytes(responseBody));
            } else {
                servletResponse.getWriter().println("验签失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void destroy() {

    }
}

服务端controller

@RestController
@RequestMapping("/person")
public class PersonController {
    @RequestMapping("/test")
    public Person test(@RequestBody Person person) {
        String personStr = "person.name:" + person.getName() + ",person.address:" + person.getAddress();
        System.out.println("服务端controller接收到请求:"+personStr);
        return person;
    }
}

实体类Person

public class Person {

    String name;

    String address;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

  • 客户端项目digital-signature-client

文件清单如下

pom.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>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xxx.xxx</groupId>
    <artifactId>digital-signature-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>digital-signature-server</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.6</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.48</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

 

secretKey.properties如下,包含客户端私钥和服务端公钥

serverPublicKey=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCcfg5Ng2vd9XiSXFVSgGIuH4MIm+i+WH56grv2DtcoXxNb/kxbjCQGfZnqNERhk58Qt2ly+IlIKMil9HMFVd42bOmN3ngGOFcFLidaeaWiMvPFWut854zr4lLb0Gzu16iT/u4tF2/Oq6nItOMnQaJStr5jLKyFzgdwgN6876GdawIDAQAB
clientPrivateKey=MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJx+Dk2Da931eJJcVVKAYi4fgwib6L5YfnqCu/YO1yhfE1v+TFuMJAZ9meo0RGGTnxC3aXL4iUgoyKX0cwVV3jZs6Y3eeAY4VwUuJ1p5paIy88Va63znjOviUtvQbO7XqJP+7i0Xb86rqci04ydBolK2vmMsrIXOB3CA3rzvoZ1rAgMBAAECgYA7NiL5RzmgIQn+7vrFnZgIdZnhvwQgSWGJvz+ZSWI1f0vW6fBAT1UuM4XyLNaWyQFNlOhMPSfMasoIqOaAZU4PWCEP+kVA9iDRCCFxF2fJfMCOnrOx5o79S5yH/AoUWAc5kABaNG4Dg1koIg+0UV+sFZ42925mvSpkXw/dhVypUQJBANHm948fB/HvduC/UgZ3rJh74DHj54I3Fnle6twI/K6DMKCjaUtwKjvvQxtMzgk4rvomhD1WYIIPKXixhDW2u4kCQQC+3E4nPLbCBjtCDjvqcRC9OmxZIAYw/p6S68ohk8BrXhdCO5YwPaCPryvHxx1Hht84N8/YWhPKwWP0Yif6iFBTAkAGJxAAmPdBpzRD2DfOSrm7an4i2DxT+8tj2V1m/7hwYRYOz1tpw6rpQNUlurWbXZb7bB+aMKr5hPpBOGrYrDeJAkAu9QzVYn6kZdwWeGINYBv6MnGNy+86BqsFArYMZMmmoNOgHADrhX4HW9WtpTNy8Z/huPmOBTtxWvs4mR206ey5AkBprGTeD+iONcsn975sbiGXRSqgZ+LmbDw//J3CVUzV7tKCtULukz8HA8GhNNH//H13iPkd9KTGkmYkqnxYFlQ6

applicaton.properties如下,设置服务启动端口为8081

server.port=8081

KeyConfig.java读取secretKey.properties配置 

@Configuration
@PropertySource(value="classpath:secretKey.properties")
public class KeyConfig {


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

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

    public String getClientPrivateKey() {
        return clientPrivateKey;
    }

    public void setClientPrivateKey(String clientPrivateKey) {
        this.clientPrivateKey = clientPrivateKey;
    }

    public String getServerPublicKey() {
        return serverPublicKey;
    }

    public void setServerPublicKey(String serverPublicKey) {
        this.serverPublicKey = serverPublicKey;
    }
}

PublicAndPrivateKeyBuilder初始化客户端私钥和服务端公钥对并生成相应的bean

@Component
public class PublicAndPrivateKeyBuilder {

    @Resource
    KeyConfig keyConfig;

    @Bean
    public PrivateKey initClientPrivateKey() {
        try {
            String privateKey = keyConfig.getClientPrivateKey();
            if (privateKey != null && privateKey.trim().length() != 0) {
                byte[] privateKeys;
                privateKeys = Base64Util.decodeBase64(privateKey);
                PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeys);
                KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                return keyFactory.generatePrivate(privateKeySpec);
            }
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Bean
    public PublicKey initServerPublicKey() {
        try {
            String publicKey = keyConfig.getServerPublicKey();
            if (publicKey != null && publicKey.trim().length() != 0) {
                byte[] publicKeys;
                publicKeys = Base64Util.decodeBase64(publicKey);
                X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeys);
                KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                return keyFactory.generatePublic(publicKeySpec);
            }
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

 SignatureController,测试客户端发送请求到服务端,并对服务端响应进行处理

@RestController
@RequestMapping("/signature")
public class SignatureController {

    @Resource
    MessageDigestService messageDigestService;

    @Resource
    DigitalSignService digitalSignService;

    @RequestMapping("/sendRequest")
    public ResponseBody sendRequest(Person person) {
        String reqStr = JSON.toJSONString(person);
        String digestMessage = messageDigestService.digest(reqStr);
        String signature = digitalSignService.sign(digestMessage);
        RequestBody requestBody = new RequestBody();
        requestBody.setRequest(reqStr);
        requestBody.setSignature(signature);
        try {
            URI uri = new URI("http://localhost:8080/person/test");
            RestTemplate tlp = new RestTemplate();
            String responseBodyStr = tlp.postForObject(uri, requestBody, String.class);
            ResponseBody responseBody = JSON.parseObject(responseBodyStr, ResponseBody.class);
            String responseStr = responseBody.getResponse();
            System.out.println("客户端responseStr:" + responseStr);
            String responseSignature = responseBody.getSignature();
            System.out.println("客户端responseSignature:" + responseSignature);
            String responseDigest = messageDigestService.digest(responseStr);
            //对响应进行验签
            boolean verify = digitalSignService.verify(responseSignature, responseDigest);
            System.out.println("客户端验签结果:" + verify);
            sendRequestDemo(person);
            return responseBody;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

   
}

其他文件同服务端一致


  • 测试结果

启动服务端和客户端

使用postman发送请求,url及参数如下图

服务端控制台输出如下图

客户端控制台输出如下图

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值