您的位置:首页 > 娱乐 > 明星 > 海南百度推广总代理_公司组织机构框架图_企业培训课程表_网站安全检测工具

海南百度推广总代理_公司组织机构框架图_企业培训课程表_网站安全检测工具

2024/12/26 9:32:27 来源:https://blog.csdn.net/fyl1991fyl/article/details/144718124  浏览:    关键词:海南百度推广总代理_公司组织机构框架图_企业培训课程表_网站安全检测工具
海南百度推广总代理_公司组织机构框架图_企业培训课程表_网站安全检测工具

一、确定签名策略

  • HMAC(Hash-based Message Authentication Code):使用对称密钥。
  • RSA:使用非对称密钥对(公钥/私钥)。
  • OAuth:用于第三方授权和签名。

二、创建签名工具类

1、HMAC 签名工具类

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;public class HmacSignatureUtil {private static final String HMAC_ALGORITHM = "HmacSHA256";public static String sign(String data, String secretKey) throws Exception {Mac sha256_HMAC = Mac.getInstance(HMAC_ALGORITHM);SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), HMAC_ALGORITHM);sha256_HMAC.init(secretKeySpec);byte[] hash = sha256_HMAC.doFinal(data.getBytes(StandardCharsets.UTF_8));return Base64.getEncoder().encodeToString(hash);}public static boolean verify(String data, String signature, String secretKey) throws Exception {String calculatedSignature = sign(data, secretKey);return calculatedSignature.equals(signature);}
}

2、RSA 签名工具类

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;public class RsaSignatureUtil {static {Security.addProvider(new BouncyCastleProvider());}private static final String ALGORITHM = "RSA";private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";private static final long TIMESTAMP_TOLERANCE = 300 * 1000; // 5分钟的容差public static String sign(String data, PrivateKey privateKey) throws Exception {Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);signature.initSign(privateKey);signature.update(data.getBytes(StandardCharsets.UTF_8));byte[] signBytes = signature.sign();return Base64.getEncoder().encodeToString(signBytes);}public static boolean verify(String data, String signature, PublicKey publicKey) throws Exception {Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);sig.initVerify(publicKey);sig.update(data.getBytes(StandardCharsets.UTF_8));byte[] decodedSignature = Base64.getDecoder().decode(signature);return sig.verify(decodedSignature);}// 方法用于加载私钥和公钥public static PrivateKey loadPrivateKey(String privateKeyPem) throws Exception {String privateKeyStr = privateKeyPem.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replaceAll("\\s+", "");byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyStr);PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);return keyFactory.generatePrivate(keySpec);}public static PublicKey loadPublicKey(String publicKeyPem) throws Exception {String publicKeyStr = publicKeyPem.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "").replaceAll("\\s+", "");byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyStr);X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);return keyFactory.generatePublic(keySpec);}/*** 从文件加载私钥*/public static PrivateKey loadPrivateKeyFromFile(String filePath) throws Exception {System.getProperty("user.dir");byte[] keyBytes = Files.readAllBytes(new File(filePath).toPath());PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);KeyFactory keyFactory = KeyFactory.getInstance("RSA");return keyFactory.generatePrivate(spec);}/*** 从文件加载公钥*/public static PublicKey loadPublicKeyFromFile(String filePath) throws IOException, Exception {byte[] keyBytes = Files.readAllBytes(new File(filePath).toPath());X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);KeyFactory keyFactory = KeyFactory.getInstance("RSA");return keyFactory.generatePublic(spec);}/*** 构建待签名的数据,包括原始数据、时间戳和随机数、除了登录接口其他接口还需要包含sessionId*/public static String buildDataToSign(String requestBody, long timestamp, String nonce, String sessionId) throws JsonProcessingException {StringBuilder content = new StringBuilder();if(StrUtil.isNotBlank(requestBody)){content.append("body=").append(requestBody.replaceAll("\\s+", "").trim()).append("&");}content.append("timestamp=").append(timestamp).append("&");content.append("nonce=").append(nonce).append("&");if(StrUtil.isNotBlank(sessionId)){content.append("sessionId=").append(sessionId).append("&");}return content.toString().substring(0, content.length() - 1);}public static String buildDataToSignForSort(String requestBody, long timestamp, String nonce, String sessionId) throws JsonProcessingException {// 将请求体转换为MapMap<String, Object> params = new LinkedHashMap();if(StrUtil.isNotBlank(requestBody)){params = objectMapper.readValue(requestBody.replaceAll("\\s+", "").trim(), LinkedHashMap.class);}params.put("timestamp", timestamp);params.put("nonce", nonce);if(StrUtil.isNotBlank(sessionId)){params.put("sessionId", sessionId);}SortedMap<String, Object> sortedParams = new TreeMap<>(params);StringBuilder content = new StringBuilder();for (String key : sortedParams.keySet()) {if ("sign".equals(key)) continue; // 排除签名本身if (sortedParams.get(key) != null) { // 忽略值为null的字段content.append(key).append("=").append(sortedParams.get(key)).append("&");}}String str= content.toString().substring(0, content.length() - 1); // 移除最后一个 &return str;}/*** 验证时间戳是否在合理范围内*/public static boolean isValidTimestamp(Long timestamp) {long currentTime = System.currentTimeMillis();return Math.abs(currentTime - timestamp) <= TIMESTAMP_TOLERANCE;}/*** 检查并验证nonce(一次性令牌)*/public static boolean isValidNonce(String nonce) {// 实际应用中应使用数据库或缓存服务来管理nonce// 这里简单示例中我们假设总是返回truereturn true;}
}

