| | |
| | | audit.put("operationName", "账号登录"); |
| | | audit.put("requestMethod", "POST"); |
| | | audit.put("requestUri", "/oauth/getToken"); |
| | | audit.put("requestParams", "username=" + mask(request == null ? null : request.getUsername())); |
| | | audit.put("userAccount", mask(request == null ? null : request.getUsername())); |
| | | audit.put("requestParams", "username=" + (request == null ? null : request.getUsername())); |
| | | // 登录账号是审计追溯字段,不做掩码;密码、token 等敏感字段仍然脱敏。 |
| | | audit.put("userAccount", request == null ? null : request.getUsername()); |
| | | audit.put("result", success ? "SUCCESS" : "FAIL"); |
| | | audit.put("errorMessage", error == null ? null : error.replaceAll("(?i)(password|token)\\s*[:=]\\s*[^,\\s&}]+", "$1=***")); |
| | | audit.put("clientIp", clientIp(httpRequest)); |
| | |
| | | } catch (Exception ignored) { |
| | | // 审计链路不可用时绝不能影响认证主流程;客户端会输出 Feign 异常用于告警。 |
| | | } |
| | | } |
| | | |
| | | private String mask(String value) { |
| | | if (value == null || value.length() <= 4) return "***"; |
| | | return value.substring(0, 3) + "***" + value.substring(value.length() - 2); |
| | | } |
| | | |
| | | private String clientIp(HttpServletRequest request) { |
| | |
| | | audit.put("requestMethod", request.getMethodValue()); |
| | | audit.put("requestUri", uri); |
| | | audit.put("requestParams", maskedQuery(request)); |
| | | audit.put("userAccount", mask(username(request))); |
| | | // 审计账号用于追溯操作人,必须保留原始账号;手机号、证件号等请求参数仍按规则脱敏。 |
| | | audit.put("userAccount", username(request)); |
| | | audit.put("clientIp", clientIp(request)); |
| | | int status = exchange.getResponse().getStatusCode() == null ? 200 : exchange.getResponse().getStatusCode().value(); |
| | | audit.put("result", status < 400 ? "SUCCESS" : "FAIL"); |
| | |
| | | key.toLowerCase().matches(".*(password|token|secret|idcard|bank|phone|mobile).*") ? "***" : value)); |
| | | return JSON.toJSONString(values); |
| | | } |
| | | private String mask(String value) { return StringUtils.isBlank(value) ? null : value.length() <= 4 ? "***" : value.substring(0, 3) + "***" + value.substring(value.length() - 2); } |
| | | private String clientIp(ServerHttpRequest request) { String value = request.getHeaders().getFirst("X-Forwarded-For"); return StringUtils.isBlank(value) ? String.valueOf(request.getRemoteAddress()) : value.split(",")[0].trim(); } |
| | | } |
| | |
| | | import feign.RequestInterceptor; |
| | | import feign.RequestTemplate; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.Date; |
| | |
| | | audit.put("requestUri", url); |
| | | audit.put("requestParams", template.body() == null ? null : "bodyBytes=" + template.body().length); |
| | | audit.put("userId", UserExt.getUser() == null ? null : UserExt.getUser().getId()); |
| | | audit.put("userAccount", UserExt.getUser() == null ? null : mask(UserExt.getUser().getAccount())); |
| | | audit.put("userAccount", UserExt.getUser() == null ? null : UserExt.getUser().getAccount()); |
| | | audit.put("result", "REQUESTED"); |
| | | audit.put("createdAt", new Date()); |
| | | auditWriter.append(audit); |
| | |
| | | private String riskLevel(String url) { |
| | | String value = url == null ? "" : url.toLowerCase(); |
| | | return value.matches(".*(ticket|payment|finance|refund|withdraw|balance|status|state).*") ? "CRITICAL" : "NORMAL"; |
| | | } |
| | | private String mask(String value) { |
| | | if (StringUtils.isEmpty(value)) return null; |
| | | return value.length() <= 4 ? "***" : value.substring(0, 3) + "***" + value.substring(value.length() - 2); |
| | | } |
| | | } |
| | |
| | | public Map<String, Object> appUser(@PathVariable Integer id) { |
| | | assertAdmin(); |
| | | Map<String, Object> result = appUserClient.sensitiveAppUser(id); |
| | | writeAudit("app-user", id, result == null ? "NOT_FOUND" : "SUCCESS"); |
| | | writeAudit("app-user", id, userName(result), result == null ? "NOT_FOUND" : "SUCCESS"); |
| | | return result; |
| | | } |
| | | |
| | |
| | | result.put("phone", employee.getPhone()); |
| | | result.put("idCard", employee.getIdCard()); |
| | | } |
| | | writeAudit("employee", id, result == null ? "NOT_FOUND" : "SUCCESS"); |
| | | writeAudit("employee", id, employee == null ? null : employee.getName(), result == null ? "NOT_FOUND" : "SUCCESS"); |
| | | return result; |
| | | } |
| | | |
| | |
| | | public Map<String, Object> student(@PathVariable Integer id) { |
| | | assertAdmin(); |
| | | Map<String, Object> result = appUserClient.sensitiveStudent(id); |
| | | writeAudit("student", id, result == null ? "NOT_FOUND" : "SUCCESS"); |
| | | writeAudit("student", id, userName(result), result == null ? "NOT_FOUND" : "SUCCESS"); |
| | | return result; |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | private void writeAudit(String resource, Integer targetUserId, String result) { |
| | | private void writeAudit(String resource, Integer targetUserId, String targetUserName, String result) { |
| | | Map<String, Object> audit = new HashMap<String, Object>(); |
| | | audit.put("traceId", UUID.randomUUID().toString().replace("-", "")); |
| | | audit.put("sourceService", "mb-cloud-management"); |
| | |
| | | audit.put("operationName", "超级管理员查看敏感信息"); |
| | | audit.put("requestMethod", "GET"); |
| | | audit.put("requestUri", "/system/sensitive/" + resource + "/" + targetUserId); |
| | | audit.put("requestParams", "targetUserId=" + targetUserId); |
| | | // 敏感信息访问需同时记录对象名称,避免日志中只有无法识别的 ID。 |
| | | audit.put("requestParams", "targetUserId=" + targetUserId + ", targetUserName=" |
| | | + (targetUserName == null ? "-" : targetUserName)); |
| | | audit.put("userId", UserExt.getUser().getId()); |
| | | audit.put("userAccount", "admin"); |
| | | audit.put("result", result); |
| | | audit.put("createdAt", new Date()); |
| | | auditWriter.append(audit); |
| | | } |
| | | |
| | | private String userName(Map<String, Object> user) { |
| | | if (user == null || user.get("name") == null) return null; |
| | | return String.valueOf(user.get("name")); |
| | | } |
| | | } |
| | |
| | | SysAccountAuditLog log = new SysAccountAuditLog(); |
| | | log.setTraceId(StringUtils.defaultIfBlank(request.getHeader(TRACE_HEADER), UUID.randomUUID().toString().replace("-", ""))); |
| | | log.setUserId(toLong(request.getHeader("userId"))); |
| | | log.setUserAccount(mask(request.getHeader("username"))); |
| | | // 账号列用于定位操作人,不能保存为 a***in 这类不可检索的掩码值。 |
| | | log.setUserAccount(request.getHeader("username")); |
| | | log.setSourceService("mb-cloud-other"); |
| | | log.setSourceType("HTTP"); |
| | | log.setRequestMethod(method); |
| | |
| | | return JSON.toJSONString(result); |
| | | } |
| | | |
| | | static String mask(String value) { |
| | | if (StringUtils.isBlank(value)) return value; |
| | | return value.length() <= 4 ? "***" : value.substring(0, 3) + "***" + value.substring(value.length() - 2); |
| | | } |
| | | static String maskText(String text) { return text == null ? null : text.replaceAll("(?i)(password|token|authorization|idcard|bankCard)\\s*[:=]\\s*[^,\\s&}]+", "$1=***"); } |
| | | static boolean isSensitiveKey(String key) { return key != null && key.toLowerCase().matches(".*(password|token|authorization|secret|idcard|identity|bank|card|mobile|phone).*" ); } |
| | | static Long toLong(String value) { try { return StringUtils.isBlank(value) ? null : Long.valueOf(value); } catch (NumberFormatException e) { return null; } } |
| | |
| | | /** 账号ID。 */ |
| | | @TableField("user_id") |
| | | private Long userId; |
| | | /** 脱敏后的账号标识。 */ |
| | | /** 原始账号标识,用于准确追溯操作人;敏感个人信息仍在其他字段脱敏。 */ |
| | | @TableField("user_account") |
| | | private String userAccount; |
| | | /** 发起服务。 */ |