自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

專業(yè)!Spring Boot 3.3 集成 iText 實現(xiàn)高效電子簽章

開發(fā) 前端
本文介紹了在 Spring Boot 3.3 項目中集成 iText 實現(xiàn)電子簽章的完整流程。通過配置文件管理簽章參數(shù)、使用 @ConfigurationProperties? 注入配置、Lombok? 簡化代碼,以及使用 jQuery?與 Thymeleaf 搭建前端界面,我們構(gòu)建了一個簡單而專業(yè)的電子簽章功能。

在現(xiàn)代企業(yè)應(yīng)用中,電子簽章技術(shù)在文檔簽署、文件認(rèn)證和法律效力保障中發(fā)揮著重要作用。通過 Bouncy Castle 生成數(shù)字證書來加密簽章數(shù)據(jù)并驗證簽章合法性。本文將介紹如何在 Spring Boot 3.3 項目中集成 iText 實現(xiàn)電子簽章功能,內(nèi)容涵蓋生成數(shù)字證書、繪制簽章圖片、項目配置和代碼示例。

運行效果:

圖片圖片

圖片圖片

若想獲取項目完整代碼以及其他文章的項目源碼,且在代碼編寫時遇到問題需要咨詢交流,歡迎加入下方的知識星球。

使用 Bouncy Castle 生成數(shù)字證書

在生成數(shù)字證書之前,需要在 pom.xml 中添加 Bouncy Castle 的依賴:

<?xml versinotallow="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.3.5</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.icoderoad</groupId>
	<artifactId>itext_sign_pdf</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>itext_sign_pdf</name>
	<description>Demo project for Spring Boot</description>
	
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-web</artifactId>
	    </dependency>
	    <!-- Thymeleaf -->
	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-thymeleaf</artifactId>
	    </dependency>
	     <!-- Lombok 依賴 -->
	    <dependency>
	        <groupId>org.projectlombok</groupId>
	        <artifactId>lombok</artifactId>
	        <scope>provided</scope>
	    </dependency>
		<dependency>
		    <groupId>org.bouncycastle</groupId>
		    <artifactId>bcprov-jdk15to18</artifactId>
		    <version>1.78.1</version>
		</dependency>
		<dependency>
		    <groupId>org.bouncycastle</groupId>
		    <artifactId>bcpkix-jdk15to18</artifactId>
		    <version>1.78.1</version>
		</dependency>
		<dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcprov-ext-jdk15to18</artifactId>
        <version>1.78.1</version>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>itextpdf</artifactId>
        <version>5.5.13.4</version>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>html2pdf</artifactId>
        <version>5.0.5</version>
    </dependency>
        
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

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

</project>

然后,可以使用以下代碼生成一個自簽名的數(shù)字證書(.p12 文件),用于后續(xù)簽章操作:

package com.icoderoad.util;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.*;

public class PkcsUtil {

