java后台与c#客户端免密实现

项目中c#客户端需要访问Java后台并且实现免密方案,所以增加了一个简单的免密任认证的方式

c#前台代码:


  public class SignatureHelper
    {
        private readonly string _apiKey;
        private readonly string _apiSecret;

        public SignatureHelper()
        {
            _apiKey = "APPKEY";
            _apiSecret = CreateAppSecret(_apiKey);
        }


        private static string CreateAppSecret(string appKey)
        {
            string encodeString = appKey + DateTime.Today.ToString("yyyy-MM-dd");
            return EncryptToMD5(encodeString);
        }

        /// <summary>
        /// MD5加密方法
        /// </summary>
        /// <param name="input">待加密字符串</param>
        /// <returns>32位小写MD5哈希值</returns>
        public static string EncryptToMD5(string input)
        {
            if (string.IsNullOrEmpty(input))
                return string.Empty;
           
            using (MD5 md5 = MD5.Create())
            {
                 
                byte[] inputBytes = Encoding.UTF8.GetBytes(input);
                byte[] hashBytes = md5.ComputeHash(inputBytes);
                StringBuilder hexString = new StringBuilder();
                for (int i = 0; i < hashBytes.Length; i++)
                { 
                    hexString.Append(hashBytes[i].ToString("x2"));
                }
                return hexString.ToString();
            }
        }

        public string GenerateSignature(string method, string url, string timestamp, string nonce, string body = "")
        {
            
            var signString = $"{method.ToUpper()}\n{url}\n{timestamp}\n{nonce}\n{body}";

            using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_apiSecret)))
            {
                var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(signString));
                return Convert.ToBase64String(hash);
            }
        }

        public Dictionary<string, string> GenerateHeaders(string method, string url, string body = "")
        {
            var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
            var nonce = Guid.NewGuid().ToString("N");
            var signature = GenerateSignature(method, url, timestamp, nonce, body);

            return new Dictionary<string, string>
        {
            {"X-API-Key", _apiKey},
            {"X-Timestamp", timestamp},
            {"X-Nonce", nonce},
            {"X-Signature", signature}
        };
        }
    }

注意这里的appScrect用appkey+年月日实现以实现和Java对称,更复杂一点这里不能这样实现。

java 后台使用spring拦截器实现验证

public class SignatureInterceptor {

    public static boolean preHandle(HttpServletRequest request) throws Exception {

        // 预请求不验证
        if (HttpMethod.OPTIONS.matches(request.getMethod())) {
            return true;
        }

        // 获取请求体
        String requestBody = getRequestBody(request);
        // 验证签名
        if (!SignUtils.validateSignature(request, requestBody)) {
            log.error("签名验证失败");
            return false;
        }
        return true;
    }

    /**
     * 获取请求体(支持重复读取)
     */
    private static String getRequestBody(HttpServletRequest request) {
        if (request instanceof ContentCachingRequestWrapper) {
            ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
            byte[] buf = wrapper.getContentAsByteArray();
            if (buf.length > 0) {
                return new String(buf, StandardCharsets.UTF_8);
            }
        }
        return "";
    }
SignUtils验证实现
 private static final long TIMESTAMP_TOLERANCE = 5 * 60 * 1000; // 5分钟容忍度

    /**
     * 验证签名
     */
    public static boolean validateSignature(HttpServletRequest request, String requestBody) {
        try {
            // 1. 获取请求头
            String apiKey = getHeader(request, "X-API-Key");
            String timestamp = getHeader(request, "X-Timestamp");
            String nonce = getHeader(request, "X-Nonce");
            String signature = getHeader(request, "X-Signature");

            // 2. 检查必要头部是否存在
            if (StringUtils.isAnyBlank(apiKey, timestamp, nonce, signature)) {
                log.warn("签名验证失败:缺少必要请求头");
                return false;
            }

            // 3. 验证时间戳
            if (!validateTimestamp(timestamp)) {
                log.warn("签名验证失败:时间戳无效");
                return false;
            }

            // 4. 获取API密钥
            String apiSecret = getSecretByKey(apiKey);
            if (StringUtils.isBlank(apiSecret)) {
                log.warn("签名验证失败:API Key无效");
                return false;
            }

            // 5. 重新计算签名
            String method = request.getMethod();
            String url = request.getRequestURL().toString();
            String calculatedSignature = calculateSignature(method, url, timestamp, nonce, requestBody, apiSecret);

//            System.out.println("method:"+method);
//            System.out.println("v:"+timestamp);
//            System.out.println("url:"+url);
//            System.out.println("calculatedSignature:"+calculatedSignature);
            // 6. 比较签名
            boolean isValid = signature.equals(calculatedSignature);
            if (!isValid) {
                log.warn("签名验证失败:签名不匹配");
            }
            return isValid;

        } catch (Exception e) {
            log.error("签名验证异常", e);
            return false;
        }
    }

    private static String getSecretByKey(String apiKey) {
        return encryptToMD5(apiKey + DateTime.date2String(new Date()));
    }

    /**
     * 验证时间戳
     */
    private static boolean validateTimestamp(String timestampStr) {
        try {
            long requestTime = Long.parseLong(timestampStr) * 1000;
            long currentTime = System.currentTimeMillis();
            return Math.abs(currentTime - requestTime) <= TIMESTAMP_TOLERANCE;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    /**
     * 计算签名
     */
    private static String calculateSignature(String method, String url, String timestamp,
                                             String nonce, String body, String secret) {
        // 构造签名字符串
        String signString = buildSignString(method, url, timestamp, nonce, body);

        try {
            Mac hmac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            hmac.init(secretKey);
            byte[] hash = hmac.doFinal(signString.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(hash);
        } catch (Exception e) {
            throw new RuntimeException("签名计算失败", e);
        }
    }

    /**
     * 构造签名字符串
     */
    private static String buildSignString(String method, String url, String timestamp,
                                          String nonce, String body) {
        return String.format("%s\n%s\n%s\n%s\n%s",
                method.toUpperCase(),
                url,
                timestamp,
                nonce,
                body != null ? body : "");
    }

    private static String getHeader(HttpServletRequest request, String headerName) {
        String value = request.getHeader(headerName);
        return value != null ? value.trim() : "";
    }

    /**
     * MD5加密方法
     *
     * @param input 待加密字符串
     * @return 32位小写MD5哈希值
     */
    public static String encryptToMD5(String input) {
        if (input == null) {
            return null;
        }

        try {
            // 创建MD5消息摘要实例
            MessageDigest md5 = MessageDigest.getInstance("MD5");

            // 使用UTF-8编码将字符串转换为字节数组
            byte[] inputBytes = input.getBytes("UTF-8");

            // 计算MD5哈希值
            byte[] hashBytes = md5.digest(inputBytes);

            // 将字节数组转换为十六进制字符串
            StringBuilder hexString = new StringBuilder();
            for (byte b : hashBytes) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }

            return hexString.toString();

        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5 algorithm not found", e);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("UTF-8 encoding not supported", e);
        }
    }

以上一个简单的免密认证就完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值