提供三方API接口、调用第三方接口API接口、模拟API接口(一)通过signature签名验证,避免参数恶意修改
为什么要设计安全的api接口
运行在外网服务器的接口暴露在整个互联网中,可能会受到各种攻击,例如恶意爬取服务器数据、恶意篡改请求数据等,因此需要一个机制去保证api接口是相对安全的。
本项目api接口安全设计
本项目api接口的安全性主要是为了请求参数不会被篡改和防止接口被多次调用而产生脏数据,实现方案主要围绕令牌(token)、时间戳(timestamp)、签名(signature)三个机制展开设计。
模拟前端签名与后端验证签名
RSA密钥对生成
KeyPairGenerator生成RSA密钥对
自定义Generator密钥生成器:
package com.atguigu.signcenter.util;import org.apache.tomcat.util.codec.binary.Base64;import java.io.UnsupportedEncodingException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;/*** 自定义Generator密钥生成器:* 描述:密钥生成器** RSA密钥对生成* KeyPairGenerator生成RSA密钥对*** @author: jd* @create: 2024-07-30*/
public class Generator {public static final String ALGORITHM_RSA ="RSA";private static final String RSA_CHARSET = "UTF-8";public static void main(String[] args) throws Exception {// 初始化密钥对生成器,生成RSA密钥对KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(ALGORITHM_RSA);// // 可以指定密钥大小,如1024位keyPairGen.initialize(1024);// 生成密钥对KeyPair keyPair = keyPairGen.generateKeyPair();//从密钥对中获取公钥RSAPublicKey rsaPublicKey = (RSAPublicKey)keyPair.getPublic();// 获取公钥的ASN.1 DER编码 byte[] keyBs = rsaPublicKey.getEncoded();String publicKey = encodeBase64(keyBs);System.out.println("生成的公钥:\r\n" + publicKey);//从密钥对中获取私钥RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();// 获取私钥的ASN.1 DER编码keyBs = rsaPrivateKey.getEncoded();String privateKey = encodeBase64(keyBs);System.out.println("生成的私钥:\r\n" + privateKey);}/*** 描述:byte数组转String* @param source 入参byte数组* @return*/public static String encodeBase64(byte[] source) throws UnsupportedEncodingException {return new String(Base64.encodeBase64(source), RSA_CHARSET);}/*** 描述:String转byte数组* @param target 入参字符串* @return* @throws Exception*/public static byte[] decodeBase64(String target) throws Exception {return Base64.decodeBase64(target.getBytes(RSA_CHARSET));}}
测试效果:
生成的公钥:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDSv50KbmfIibYRZoFnVLdtjp/sjDAAHrAwWu9lLk6zSfx9QhEqGFbeLD6tDuS0vcBoXRCrXphOvDbMGjEWPRGw0bz2Q65zfwLTyk//XBgezDTNBK3VYYeMu9/Skgdm9lZDsvXFHPBypTfJf8/Co7sKZRXtwS69pT0UK9uMWN0+3wIDAQAB
生成的私钥:
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANK/nQpuZ8iJthFmgWdUt22On+yMMAAesDBa72UuTrNJ/H1CESoYVt4sPq0O5LS9wGhdEKtemE68NswaMRY9EbDRvPZDrnN/AtPKT/9cGB7MNM0ErdVhh4y739KSB2b2VkOy9cUc8HKlN8l/z8KjuwplFe3BLr2lPRQr24xY3T7fAgMBAAECgYBSVqS/f5Uyx4MH11m1imbD+hZHcBoM4fCKY+zmRipfaAeq1JYqMSo3UWgHays15nD3FW0+1w2ArY5nPdBx1NhE3D+icwKbbtvIpgfMRGU0EYBkm2pNdGaQDI3UYyLeMOjytletH85Dj0kue+4YHSOVDPGj2sNAHtaKTL57d65Q8QJBAPd5YALQgNAQLNCjhaDZYbdeySOrtp7uP0kAs37NywFgf9L14FMi0orTr1FduexYs2cB9tPpMJp43nzDppohNCcCQQDaAlUrbjxBHf0cPhWHcMOiEFUqPCFQB/JJDQsCZVFA8ZwXAUTOAi51tK3ID0XcsJzVZiaIlI6CyVE8WRKVrLqJAkEAqhNQWJ7S6Cs1oW3AOHstHMiXk1w/dZpnA9Tnhw4HpjqbnnA8auZTq/UvV8wCKtwK74/6AkkQjhjjTvtnVCXdoQJBAJqGrsHzCAiL7h23r+Dpv/E+rG8cYextRYIcGaKgGCD1YNM5lgCDsVTDNa6pjLZqBTCJkGSdEAqKEee5px+qaCkCQHIrF3nC9CoaJMrywRYOF3sKNt9loHWHOg5wtzt0Ccdfy3MKpuJjMlWqQe8Licz/FcaQFMr9PVtk93cHBtIDANo=
使用keytool生成RSA密钥对
keytool口令:生成一个名称为jwt.jks、别名为jwt的RSA密钥证书
keytool -genkey -alias jwt -keyalg RSA -keystore jwt.jks
注意:如果提示,keytool 工具未找到,则直接到JDK的bin目录下cmd执行即可。
产生了秘钥文件:
然后把产生的秘钥文件放到类路径下面,
KeyPair配置类:
KeyPairConfig.java
package com.atguigu.signcenter.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;import java.security.KeyPair;/*** @author: jd* @create: 2024-07-30*/
@Configuration
public class KeyPairConfig {@Beanpublic KeyPair keyPair() {//从classpath下的证书中获取秘钥对KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());}
}
SecurityUtilTestService服务类
package com.atguigu.signcenter.service;import java.io.UnsupportedEncodingException;public interface SecurityUtilTestService {public void test() throws UnsupportedEncodingException;
}
SecurityUtilTestServiceImpl.java 服务实现类
package com.atguigu.signcenter.service.serviceImpl;import com.atguigu.signcenter.service.SecurityUtilTestService;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.io.UnsupportedEncodingException;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;/***https://blog.csdn.net/weixin_47560078/article/details/118222785* 使用keytool生成RSA密钥对* keytool口令:生成一个名称为jwt.jks、别名为jwt的RSA密钥证书* @author: jd* @create: 2024-07-30*/@Slf4j
@Service
public class SecurityUtilTestServiceImpl implements SecurityUtilTestService {@Autowiredpublic KeyPair keyPair;public void test() throws UnsupportedEncodingException {RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();log.info("公钥信息 => \n {} \n 公钥:{}", publicKey.toString(), encodeBase64(publicKey.getEncoded()));RSAPrivateKey priKey = (RSAPrivateKey) keyPair.getPrivate();log.info("私钥信息 => \n {} \n 私钥:{}", priKey.toString(), encodeBase64(priKey.getEncoded()));}public String encodeBase64(byte[] source) throws UnsupportedEncodingException {return new String(Base64.encodeBase64(source),"UTF-8");}}
测试效果:
2024-08-02 09:28:00.380 INFO 15240 --- [nio-8025-exec-1] c.a.s.s.s.SecurityUtilTestServiceImpl : 公钥信息 => Sun RSA public key, 2048 bitsmodulus: 23829896032654266449237055885186070100267258718676980642255177035533633961682767587031637650539693543712242029204450662903733370684503258275060048893522899536661642394654212548304797355318568454907620090143269620466272360481207010741679722568484516052908816917363309070994255480818549791827817786183403586871515551029544108830017390208760682982163893096953077182893148288822722804881185915432409621143786029919505816246686776327742032964509838775989821139201389029065260189892568467570697405413232162062535688873178341418936979958788897096099384096388668260002427751117248616155956906183130250777246881881675374119023public exponent: 65537 公钥:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvMTmPTwH1lX5wnfQndyU2s0945VNpagEFGDZp+Qsh30377r5tB7baUv4WW4AkDtGzE1Tog8vZStZGnZzxOzcfi65Lfwir7BaXm1BZX2VzPHcj6pi/rzNoQBfCVu4sb2OuOzU2znpQdDJsp4oBZYLOOc201vUyj//2CZXljIFS22OLdFcsyabAe+WVK8eLuWrNeV4wIScP2oGOBED5amvNlojhlrYP//Rq2HZxZJAQWZFKzbqMqcY9x+z74DZK4ZE8ttSfVjMNQ6ZzI76MGIWXzIh83O+eLJv/wC9LpDMdlkGnbRVWUSt14IFLIJiMGIG7PsJIaCH9Ri8qpL+clHIbwIDAQAB
2024-08-02 09:28:00.381 INFO 15240 --- [nio-8025-exec-1] c.a.s.s.s.SecurityUtilTestServiceImpl : 私钥信息 => sun.security.rsa.RSAPrivateCrtKeyImpl@ffdad0f9 私钥:MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8xOY9PAfWVfnCd9Cd3JTazT3jlU2lqAQUYNmn5CyHfTfvuvm0HttpS/hZbgCQO0bMTVOiDy9lK1kadnPE7Nx+Lrkt/CKvsFpebUFlfZXM8dyPqmL+vM2hAF8JW7ixvY647NTbOelB0MmynigFlgs45zbTW9TKP//YJleWMgVLbY4t0VyzJpsB75ZUrx4u5as15XjAhJw/agY4EQPlqa82WiOGWtg//9GrYdnFkkBBZkUrNuoypxj3H7PvgNkrhkTy21J9WMw1DpnMjvowYhZfMiHzc754sm//AL0ukMx2WQadtFVZRK3XggUsgmIwYgbs+wkhoIf1GLyqkv5yUchvAgMBAAECggEAIWFcKXXlGOTJLrdLP68S74IdsJHlUibc2dGqi4LQ0QC2lIDmyRAv0nXpq77FALxKr7P41w6CXgyFTCWoISmVcAbJHjWY2KKByHLffpfvlncqfoktykgOwKq1I42BooSXqWHP3hhmhAnssNZA04QN5fkU+9kgTsd0cX+OO3QK9aRTIOrogcdor1a+uytnpNQKBRA6W+e7MJo67HFWn1DkacKdYaty+jI4B1+1eSuHQoiGSR92Is0ITnu1ToVZq8ljknI87sUeSr2745ZNa9dvCixnqu3MDo8MpriIbvIs/ou/EIkOkWIqxk3ZcJlnSLAp3KExzZrEn6oNgnt0f5o1SQKBgQDeCvijwzUfgPFT6+BmqcG7aizd8c9muwIJq5SZ092WbkeYLJ1MTE/M/FdY+gf60+RGerIoqs/MS5Wq22Wkm2zoNExlx1J74j3T/hLJi83k2sPcLZ8ERylF2gxHCC0mSq8XYBP95JnhP3GhJAb0AfZAX33aNlTmhI+x0dGK/E3LEwKBgQDZo0EhzhTQ6LfKgdq856V0BhPCrUMAiclvgww9hZTKJizDDhYRmozplqMxJDsoYM+UIrT4+HOgn1hhyVyQrL+UuU4avwlFvttRV2qP5DN23cCmO2fCEl1VZsdfYwM6Wxi5W3HtrfPCOTI3UDCBBaIVIY5ysgaYN/K3d7GApbx8tQKBgQC6Fr3NGZPTBFC8wam+wLSxqklR9Q+aDE6n5hnTVgGWynPMME/gGvCiXjWiR2IasCzXeIsJlCY1FH+pwtgLvYrnjLUec49IbhLdMUlzimyTSVjulXl4r18CwLybZ1nXhq35TDdVCJqCcZZ8s2H0bBSCXMVv9SrwStbW837HGa7k6wKBgDEfL+w5X2lnnVQPyxcoEagGVx9xi9XrQ5PvuCuKovKdeyzrWP/QKk3uuha1XsTWVQPScZZgPXjbfk9T65ib5QHkcUSIKcecNFQ/rsWbdCI4GutqLRkYnAhSkl1tM6VQOlxbz3Md+A62aich6lC0vMiYMlag5+wjdJ0EXwPyZIChAoGBALY2A7hlCaI8tbg2QVnbMBBmLCUWneEV3wVVehDSMA3Dwq6jD/MrCHHSdFIk0pOsH+YkKyifrhjS58M/irrwv1fsbs/NHdjngBHYGswP10cYyCNSI9LvsN47/pVxDPr/9nUfSB2wNWUTaRLflPpokT8tmMc4AIkAmri6+VWXnlgO
进行实际的http请求的签名模块模拟签名与验证
模块pom依赖
<?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.6.3</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.atguigu</groupId><artifactId>sign-center</artifactId><version>0.0.1-SNAPSHOT</version><name>sign-center</name><description>api接口的安全设计</description><url/><licenses><license/></licenses><developers><developer/></developers><scm><connection/><developerConnection/><tag/><url/></scm><properties><java.version>1.8</java.version><!-- <spring-cloud.version>2021.0.4</spring-cloud.version>--><spring-cloud.version>2021.0.1</spring-cloud.version></properties><dependencies><!-- redisson依赖--><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.15.5</version></dependency><!--redis链接客户端--><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.2.0</version></dependency><!--fastjson--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.62</version></dependency><!--集成redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.1.7.RELEASE</version></dependency><!--joda time ? 这个还有些问题,这个类库是做什么的--><dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId><version>2.10</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId></dependency><dependency><groupId>com.atguigu.gulimall</groupId><artifactId>gulimall-common</artifactId><version>0.0.1-SNAPSHOT</version><exclusions><exclusion><artifactId>servlet-api</artifactId><groupId>javax.servlet</groupId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.amqp</groupId><artifactId>spring-rabbit-test</artifactId><scope>test</scope></dependency><!--什么作用? --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections4</artifactId><version>4.2</version></dependency><dependency><groupId>org.springframework.amqp</groupId><artifactId>spring-rabbit</artifactId><version>2.4.2</version><scope>compile</scope></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>
主类:
package com.atguigu.signcenter;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;@EnableDiscoveryClient
@SpringBootApplication
public class SignCenterApplication {public static void main(String[] args) {SpringApplication.run(SignCenterApplication.class, args);}}
封装的安全工具类:SecurityUtil
package com.atguigu.signcenter.util;import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;/*** 描述:安全工具类* ————————————————* <p>* 原文链接:https://blog.csdn.net/weixin_47560078/article/details/118222785** @author: jd* @create: 2024-07-31*/
public class SecurityUtil {//加密算法private static final String ALGORITHM_RSA = "RSA";//字符编码指定的字符集private static final String RSA_CHARSET = "UTF-8";/*** 描述:将字符串通过RSA算法公钥加密** @param content 需要加密的内容* @param pubKey 公钥* @return 加密后字符串* @throws Exception*/private static String EncryptByRSAPubKey(String content, String pubKey) throws Exception {try {PublicKey publicKey = SecurityUtil.getRSAPubKey(pubKey);Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());cipher.init(Cipher.ENCRYPT_MODE, publicKey);cipher.update(content.getBytes(RSA_CHARSET));return SecurityUtil.encodeBase64(cipher.doFinal());} catch (Exception e) {e.printStackTrace();throw new Exception();}}/*** 描述:将字符串通过RSA算法公钥解密** @param content 需要解密的内容* @param pubKey 公钥* @return 解密后字符串* @throws Exception*/public static String DecryptByRSAPubKey(String content, String pubKey) throws Exception {try {PublicKey publicKey = SecurityUtil.getRSAPubKey(pubKey);Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());cipher.init(Cipher.DECRYPT_MODE, publicKey);cipher.update(SecurityUtil.decodeBase64(content));return new String(cipher.doFinal(), RSA_CHARSET);} catch (Exception e) {e.printStackTrace();throw new Exception();}}/*** 描述:将字符串通过RSA算法私钥加密** @param content 需要加密的内容* @param priKey 私钥* @return 加密后字符串* @throws Exception*/public static String EncryptByRSAPriKey(String content, String priKey) throws Exception {try {PrivateKey privateKey = SecurityUtil.getRSAPriKey(priKey);Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());cipher.init(Cipher.ENCRYPT_MODE, privateKey);cipher.update(content.getBytes(RSA_CHARSET));return SecurityUtil.encodeBase64(cipher.doFinal());} catch (Exception e) {e.printStackTrace();throw new Exception();}}/*** 描述:将字符串通过RSA算法私钥解密** @param content 需要解密的内容* @param priKey 私钥* @return 解密后字符串* @throws Exception*/public static String DecryptByRSAPriKey(String content, String priKey) throws Exception {try {PrivateKey privateKey = SecurityUtil.getRSAPriKey(priKey);Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());cipher.init(Cipher.DECRYPT_MODE, privateKey);cipher.update(SecurityUtil.decodeBase64(content));return new String(cipher.doFinal(), RSA_CHARSET);} catch (Exception e) {e.printStackTrace();throw new Exception();}}/*** 从秘钥文件中获取密钥对** @return*/private static KeyPair getKeyPair(){//从classpath下的证书中获取秘钥对KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());}/*** 从jwt.jks文件中获取公钥字符串** @return* @throws Exception*/public static String getPublicKey() throws Exception {// 获取密钥对KeyPair keyPair = getKeyPair();// 获取私钥信息PublicKey publicKey = keyPair.getPublic();//byte 转 Stringreturn encodeBase64(publicKey.getEncoded());}/*** 从jwt.jks文件中获取私钥字符串** @return* @throws Exception*/public static String getPrivateKey() throws Exception {// 获取密钥对KeyPair keyPair = SecurityUtil.getKeyPair();// 获取私钥信息PrivateKey privateKey = keyPair.getPrivate();// byte 转 Stringreturn SecurityUtil.encodeBase64(privateKey.getEncoded());}/*** 描述:获取RSA私钥** @param priKey 私钥* @return PrivateKey* @throws Exception*/private static PrivateKey getRSAPriKey(String priKey) throws Exception {try {PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(SecurityUtil.decodeBase64(priKey));KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);return keyFactory.generatePrivate(privateKeySpec);} catch (Exception e) {e.printStackTrace();throw new Exception();}}/*** 描述:获取RSA公钥** @param pubKey 公钥* @return*/private static PublicKey getRSAPubKey(String pubKey) throws Exception {try {X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(SecurityUtil.decodeBase64(pubKey));KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);return keyFactory.generatePublic(publicKeySpec);} catch (Exception e) {e.printStackTrace();}return null;}/*** base64编码** @param source* @return* @throws Exception*/public static String encodeBase64(byte[] source) throws Exception {return new String(Base64.encodeBase64(source), RSA_CHARSET);}/*** Base64解码** @param target 需要解码的目标字符串* @return*/private static byte[] decodeBase64(String target) throws Exception {return Base64.decodeBase64(target.getBytes(RSA_CHARSET));}public static void main(String[] args) throws Exception {//获取公钥String pubKey = getPublicKey();//获取私钥String priKey = getPrivateKey();//需要加密的参数String content = "age=18&name=yushanma";//字符串加密String s = EncryptByRSAPubKey(content, pubKey);System.out.println("s参数通过公钥加密后:" + s);System.out.println("加密后的字符串通过私钥解密后:" + DecryptByRSAPriKey(s, priKey));content = "age=18&name=yushanma";s = EncryptByRSAPriKey(content, priKey);System.out.println("s参数通过私钥加密后:" + s);System.out.println("加密后的字符串通过公钥解密后:" + DecryptByRSAPubKey(s, pubKey));//有个疑问,是私钥和公钥都可以用来加密 吗,私钥和公钥是什么关系??//TODO}}
main方法
public static void main(String[] args) throws Exception {//获取公钥String pubKey = getPublicKey();//获取私钥String priKey = getPrivateKey();//需要加密的参数String content = "age=18&name=yushanma";//字符串加密String s = EncryptByRSAPubKey(content, pubKey);System.out.println("s参数通过公钥加密后:" + s);System.out.println("加密后的字符串通过私钥解密后:" + DecryptByRSAPriKey(s, priKey));content = "age=18&name=yushanma";s = EncryptByRSAPriKey(content, priKey);System.out.println("s参数通过私钥加密后:" + s);System.out.println("加密后的字符串通过公钥解密后:" + DecryptByRSAPubKey(s, pubKey));//有个疑问,是私钥和公钥都可以用来加密 吗,私钥和公钥是什么关系??//TODO}
测试结果
s参数通过公钥加密后:RT9yWv4CL3LtG2RYKxV35KWkFNHA/tCFQEp+En0AjFJYelscLYupJL+1Fssblp0OC/HGUqd0bx5Ji3OdZln5wI8HDMChfAxj5k4pb9NfAlXPvCKYZynFL4n/gnxLijJCWTTC0j+XazYrn8qxMmPUvh8uqwIhTGnm5F1nNlLFoxOzaGyWz3wkjy36RVFr609g1mHWRu1PoqLSWb6+qyiWhL0qr4tCNYsgx1Oe+JVptSJ67OW747o+Vk+oAzs22LKlSv+1gMtGvSqYLh2rxdk+vdN36PeSmQgsuW3dRKdpQEsyfR7jmmZnQ99Zjog5Hc6CKQjlYFQjgZjnGpOM2kTGDw==
加密后的字符串通过私钥解密后:age=18&name=yushanma
s参数通过私钥加密后:YOW/GiySigBsY4RyM/Q25Qyg3ypohvelONxHuSd+8NbRoRu9mBxVOgCq4MXEKOqf92mBcuAEdIxq4J8ARwZz0ba7846bQnpuRiLxpndZXs/ViZVH/gC93s5GpkmOhsx1nM4kNMap39FNASSn04Gxs8MJkYZLibZ4HKCaq1IZrolIawzW1Stm2nKn+B3CHpyYvMxKCDn7WdOtJaAdDf9MLOjvbZDM3GmIkQAoNctWrExjLoIsF689lAQuDRoIPLNNsYMtD4Zrx4KqPFAWnLsAMI17Mc6igQxNejiGNOw4S2WjZew44Mo+EKxutQCR5mFUVAXs94fsFH7AVwtJhuHW3A==
加密后的字符串通过公钥解密后:age=18&name=yushanma
封装签名工具类: ApiUtil
package com.atguigu.signcenter.util;import com.alibaba.fastjson.JSONObject;
import com.mysql.cj.util.StringUtils;
import com.rabbitmq.tools.json.JSONUtil;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.DigestException;
import java.util.*;/*** 封装签名工具类* https://blog.csdn.net/weixin_47560078/article/details/118222785* @author: jd* @create: 2024-07-31*/
@Slf4j
public class ApiUtil {/*** 根据传的参数Map,对参数进行排序并拼接成A=1&b=2....* @param data* @return*/private static String getSortedContent(Map<String, String> data){StringBuffer content = new StringBuffer();List<String> keys = new ArrayList<>(data.keySet());Collections.sort(keys);int index = 0;for (String key : keys) {String value = data.get(key);content.append((index == 0 ? "" : "&")).append(key).append("=").append(value);index++;}return content.toString();}/*** 参数加密,吸纳使用MD5加密,然后使用自定义的私钥加密方法进行加密,最终进行URL编码,并返回。* @param data* @return* @throws Exception*/public static String getSignature(Map<String, String> data) throws Exception {// 第一次加密:使用MD5加密 对排序好的字符串进行加密,然后转小写String summary = DigestUtils.md5Hex(getSortedContent(data));log.info("======>md5加密后的参数 summary:" + summary);//第二次加密,使用自定义的加密方法加密[自定义的加密方法,通过私钥加密]String encryptByRSAPriSummary = SecurityUtil.EncryptByRSAPriKey(summary, SecurityUtil.getPrivateKey());//对加密后的字符串进行Url编码,并返回return URLEncoder.encode(encryptByRSAPriSummary,"utf-8");}/*** 验证签名* @param params 所有参数* @param sign 数字签名* @param signType 加密类型* @return* @throws Exception*/public static boolean verifySign(Map<String, String> params, String sign, String signType) throws Exception {//如果签名直接为空,则返回验证失败if(StringUtil.isEmpty(URLDecoder.decode(sign, "utf-8"))){return false;}//暂不支持非RSA的签名if(StringUtil.isEmpty(signType)&&!"RSA".equals(signType)){log.info("暂不支持其他加密方法的签名验证");return false;}//走过参数校验之后,开始正式的签名校验//参与签名的数据String data = getSortedContent(params);log.info("=====>原始data:" + data);String summary = DigestUtils.md5Hex(data).toLowerCase();log.info("=====>data进行md5Hex加密后 summary:" + summary);String summaryDecode = null;try {//对签名通过公钥解密,因为加密的时候先加密后编码,所以这里需要先解码,后解密,又因为使用自定义加密方法之前,是先经过了MD5加密,所以对比的时候,也要对原始串进行下MD5,//(紧接着上一行)然后再和解密的字符串去对比。summaryDecode = SecurityUtil.DecryptByRSAPubKey(URLDecoder.decode(sign,"utf-8"),SecurityUtil.getPublicKey());} catch (Exception e) {throw new RuntimeException("do_digest_error", e);}return summary.equals(summaryDecode);}public static void main(String[] args) throws Exception {Map<String,String> data = new HashMap<>();data.put("name","zhaijh");data.put("age","20");log.info("原始参数:{}",data);String signature = getSignature(data);log.info("参数签名:{}",signature);String signType = "RSA";log.info("验证结果:{}",verifySign(data,signature,signType));}}
测试结果
09:01:07.417 [main] INFO com.atguigu.signcenter.util.ApiUtil - 原始参数:{name=zhaijh, age=20}
09:01:07.432 [main] INFO com.atguigu.signcenter.util.ApiUtil - ======>md5加密后的参数 summary:bea0310dc7f03ad5d036d906dbe513f4
09:01:08.642 [main] INFO com.atguigu.signcenter.util.ApiUtil - 参数签名:j0CTBZ0NpiZFycua7otGYXr7KHNgXsBc0DBojHzyKbTKtSrutZv%2FblSFqFi5lCM6stSkxWd8OQQRcgPwPopsy1HYZ6cU%2Fi%2Bpwc0JAH2DH49jmTGgJVatCGFZ0NtcNK972XIncpSOaeWNx6fvBffi71jbxg7BdWM89anLL%2BQJ1GeF5i3OL4wBzFoyC7FdiGdxG9qShSdORBcy2LMyCa3sHJDTayoPmOhNLAqgvrAWeTfF%2BkUZAPqysHVGX8KpmfcNY0vf1y%2BSmWXYJ5x%2Faw1GVCIy39vcXppqy1ofqZQTo0d6drk62J1cjmDzl0Pj7c4J0h13mCw4yl4dn3KRkJ3jhg%3D%3D
09:01:08.644 [main] INFO com.atguigu.signcenter.util.ApiUtil - =====>原始data:age=20&name=zhaijh
09:01:08.644 [main] INFO com.atguigu.signcenter.util.ApiUtil - =====>data进行md5Hex加密后 summary:bea0310dc7f03ad5d036d906dbe513f4
09:01:08.645 [main] INFO com.atguigu.signcenter.util.ApiUtil - 验证结果:true
控制器签名接口与校验接口: SignController
package com.atguigu.signcenter.controller;import com.atguigu.signcenter.util.ApiUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;/*** 控制器签名接口与校验接口:* @author: jd* @create: 2024-08-01*/
@Slf4j
@RestController
public class SignController {/*** 模拟前端请求时发送的 参数签名* @param data 实际的业务入参数据* @return*/@GetMapping("/sign/getSign")public Map<String,Object> getSign(@RequestParam Map<String, String> data) throws Exception {log.info("入参:{}",data);//返回信息,map结构HashMap<String, Object> result = new HashMap<>();result.put("code",0); //状态码result.put("msg","success"); //返回信息String signature = ApiUtil.getSignature(data);//获得入参参 通过私钥的签名内容result.put("data",signature);result.put("verify",ApiUtil.verifySign(data,signature,"RSA"));return result;}/*** 模拟后端进行请求中“参数签名”的验证* @param request Http请求* @param data 实际的业务入参数据* @return* @throws Exception*/@GetMapping("/sign/verifySign")public boolean verifySign(HttpServletRequest request,@RequestParam Map<String, String> data) throws Exception {String sign = request.getHeader("sign");String sign_type = request.getHeader("sign_type");boolean result = ApiUtil.verifySign(data, sign, sign_type);log.info("参数签名验证结果:{}",result);return result;}}
yml相关配置:
server:port: 8025spring:application:name: sign-centerdatasource:url: jdbc:mysql://192.168.56.10:3306/gulimall_umsusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Drivercloud:nacos:discovery:server-addr: 127.0.0.1:8848#配置日志输出级别
logging:level:com.atguigu.gulimall: debug #level 日志等级 指定命名空间的日志输出
模拟前端产生签名测试: 参数name zhaijh ; age 21
模拟后端签名校验测试:
请求头
请求参数
这里我将sign放到header中,是因为签名signature中有些字符是特殊字符,放到parameter中可能会导致有些字符被过滤,最终在校验签名时抛出签名长度不够的错误,这个问题可以通过url编码解决:
当传的参数是一样的话,验证通过 返回true 【参数name zhaijh ; age 21】
当参数被修改时,签名验证就会返回false: 【参数name zhaijh ; age 22】
参考链接:https://blog.csdn.net/weixin_47560078/article/details/118222785
相关文章:
提供三方API接口、调用第三方接口API接口、模拟API接口(一)通过signature签名验证,避免参数恶意修改
为什么要设计安全的api接口 运行在外网服务器的接口暴露在整个互联网中,可能会受到各种攻击,例如恶意爬取服务器数据、恶意篡改请求数据等,因此需要一个机制去保证api接口是相对安全的。 本项目api接口安全设计 本项目api接口的安全性主要…...
CDO学习
1.备份instie.mdb文件 2....
奥运会Ⅱ---谁会先抢走你的工作?
Devin AI 与 Microsoft AutoDev,谁会先抢走你的工作? 软件开发领域正处于一场革命的风口浪尖。Devin AI和Microsoft AutoDev 的出现,是人工智能编码领域的两项突破性进步,有望重塑软件构建方式。但是,在如此截然不同的…...
用Python打造精彩动画与视频,4.3 创建动态文本和字幕
第四章:深入MoviePy 4.3 创建动态文本和字幕 在视频编辑中,动态文本和字幕是传达信息、增强观众体验的重要元素。MoviePy 提供了丰富的工具来添加和自定义文本和字幕,包括字体、颜色、动画效果等。本节将介绍如何在视频中添加动态文本和字幕…...
spring boot + vue3 接入钉钉实现扫码登录
1:准备工作 1.1:进入钉钉开放平台创建开发者应用。应用创建和类型介绍,参考下方。 应用类型介绍 - 钉钉开放平台 (dingtalk.com) 应用能力介绍 - 钉钉开放平台 (dingtalk.com) 扫码登录第三方网站 - 钉钉开放平台 (dingtalk.com) 1.2&…...
二叉树构建(从3种遍历中构建)python刷题记录
R3-树与二叉树篇. 目录 从前序与中序遍历序列构造二叉树 算法思路: 灵神套路 从中序与后序遍历序列构造二叉树 算法思路: 灵神套路 从前序和后序遍历序列构造二叉树 算法思路: 灵神套路 从前序与中序遍历序列构造二叉树 算法…...
计算机网络中协议与报文的关系
协议和报文在网络通信中扮演着不同的角色,但它们是紧密相关的。 协议是计算机网络中实现通信的“约定”,它规定了计算机之间如何进行通信,包括数据传输的格式、步骤和规则。协议确保了不同厂商的设备、不同的CPU和操作系统之间的计算机能够相…...
机器学习 第8章-集成学习
机器学习 第8章-集成学习 8.1 个体与集成 集成学习(ensemble learning)通过构建并结合多个学习器来完成学习任务,有时也被称为多分类器系统(multi-classifersystem)、基于委员会的学习(committee-based learning)等。 图8.1显示出集成学习的一般结构:先产生一组“…...
Docker 安装 GitLab教程
本章教程,主要介绍如何在Docker 中安装GitLab。 GitLab 是一个开源的 DevOps 平台,提供了一整套工具,用于软件开发生命周期的各个阶段,从代码管理到 CI/CD(持续集成和持续交付/部署),再到监控和安全分析。 一、拉取镜像 docker pull gitlab/gitlab-ce:latest二、创建 G…...
如何在生产环境中千万表添加索引并保证数据一致性
技术分享文档:如何在生产环境中千万表添加索引并保证数据一致性 目录 引言添加索引的挑战解决方案概述详细步骤 4.1 创建新表并添加索引 4.2 批量导入数据 4.3 处理增量数据 4.4 表名切换确保数据一致性 5.1 暂停写操作 5.2 记录增量数据 5.3 应用增量数据设置回滚…...
Uni-APP页面跳转问题(十六)
【背景】最近在做公司一个PAD端,谁被点检功能,主要时为了移动端点检设备和打印标签,需求比较简单就是扫描设备二维码,问题在于扫描后要能够重复进行多设备的扫描;早期开发的设备点检能够满足需求但是当连续扫描五六十个设备后,APP卡死,必须重启才能使用。 界面原图: 输…...
Java新特性(二) Stream与Optional详解
Java8新特性(二) Stream与Optional详解 一. Stream流 1. Stream概述 1.1 基本概念 Stream(java.util.stream) 是Java 8中新增的一种抽象流式接口,主要用于配合Lambda表达式提高批量数据的计算和处理效率。Stream不是…...
springboot系列教程(三十一):springboot整合Nacos组件,环境搭建和入门案例详解
一、Nacos基础简介 1、概念简介 Nacos 是构建以“服务”为中心的现代应用架构,如微服务范式、云原生范式等服务基础设施。聚焦于发现、配置和管理微服务。Nacos提供一组简单易用的特性集,帮助开发者快速实现动态服务发现、服务配置、服务元数据及流量管…...
Traefik系列
一、入门Traefik系列——基础简介 官方文档 https://doc.traefik.io/traefik/[1] 简介 Traefik是一个为了让部署微服务更加便捷而诞生的现代HTTP反向代理、负载均衡工具。它支持多种后台 (Docker, Swarm, Kubernetes, Marathon, Mesos, Consul, Etcd, Zookeeper, BoltDB, Re…...
【力扣】3128. 直角三角形 JAVA
一、题目描述 给你一个二维 boolean 矩阵 grid 。 请你返回使用 grid 中的 3 个元素可以构建的 直角三角形 数目,且满足 3 个元素值 都 为 1 。 注意: 如果 grid 中 3 个元素满足:一个元素与另一个元素在 同一行,同时与第三个元素…...
如何全面提升企业安全意识
引言 在当今数字化和信息化的时代,网络安全已成为企业运营不可忽视的核心问题。员工的安全意识直接关系到企业的数据安全和整体网络防护能力。即使企业采用了先进的安全技术,如果员工缺乏足够的安全意识,仍然容易成为攻击者的突破口。本文将…...
全球支持与无界服务:跨越地域的数据采集与分析
在当今企业运营中,IT 监控系统的全球支持和无界服务变得至关重要。随着企业业务的全球化扩展,传统的监控工具往往因地域限制而无法满足全球统一监控的需求。观测云通过其全球部署的数据采集点和多语言支持,确保了无论数据产生于何处ÿ…...
Java面试八股之简述spring boot的目录结构
简述spring boot的目录结构 Spring Boot 项目遵循标准的 Maven 或 Gradle 项目布局,并且有一些约定的目录用于组织不同的项目组件。下面是一个典型的 Spring Boot 项目目录结构: src/main/java:包含所有的 Java 源代码,通常按包组…...
python == 与 is区别
刷到一个面试题 python中 与 is 的区别 根据以往的经验,这个问题应该考察的是运算符根据地址 还是值进行比较的 s1 [a] s2 [a] s3 s1 print(s1 s2) # True 值相等 print(s1 s3) # True 值相等 print(s1 is s2) # False 值相等,引用地址不相…...
STM32学习笔记1---LED,蜂鸣器
目录 GPIO LED 蜂鸣器 RCC外设 GPIO外设 总概 操作STM32的GPIO 代码 LED闪烁 LED流水灯 蜂鸣器! 连接方式 GPIO GPIO输出:向外驱动控制 GPIO输入:读取,捕获(信息)(控制)…...
动手学强化学习 第 15 章 模仿学习 训练代码
基于 https://github.com/boyu-ai/Hands-on-RL/blob/main/%E7%AC%AC15%E7%AB%A0-%E6%A8%A1%E4%BB%BF%E5%AD%A6%E4%B9%A0.ipynb 理论 模仿学习 修改了警告和报错 运行环境 Debian GNU/Linux 12 Python 3.9.19 torch 2.0.1 gym 0.26.2 运行代码 #!/usr/bin/env pythonimpor…...
第一阶段面试问题(前半部分)
1. 进程和线程的概念、区别以及什么时候用线程、什么时候用进程? (1)线程 线程是CPU任务调度的最小单元、是一个轻量级的进程 (2)进程 进程是操作系统资源分配的最小单元 进程是一个程序动态执行的过程,包…...
《数学教学通讯》是一本怎样的刊物?投稿难吗?
《数学教学通讯》是一本怎样的刊物?投稿难吗? 《数学教学通讯》是一本具有较高学术价值的教育类刊物。它创刊于 1979 年,由西南大学主管,西南大学数学与统计学院、重庆市数学学会主办,出版周期为旬刊。该刊物在国内外…...
<机器学习> K-means
K-means定义 K-means 是一种广泛使用的聚类算法,旨在将数据集中的点分组为 K 个簇(cluster),使得每个簇内的点尽可能相似,而不同簇的点尽可能不同。K-means 算法通过迭代的方式,逐步优化簇的分配和簇的中心…...
我们如何优化 Elasticsearch Serverless 中的刷新成本
作者:来自 Elastic Francisco Fernndez Castao, Henning Andersen 最近,我们推出了 Elastic Cloud Serverless 产品,旨在提供在云中运行搜索工作负载的无缝体验。为了推出该产品,我们重新设计了 Elasticsearch,将存储与…...
MySQL半同步复制
1.MySQL主从复制模式 1.1异步复制 异步复制为 MySQL 默认的复制模式,指主库写 binlog、从库 I/O 线程读 binlog 并写入 relaylog、从库 SQL 线程重放事务这三步之间是异步的。 异步复制的主库不需要关心备库的状态,主库不保证事务被传输到从库…...
[一本通提高数位动态规划]数字游戏:取模数题解
[一本通提高数位动态规划]数字游戏:取模数题解 1前言2问题3状态的设置4数位dp-part1预处理5数位dp-part2利用状态求解6代码7后记 1前言 本文为数字游戏:取模数的题解 需要读者对数位dp有基础的了解,建议先阅读 论数位dp–胎教级教学 B3883 […...
[Day 39] 區塊鏈與人工智能的聯動應用:理論、技術與實踐
區塊鏈的安全性分析 區塊鏈技術已經成為現代數字經濟的一個重要組成部分,提供了去中心化、透明和不可篡改的數據存儲與交易系統。然而,隨著區塊鏈技術的廣泛應用,其安全性問題也日益受到關注。本篇文章將詳細探討區塊鏈技術的安全性…...
OpenStack入门体验
一、云计算概述 1.1什么是云计算 云计算(cloud computing)是一种基于网络的超级计算模式,基于用户的不同需求,提供所需的资源,包括计算资源、存储资源、网络资源等。云计算服务运行在若干台高性能物理服务器之上,提供每秒 10万亿次的运算能力…...
预测未来 | MATLAB实现RF随机森林多变量时间序列预测未来-预测新数据
预测未来 | MATLAB实现RF随机森林多变量时间序列预测未来-预测新数据 预测效果 基本介绍 随机森林属于 集成学习 中的 Bagging(Bootstrap AGgregation 的简称) 方法。如果用图来表示他们之间的关系如下: 随机森林是由很多决策树构成的,不同决策树之间没有关联。当我们进行…...
网站生成海报功能怎么做/网站如何快速收录
1。确定c:\windows\system32\下有xcopy.exe文件 2。我的电脑---右键---属性----高级----环境变量----在系统变量中找到path(不分大小写)---双击它----在其变量值 (V)中添加“c:\windows\system32”!,添加方法为:在原变量值后面加英…...
最近在线观看免费完整版高清电影/seo公司排名
1. kafka介绍1.1. 主要功能根据官网的介绍,ApacheKafka是一个分布式流媒体平台,它主要有3种功能:1:It lets you publish and subscribe to streams of records.发布和订阅消息流,这个功能类似于消息队列&a…...
wordpress原创免费主题/2345网址大全设主页
真伪小叶紫檀佛珠的辨别是个难题,选购小叶紫檀佛珠的朋友都是遇到过这些假小叶紫檀佛珠,能够说成类别诸多,五花八门。有很多跟小叶紫檀佛珠很像的木料都能够制成佛珠来欺骗消费者,大家应当怎样辨别小叶紫檀佛珠真伪呢?在网上也是…...
本机做网站/深圳网络优化seo
接口中的数据域只能是public static final,方法只能是public abstract 由于这个原因,这些修饰也可以忽略。 数据域只能是static final的原因: stackoverflow上: An interface can’t have behavior or state because it is int…...
wordpress 隐藏顶部/武汉seo报价
整个项目包含了:开题报告 开题报告PPT 任务书 中期报告 论文模板 答辩PPT等 项目源码 主要安介绍了系统在开发过程中所应用到的一些关键的技术 主要python技术介绍;框架Django概要;MySQL数据库知识; 以及常规的网页技术HTM…...
武汉网站制作好/数据网站
之前我们做的都是单表查询,那么问题来了,如果我们想要做多表关联查询该怎么处理呢,本篇Blog来探索关联查询(多对一关系)和集合查询(一对多关系)的实现方式。同样还是在之前的Person表基础上做一…...