    /**
     * 生成證書
     *
     * @return
     * @throws NoSuchAlgorithmException
     */
    private static KeyPair getKey() throws NoSuchAlgorithmException {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA",
                new BouncyCastleProvider());
        generator.initialize(1024);
        // 證書中的密鑰 公鑰和私鑰
        KeyPair keyPair = generator.generateKeyPair();
        return keyPair;
    }

    /**
     * 生成證書
     *
     * @param password
     * @param issuerStr
     * @param subjectStr
     * @param certificateCRL
     * @return
     */
    public static Map<String, byte[]> createCert(String password, String issuerStr, String subjectStr, String certificateCRL) {
        Map<String, byte[]> result = new HashMap<String, byte[]>();
        try(ByteArrayOutputStream out= new ByteArrayOutputStream()) {
            // 標(biāo)志生成PKCS12證書
            KeyStore keyStore = KeyStore.getInstance("PKCS12", new BouncyCastleProvider());
            keyStore.load(null, null);
            KeyPair keyPair = getKey();
            // issuer與 subject相同的證書就是CA證書
            X509Certificate cert = generateCertificateV3(issuerStr, subjectStr,
                    keyPair, result, certificateCRL);
            // 證書序列號
            keyStore.setKeyEntry("cretkey", keyPair.getPrivate(),
                    password.toCharArray(), new X509Certificate[]{cert});
            cert.verify(keyPair.getPublic());
            keyStore.store(out, password.toCharArray());
            byte[] keyStoreData = out.toByteArray();
            result.put("keyStoreData", keyStoreData);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 生成證書
     * @param issuerStr
     * @param subjectStr
     * @param keyPair
     * @param result
     * @param certificateCRL
     * @return
     */
    public static X509Certificate generateCertificateV3(String issuerStr,
                                                        String subjectStr, KeyPair keyPair, Map<String, byte[]> result,
                                                        String certificateCRL) {
        ByteArrayInputStream bint = null;
        X509Certificate cert = null;
        try {
            PublicKey publicKey = keyPair.getPublic();
            PrivateKey privateKey = keyPair.getPrivate();
            Date notBefore = new Date();
            Calendar rightNow = Calendar.getInstance();
            rightNow.setTime(notBefore);
            // 日期加1年
            rightNow.add(Calendar.YEAR, 1);
            Date notAfter = rightNow.getTime();
            // 證書序列號
            BigInteger serial = BigInteger.probablePrime(256, new Random());
            X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(
                    new X500Name(issuerStr), serial, notBefore, notAfter,
                    new X500Name(subjectStr), publicKey);
            JcaContentSignerBuilder jBuilder = new JcaContentSignerBuilder(
                    "SHA1withRSA");
            SecureRandom secureRandom = new SecureRandom();
            jBuilder.setSecureRandom(secureRandom);
            ContentSigner singer = jBuilder.setProvider(
                    new BouncyCastleProvider()).build(privateKey);
            // 分發(fā)點
            ASN1ObjectIdentifier cRLDistributionPoints = new ASN1ObjectIdentifier(
                    "2.5.29.31");
            GeneralName generalName = new GeneralName(
                    GeneralName.uniformResourceIdentifier, certificateCRL);
            GeneralNames seneralNames = new GeneralNames(generalName);
            DistributionPointName distributionPoint = new DistributionPointName(
                    seneralNames);
            DistributionPoint[] points = new DistributionPoint[1];
            points[0] = new DistributionPoint(distributionPoint, null, null);
            CRLDistPoint cRLDistPoint = new CRLDistPoint(points);
            builder.addExtension(cRLDistributionPoints, true, cRLDistPoint);
            // 用途
            ASN1ObjectIdentifier keyUsage = new ASN1ObjectIdentifier(
                    "2.5.29.15");
            // | KeyUsage.nonRepudiation | KeyUsage.keyCertSign
            builder.addExtension(keyUsage, true, new KeyUsage(
                    KeyUsage.digitalSignature | KeyUsage.keyEncipherment));
            // 基本限制 X509Extension.java
            ASN1ObjectIdentifier basicConstraints = new ASN1ObjectIdentifier(
                    "2.5.29.19");
            builder.addExtension(basicConstraints, true, new BasicConstraints(
                    true));
            X509CertificateHolder holder = builder.build(singer);
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            bint = new ByteArrayInputStream(holder.toASN1Structure()
                    .getEncoded());
            cert = (X509Certificate) cf.generateCertificate(bint);
            byte[] certBuf = holder.getEncoded();
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
            // 證書數(shù)據(jù)
            result.put("certificateData", certBuf);
            //公鑰
            result.put("publicKey", publicKey.getEncoded());
            //私鑰
            result.put("privateKey", privateKey.getEncoded());
            //證書有效開始時間
            result.put("notBefore", format.format(notBefore).getBytes("utf-8"));
            //證書有效結(jié)束時間
            result.put("notAfter", format.format(notAfter).getBytes("utf-8"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bint != null) {
                try {
                    bint.close();
                } catch (IOException e) {
                }
            }
        }
        return cert;
    }

    public static void main(String[] args) throws Exception {
        // CN: 名字與姓氏    OU : 組織單位名稱
        // O :組織名稱  L : 城市或區(qū)域名稱  E : 電子郵件
        // ST: 州或省份名稱  C: 單位的兩字母國家代碼
        String issuerStr = "CN=javaboy,OU=開發(fā)部,O=路條編程,C=CN,E=happyzjp@gmail.com,L=北京,ST=北京";
        String subjectStr = "CN=javaboy,OU=開發(fā)部,O=路條編程,C=CN,E=happyzjp@gmail.com,L=北京,ST=北京";
        String certificateCRL = "http://www.icoderoad.com";
        Map<String, byte[]> result = createCert("89765431", issuerStr, subjectStr, certificateCRL);

        FileOutputStream outPutStream = new FileOutputStream("keystore.p12");
        outPutStream.write(result.get("keyStoreData"));
        outPutStream.close();
        FileOutputStream fos = new FileOutputStream(new File("keystore.cer"));
        fos.write(result.get("certificateData"));
        fos.flush();
        fos.close();
    }

}

運行此工具代碼后,將在當(dāng)前工程目錄中生成兩個文件:keystore.p12 和 keystore.cer。

  • keystore.cer 文件通常以 DER 或 PEM 格式存儲,包含 X.509 公鑰證書。它不僅包含公鑰,還記錄了證書持有者的相關(guān)信息,如姓名、組織、地理位置等。
  • keystore.p12 文件采用 PKCS#12 格式,是一種個人信息交換標(biāo)準(zhǔn),用于存儲一個或多個證書及其對應(yīng)的私鑰。.p12 文件是加密的,通常需要密碼才能打開。這種文件格式便于在不同系統(tǒng)或設(shè)備之間安全地傳輸和存儲證書和私鑰。

總結(jié)來說,.cer 文件通常僅包含公鑰證書,而 .p12 文件則可以包含證書及其對應(yīng)的私鑰。

使用 Java 代碼繪制簽章圖片

除了數(shù)字證書,電子簽章通常還需要一個可視化的簽章圖片。以下代碼將生成一個簡單的簽章圖片,并保存為 PNG 格式文件,供后續(xù)簽章操作使用:

CreateSeal 類

package com.icoderoad.itext_sign_pdf.util;

public class CreateSeal{
    public static void main(String[] args) throws Exception {
        Seal seal = new Seal();
        seal.setSize(200);
        SealCircle sealCircle = new SealCircle();
        sealCircle.setLine(4);
        sealCircle.setWidth(95);
        sealCircle.setHeight(95);
        seal.setBorderCircle(sealCircle);
        SealFont mainFont = new SealFont();
        mainFont.setText("路條編程科技有限公司");
        mainFont.setSize(22);
        mainFont.setFamily("隸書");
        mainFont.setSpace(22.0);
        mainFont.setMargin(4);
        seal.setMainFont(mainFont);
        SealFont centerFont = new SealFont();
        centerFont.setText("★");
        centerFont.setSize(60);
        seal.setCenterFont(centerFont);
        SealFont titleFont = new SealFont();
        titleFont.setText("公司專用章");
        titleFont.setSize(16);
        titleFont.setSpace(8.0);
        titleFont.setMargin(54);
        seal.setTitleFont(titleFont);
        seal.draw("公司公章1.png");
    }
}

以上代碼會生成一個帶有指定文本的簽章圖片,可以將圖片路徑配置在 application.yml 中供簽章使用。此代碼生成的 PNG 文件可以直接用于電子簽章過程。最終生成的簽章圖片類似下面這樣:

在這里提到的一些工具類未提供,需要通過加入星球獲取。

項目依賴配置

在 Spring Boot 項目中使用 iText 實現(xiàn)電子簽章功能,需要在 pom.xml 文件中添加相關(guān)依賴配置:

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.icoderoad</groupId>
    <artifactId>springboot-signature</artifactId>
    <version>1.0.0</version>
    <dependencies>
        <!-- Spring Boot Starter Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- iText for PDF signature -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext7-core</artifactId>
            <version>7.1.13</version>
        </dependency>

        <!-- Lombok for automatic getter, setter generation -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

配置文件 application.yml

為了使電子簽章功能的參數(shù)更加靈活,我們在 application.yml 中設(shè)置一些配置信息,例如簽章圖片路徑、證書路徑等。通過使用 @ConfigurationProperties 讀取這些配置信息,便于后續(xù)開發(fā)和維護(hù)。

signature:
  image-path: "/path/to/signature.png"
  certificate-path: "/path/to/certificate.p12"
  certificate-password: "yourpassword"
  position:
    x: 400                    # 默認(rèn)簽章X坐標(biāo)
    y: 50  										 #距離頁面底部距離

配置類 SignatureProperties

@ConfigurationProperties 注解用于讀取配置文件中的 signature 配置項,將其注入到配置類 SignatureProperties 中,并使用 Lombok 注解簡化代碼。

package com.icoderoad.itext_sign_pdf.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;


@Data
@Component
@ConfigurationProperties(prefix = "signature")
public class SignatureProperties {

    private String certificatePath;
    private String signImage;
    private String certificatePassword;
    private Position position = new Position();

    @Data
    public static class Position {
        private float x;
        private float y;
    }

}

配置類

package com.icoderoad.itext_sign_pdf.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 添加對/static/**路徑的支持
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/");
    }
}

后端代碼實現(xiàn):

控制器層

顯示控制類

package com.icoderoad.itext_sign_pdf.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class IndexController {
	
	@GetMapping("/")
    public String index(Model model) {
        return "index";
    }

}

在 SignatureController 中定義一個用于處理簽章請求的接口,并通過注入 SignatureService 完成簽章功能。

package com.icoderoad.itext_sign_pdf.controller;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.icoderoad.itext_sign_pdf.config.SignatureProperties;
import com.icoderoad.itext_sign_pdf.service.SignatureService;

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

    @Autowired
    private SignatureService signatureService;

    @Autowired
    private SignatureProperties signatureProperties;

    @PostMapping("/uploadAndSign")
    public ResponseEntity<String> uploadAndSignPdf(@RequestParam("pdfFile") MultipartFile pdfFile) {
        try {
            // 將上傳的PDF文件保存為臨時文件
            File tempPdfFile = convertMultiPartToFile(pdfFile);

            // 使用配置文件中的參數(shù)進(jìn)行簽章
            byte[] signedPdfData = signatureService.sign(
                signatureProperties.getCertificatePassword(),
                signatureProperties.getCertificatePath(),
                tempPdfFile.getAbsolutePath(),
                signatureProperties.getSignImage(),
                signatureProperties.getPosition().getX(),
                signatureProperties.getPosition().getY()
            );
            
            FileOutputStream f = new FileOutputStream(new File("已簽名11.pdf"));
            f.write(signedPdfData);
            f.close();

            // 刪除臨時文件
            tempPdfFile.delete();

         // 確定 PDF 文件的保存路徑
            String fileName = "簽名文檔.pdf";
            String filePath = "src/main/resources/static/" + fileName; // 保存到 static 目錄
            FileOutputStream fos = new FileOutputStream(new File(filePath));
            fos.write(signedPdfData);
            fos.close();

            // 刪除臨時文件
            tempPdfFile.delete();

            // 生成下載鏈接
            String downloadUrl = "/static/" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString());

            return ResponseEntity.ok(downloadUrl);

        } catch (Exception e) {
            e.printStackTrace();
            return ResponseEntity.status(500).body(null);
        }
    }

    private File convertMultiPartToFile(MultipartFile file) throws IOException {
        File convFile = new File(System.getProperty("java.io.tmpdir") + "/" + file.getOriginalFilename());
        try (FileOutputStream fos = new FileOutputStream(convFile)) {
            fos.write(file.getBytes());
        }
        return convFile;
    }
}

前端頁面實現(xiàn)

使用 Thymeleaf 和 jQuery 實現(xiàn)一個簡單的文件上傳和簽章觸發(fā)頁面。CDN 加載 jQuery 和 Bootstrap 樣式。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PDF 簽章</title>
    <link rel="stylesheet" >
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
</head>
<body>
    <div class="container mt-5">
        <h2 class="mb-4">上傳 PDF 文件并添加簽章</h2>
        <form id="signForm">
            <div class="mb-3">
                <label for="pdfFile" class="form-label">選擇 PDF 文件</label>
                <input class="form-control" type="file" id="pdfFile" name="pdfFile" accept=".pdf" required>
            </div>
            <button type="submit" class="btn btn-primary">簽名并獲取下載鏈接</button>
        </form>
        <div id="downloadLink" class="mt-4" style="display: none;">
            <h4>下載鏈接:</h4>
            <a id="pdfDownload" href="#" target="_blank">下載簽名文檔</a>
        </div>
    </div>

    <script>
        $(document).ready(function () {
            $('#signForm').on('submit', function (event) {
                event.preventDefault();
                
                let formData = new FormData(this);
                $.ajax({
                    url: '/api/signature/uploadAndSign',
                    type: 'POST',
                    data: formData,
                    processData: false,
                    contentType: false,
                    success: function (response) {
                        // 顯示下載鏈接
                        $('#downloadLink').show();
                        $('#pdfDownload').attr('href', response);
                    },
                    error: function (err) {
                        alert("簽名失敗,請檢查輸入并重試。");
                    }
                });
            });
        });
    </script>
</body>
</html>

結(jié)論

本文介紹了在 Spring Boot 3.3 項目中集成 iText 實現(xiàn)電子簽章的完整流程。通過配置文件管理簽章參數(shù)、使用 @ConfigurationProperties 注入配置、Lombok 簡化代碼,以及使用 jQuery與 Thymeleaf 搭建前端界面,我們構(gòu)建了一個簡單而專業(yè)的電子簽章功能。

責(zé)任編輯:武曉燕 來源: 路條編程
相關(guān)推薦

2024-10-07 08:18:05

SpringBOM管理

2024-10-18 11:32:15

2024-10-08 09:27:04

SpringRESTfulAPI

2024-09-05 09:35:58

CGLIBSpring動態(tài)代理

2024-10-15 10:38:32

2024-09-26 09:28:06

內(nèi)存Spring

2021-12-28 11:13:05

安全認(rèn)證 Spring Boot

2024-11-11 10:02:37

Spring搜索數(shù)據(jù)

2024-10-17 11:24:04

2024-10-11 11:46:40

2025-04-03 07:56:08

電子簽名合同系統(tǒng)Spring

2023-09-01 08:46:44

2024-08-29 08:23:22

EasyOCRSpring文字識別

2018-11-02 15:45:41

Spring BootRedis數(shù)據(jù)庫

2020-07-14 11:00:12

Spring BootRedisJava

2019-01-15 11:40:14

開發(fā)技能代碼

2023-01-10 07:52:15

2020-09-02 17:28:26

Spring Boot Redis集成

2024-09-05 09:38:55

SpringActuator應(yīng)用程序

2024-03-26 08:08:08

SpringBPMN模型
點贊
收藏

51CTO技術(shù)棧公眾號