项目中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);
}
}
以上一个简单的免密认证就完成了。
862

被折叠的 条评论
为什么被折叠?