import java.io.*;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;public class RSAKeyPairGenerator {private static final String KEY_ALGORITHM = "RSA";private static final int KEY_SIZE = 2048; // 使用 2048 位密钥长度/*** 生成 RSA 密钥对*/public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {KeyPairGenerator keyGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);keyGen.initialize(KEY_SIZE);return keyGen.generateKeyPair();}/*** 将密钥保存到文件*/public static void saveKeyToFile(Key key, String filePath) throws IOException {try (FileOutputStream fos = new FileOutputStream(filePath);BufferedOutputStream bos = new BufferedOutputStream(fos);OutputStreamWriter writer = new OutputStreamWriter(bos, "UTF-8")) {byte[] encoded = key.getEncoded();String base64Encoded = Base64.getEncoder().encodeToString(encoded);writer.write(base64Encoded);}}/*** 从文件加载密钥*/public static Key loadKeyFromFile(String filePath, boolean isPublicKey) throws IOException, Exception {try (FileInputStream fis = new FileInputStream(filePath);BufferedInputStream bis = new BufferedInputStream(fis);InputStreamReader reader = new InputStreamReader(bis, "UTF-8");BufferedReader br = new BufferedReader(reader)) {StringBuilder sb = new StringBuilder();String line;while ((line = br.readLine()) != null) {sb.append(line);}byte[] encoded = Base64.getDecoder().decode(sb.toString());KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);if (isPublicKey) {X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded);return keyFactory.generatePublic(spec);} else {PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(encoded);return keyFactory.generatePrivate(spec);}}}/*** 主函数:生成密钥对并保存到文件*/public static void main(String[] args) {try {KeyPair keyPair = generateKeyPair();// 保存公钥到文件saveKeyToFile(keyPair.getPublic(), "public.key");// 保存私钥到文件saveKeyToFile(keyPair.getPrivate(), "private.key");System.out.println("RSA 密钥对已成功生成并保存!");} catch (Exception e) {e.printStackTrace();}}
}

三、集成到springboot

1、创建签名拦截器

import cn.hutool.core.util.StrUtil;
import com.star.platform.springmvc.interceptor.ContentCachingRequestWrapper;
import com.star.sms.business.http.utils.RSAUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;@Slf4j
@Component
public class SignatureInterceptor implements HandlerInterceptor {private static final String SESSION_ID = "sessionId";@Value("${rsaPrivateKey:#{null}}")private String rsaPrivateKey;@Value("${rsaPublicKey}")private String rsaPublicKey;@Value("${sign.enabled:false}")private boolean signEnable;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (!(handler instanceof HandlerMethod) || !signEnable) {return true;}if ("POST".equalsIgnoreCase(request.getMethod())) {try {validateSignature(request);} catch (IOException e) {throw new SecurityException("Failed to parse request body.", e);} catch (SecurityException e) {response.setStatus(HttpServletResponse.SC_FORBIDDEN);response.getWriter().write(e.getMessage());return false;}}return true;}//验签的封装成独立的方法public void validateSignature(HttpServletRequest request) throws Exception {ContentCachingRequestWrapper contentWrapper = (ContentCachingRequestWrapper) request;String requestBody = new String(contentWrapper.getBody(), StandardCharsets.UTF_8);// 从请求头获取签名、时间戳和随机数String receivedSign = request.getHeader("X-Signature");String timestampStr = request.getHeader("X-Timestamp");Long timestamp = StrUtil.isBlank(timestampStr) ? null : Long.parseLong(timestampStr);String nonce = request.getHeader("X-Nonce");String sessionId = request.getHeader(SESSION_ID);if (StrUtil.isBlank(receivedSign) || timestamp == null || StrUtil.isBlank(nonce)) {throw new SecurityException("Missing signature, timestamp or nonce.");}// 构造待验签的数据String dataToVerify = RSAUtil.buildDataToSign(requestBody, timestamp, nonce, sessionId);log.info("dataToVerify={}", dataToVerify);//PrivateKey privateKey = RSAUtil.loadPrivateKey(rsaPrivateKey);//String genPrivateSignStr = RSAUtil.sign(dataToVerify, privateKey);//log.info("genPrivateSignStr={}", genPrivateSignStr);// 验证签名PublicKey publicKey = RSAUtil.loadPublicKey(rsaPublicKey); // 加载公钥if (!RSAUtil.verify(dataToVerify, receivedSign, publicKey)) {throw new SecurityException("Invalid signature.");}// 验证时间戳和随机数的有效性if (!RSAUtil.isValidTimestamp(timestamp)) {throw new SecurityException("Expired timestamp.");}}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}}
import org.apache.commons.io.IOUtils;import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;public class ContentCachingRequestWrapper extends HttpServletRequestWrapper {private byte[] body;private BufferedReader reader;private ServletInputStream inputStream;public ContentCachingRequestWrapper(HttpServletRequest request) throws IOException {super(request);loadBody(request);}private void loadBody(HttpServletRequest request) throws IOException {body = IOUtils.toByteArray(request.getInputStream());inputStream = new RequestCachingInputStream(body);}public byte[] getBody() {return body;}@Overridepublic ServletInputStream getInputStream() throws IOException {if (inputStream != null) {return inputStream;}return super.getInputStream();}@Overridepublic BufferedReader getReader() throws IOException {if (reader == null) {reader = new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding()));}return reader;}private static class RequestCachingInputStream extends ServletInputStream {private final ByteArrayInputStream inputStream;public RequestCachingInputStream(byte[] bytes) {inputStream = new ByteArrayInputStream(bytes);}@Overridepublic int read() throws IOException {return inputStream.read();}@Overridepublic boolean isFinished() {return inputStream.available() == 0;}@Overridepublic boolean isReady() {return true;}@Overridepublic void setReadListener(ReadListener readlistener) {}}}

2、注册拦截器

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate SignatureInterceptor signatureInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(signatureInterceptor).addPathPatterns("/api/**"); // 根据需要调整路径模式}
}

3、启动后就可以测试啦

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com