航空旅客信息安全防护:从数据加密到接口越权防御实战

📅 发布时间:2026/8/30 12:00:28
航空旅客信息安全防护:从数据加密到接口越权防御实战
航空旅客信息保护与安全防御从数据泄露风险到系统化防护实践每年航空出行高峰期关于旅客隐私泄露、航班信息被恶意查询、行程数据被倒卖的新闻层出不穷。作为技术人员我们在关注航空安全的同时也常常忽略一个更基础的问题支撑旅客服务的信息系统本身是否足够安全当“航空旅客安全”的话题被讨论时大家更多关注的是机场安检、飞行过程、应急处置这些物理层面的保障。但今天我想换一个视角从技术防御的角度完整梳理航空旅客个人信息在存储、传输、查询、展示各个环节可能面临的风险并给出可落地的安全防护方案。无论你是航空公司业务系统的开发者、机场信息化部门的工程师还是正在设计旅客服务平台的架构师这篇文章都值得收藏备用。本文将围绕旅客信息系统的威胁建模、敏感数据加密方案、接口访问控制、日志审计与异常检测、以及安全开发生命周期这几个核心方向展开结合完整代码示例和配置片段带你搭建一套覆盖“端 - 管 - 云”的旅客信息防护体系。1. 背景为什么旅客信息成为攻击目标1.1 旅客数据的敏感性远超你的想象先来看一个最简单的例子。旅客订座记录中通常包含以下字段姓名、身份证号、护照号手机号、常用邮箱航班号、座位号、行程日期常旅客卡号、积分余额支付信息部分系统会保存卡号后四位这些字段单独看似乎没什么但组合在一起就是完整的个人画像。攻击者拿到姓名和身份证号可以尝试撞库拿到航班号和日期可以精确掌握某人的出行轨迹拿到手机号可以定向发送钓鱼短信。更严重的是如果攻击者能够查询任意旅客的行程信息就可能实现“精准追踪”。这正是航空旅客信息系统需要重点防护的原因数据价值高、字段敏感、调用链长、参与方多。一套系统可能同时服务官方App、机场自助值机、第三方OTA平台、航信接口、地服系统等多个终端攻击面非常大。1.2 旅客数据泄露的典型路径根据近年来公开的安全事件分析旅客信息泄露路径主要有以下几种泄露路径典型攻击方式后果接口越权遍历旅客编号、订单号批量获取他人行程SQL注入拼接查询参数拖库、删库日志泄露敏感字段未脱敏写入日志运维人员或攻击者通过日志获取数据第三方接口滥用未鉴权或鉴权薄弱通过开放接口批量查询内部人员泄露高权限账号滥用定向查询特定旅客信息这些路径说明旅客信息安全不只是“数据库加个密”这么简单。它需要从网络边界、应用逻辑、数据存储、运维审计多个层面协同防御。1.3 为什么要从技术层面关注有些团队觉得“我们用的是内网攻击者进不来”有些团队认为“等保测评过了就安全了”。但实际情况是航空旅客业务链条长涉及大量第三方系统对接任何一方的防护薄弱都可能成为突破口。作为开发者和运维人员我们至少应该做到“即使数据被拿走也无法直接读取明文”。2. 环境准备与版本说明本文的实战部分采用 Spring Boot MyBatis Plus Redis Vue 作为示例技术栈重点展示后端防护部分的实现思路。版本信息以常见稳定版为例实际操作时请根据你的项目环境调整。JDK1.8 Spring Boot2.7.x MyBatis Plus3.5.x Redis6.x MySQL8.0 Maven3.6本文涉及的代码都是核心片段需要放入你现有的项目结构中。如果你还没有项目骨架可以先用 Spring Initializr 创建一个最简单的 Web 项目再逐步添加依赖。建议的项目结构passenger-security-demo/ ├── src/main/java/com/example/security/ │ ├── controller/ │ ├── service/ │ ├── mapper/ │ ├── entity/ │ ├── config/ │ └── util/ ├── src/main/resources/ │ ├── application.yml │ └── mapper/ └── pom.xml3. 旅客信息系统的威胁分析与安全设计原则3.1 充分信任原则 vs 零信任原则很多传统系统的开发模式是“内网可信”。系统内部服务之间调用不鉴权、参数不校验、敏感数据不加密一旦攻击者通过某个漏洞拿到内网权限整个数据库就暴露了。零信任原则要求我们对每一个访问请求都重新验证无论它来自内网还是外网。具体到旅客信息系统接口调用必须鉴权即使是内部服务之间每个请求必须校验参数范围防止越权敏感数据存储时加密不信任数据库层的访问控制日志中不记录明文敏感字段不信任日志文件的安全性3.2 最小权限原则最小权限原则同样关键。比如查询旅客信息的接口只返回当前业务需要的字段不同系统角色值机、售票、客服拥有不同的数据范围运维账号不应具有业务数据的查询权限第三方对接账号只能访问授权范围内的接口3.3 纵深防御不要指望某单一安全措施能够挡住所有攻击。合理的做法是“层层设防”网络层通过防火墙、安全组限制来源IP应用层接口鉴权、参数校验、频率控制数据层敏感字段加密、数据库账号分权运维层操作审计、异常访问告警4. 核心安全能力实现4.1 敏感数据加密存储4.1.1 选择加密方案旅客信息中身份证号、手机号等字段适合采用AES对称加密方案。为什么不用MD5因为MD5是不可逆的业务中经常需要还原明文展示给旅客或客服所以必须使用可逆加密。这里以 AES/GCM 模式为例因为它比 ECB 模式更安全而且带有认证标签可以防止密文被篡改。4.1.2 AES加解密工具类// 文件路径src/main/java/com/example/security/util/AesUtil.java package com.example.security.util; import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.Base64; public class AesUtil { private static final int GCM_TAG_LENGTH_BITS 128; private static final int IV_LENGTH_BYTES 12; public static String encrypt(String plaintext, String secretKey) throws Exception { byte[] iv new byte[IV_LENGTH_BYTES]; SecureRandom secureRandom new SecureRandom(); secureRandom.nextBytes(iv); Cipher cipher Cipher.getInstance(AES/GCM/NoPadding); SecretKeySpec keySpec new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), AES); GCMParameterSpec gcmSpec new GCMParameterSpec(GCM_TAG_LENGTH_BITS, iv); cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec); byte[] cipherText cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); // 将 IV 和密文拼接后 Base64 编码 byte[] result new byte[iv.length cipherText.length]; System.arraycopy(iv, 0, result, 0, iv.length); System.arraycopy(cipherText, 0, result, iv.length, cipherText.length); return Base64.getEncoder().encodeToString(result); } public static String decrypt(String encryptedData, String secretKey) throws Exception { byte[] decoded Base64.getDecoder().decode(encryptedData); byte[] iv new byte[IV_LENGTH_BYTES]; System.arraycopy(decoded, 0, iv, 0, IV_LENGTH_BYTES); byte[] cipherText new byte[decoded.length - IV_LENGTH_BYTES]; System.arraycopy(decoded, IV_LENGTH_BYTES, cipherText, 0, cipherText.length); Cipher cipher Cipher.getInstance(AES/GCM/NoPadding); SecretKeySpec keySpec new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), AES); GCMParameterSpec gcmSpec new GCMParameterSpec(GCM_TAG_LENGTH_BITS, iv); cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec); byte[] plainText cipher.doFinal(cipherText); return new String(plainText, StandardCharsets.UTF_8); } }这里的重点在于GCM模式自带认证能力。如果密文在传输或存储过程中被篡改解密时会抛出异常而不是返回错误明文。这比传统的AES/CBC/PKCS5Padding方案更可靠。4.1.3 数据库字段设计加密后数据建议使用 VARCHAR 类型存储字段长度要预留足够空间。加密前身份证号是18位加密后可能是80到120个字符需要根据实际情况扩容。CREATE TABLE passenger_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL COMMENT 姓名明文展示, id_card_encrypted VARCHAR(256) NOT NULL COMMENT 身份证号AES加密, phone_encrypted VARCHAR(128) NOT NULL COMMENT 手机号AES加密, flight_no VARCHAR(20) NOT NULL, seat_no VARCHAR(10), travel_date DATE NOT NULL, created_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE InnoDB DEFAULT CHARSET utf8mb4;注意姓名是否加密需要根据业务场景判断。如果查询列表时需要展示姓名加密后无法按姓名模糊搜索会造成较大的业务改造。比较常见的方案是“姓名明文、身份证号和手机号加密”或者“姓名也加密但增加一个姓名的全文索引字段”。4.1.4 加解密在业务层的应用// 文件路径src/main/java/com/example/security/service/PassengerService.java package com.example.security.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.example.security.entity.PassengerInfo; import com.example.security.mapper.PassengerInfoMapper; import com.example.security.util.AesUtil; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import javax.annotation.Resource; Service public class PassengerService { Resource private PassengerInfoMapper passengerInfoMapper; Value(${security.aes-key}) private String aesKey; public PassengerInfo queryPassengerById(Long id) { PassengerInfo passengerInfo passengerInfoMapper.selectById(id); if (passengerInfo null) { return null; } // 解密敏感字段后才返回给前端 try { passengerInfo.setIdCard(AesUtil.decrypt(passengerInfo.getIdCardEncrypted(), aesKey)); passengerInfo.setPhone(AesUtil.decrypt(passengerInfo.getPhoneEncrypted(), aesKey)); } catch (Exception e) { throw new RuntimeException(解密失败请检查密钥配置); } return passengerInfo; } public PassengerInfo queryByFlightNo(String flightNo) { LambdaQueryWrapperPassengerInfo wrapper new LambdaQueryWrapper(); wrapper.eq(PassengerInfo::getFlightNo, flightNo); return passengerInfoMapper.selectOne(wrapper); } }这里需要特别提醒解密操作尽量在服务端完成不要在数据库层做。原因有两点第一数据库层解密需要把密钥配置在数据库服务器上一旦数据库被攻破密钥也泄露第二在应用层解密可以配合后续的权限校验、访问审计做统一控制。4.2 接口访问控制与越权防护4.2.1 Token认证旅客服务接口通常采用 Token令牌认证机制。以最常见的方式为例旅客通过账号密码登录服务端验证后生成 Token 并返回后续请求携带 Token服务端校验通过后处理使用 Redis 存储 Token 时可以设置过期时间实现服务端主动失效。核心配置如下# 文件路径src/main/resources/application.yml spring: redis: host: localhost port: 6379 timeout: 3000ms security: aes-key: your-256-bit-secret-key-please-change! token-expire-hours: 244.2.2 防止水平越权水平越权是指攻击者通过修改请求参数如用户ID、订单号访问他人的数据。比如查询旅客接口// 文件路径src/main/java/com/example/security/controller/PassengerController.java package com.example.security.controller; import com.example.security.common.Result; import com.example.security.entity.PassengerInfo; import com.example.security.service.PassengerService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; RestController RequestMapping(/api/passenger) public class PassengerController { Resource private PassengerService passengerService; GetMapping(/{id}) public ResultPassengerInfo getPassenger(PathVariable(id) Long id) { PassengerInfo passengerInfo passengerService.queryPassengerById(id); return Result.success(passengerInfo); } }如果这里不校验当前登录用户是否拥有该旅客数据的访问权限攻击者只需要将id从100改成101、102、103就能遍历全库的旅客信息。这是一种非常典型的漏洞也是航班信息被恶意查询的主要入口之一。改进方案在查询前增加数据归属校验。GetMapping(/{id}) public ResultPassengerInfo getPassenger(PathVariable(id) Long id) { // 假设从Token中解析出当前登录用户ID Long currentUserId TokenHolder.getCurrentUserId(); PassengerInfo passengerInfo passengerService.queryPassengerById(id); if (passengerInfo null) { return Result.error(旅客信息不存在); } // 校验数据归属只能查看本人或本人订单关联的旅客信息 if (!passengerInfo.getUserId().equals(currentUserId)) { return Result.error(无权访问该旅客信息); } // 脱敏处理 passengerInfo.setIdCard(maskIdCard(passengerInfo.getIdCard())); passengerInfo.setPhone(maskPhone(passengerInfo.getPhone())); return Result.success(passengerInfo); }4.2.3 接口频率控制对于查询类接口一定要做频率限制。攻击者即使拿到了合法Token也可能通过高频调用批量拉取数据。这里使用简单的Redis计数器实现频率限制。// 文件路径src/main/java/com/example/security/config/RateLimitInterceptor.java package com.example.security.config; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.concurrent.TimeUnit; Component public class RateLimitInterceptor implements HandlerInterceptor { Resource private StringRedisTemplate stringRedisTemplate; private static final int MAX_COUNT 10; private static final long WINDOW_SECONDS 60; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String userId request.getHeader(X-User-Id); if (userId null || userId.isEmpty()) { response.setStatus(401); return false; } String key rate:limit: userId : request.getRequestURI(); Long count stringRedisTemplate.opsForValue().increment(key); if (count ! null count 1) { stringRedisTemplate.expire(key, WINDOW_SECONDS, TimeUnit.SECONDS); } if (count ! null count MAX_COUNT) { response.setStatus(429); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:429,\msg\:\请求过于频繁请稍后再试\}); return false; } return true; } }4.3 敏感信息脱敏展示4.3.1 脱敏工具示例即使是有权限查询的接口也不要明文返回完整的身份证号和手机号。合理的做法是默认返回脱敏数据只有在特定场景如值机、退改签需要用户验证时才返回完整明文。// 文件路径src/main/java/com/example/security/util/DesensitizeUtil.java package com.example.security.util; public class DesensitizeUtil { public static String maskPhone(String phone) { if (phone null || phone.length() 7) { return phone; } return phone.substring(0, 3) **** phone.substring(7); } public static String maskIdCard(String idCard) { if (idCard null || idCard.length() 10) { return idCard; } return idCard.substring(0, 4) ********** idCard.substring(14); } public static String maskName(String name) { if (name null || name.isEmpty()) { return name; } if (name.length() 1) { return name; } if (name.length() 2) { return name.charAt(0) *; } StringBuilder sb new StringBuilder(); sb.append(name.charAt(0)); for (int i 0; i name.length() - 2; i) { sb.append(*); } sb.append(name.charAt(name.length() - 1)); return sb.toString(); } }4.3.2 返回结果中统一处理实际项目中强烈建议不要在业务代码里逐行调用脱敏工具而应该通过统一响应包装或注解的方式处理。这样不会漏掉某个接口也不会污染已有的业务逻辑。以 Jackson 自定义序列化注解为例// 文件路径src/main/java/com/example/security/config/SensitiveSerializeConfig.java package com.example.security.config; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.ContextualSerializer; import com.example.security.util.DesensitizeUtil; import java.io.IOException; public class SensitiveFieldSerializer extends JsonSerializerString implements ContextualSerializer { private DesensitizeType type; public SensitiveFieldSerializer() { } public SensitiveFieldSerializer(DesensitizeType type) { this.type type; } Override public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { if (value null) { gen.writeNull(); return; } switch (type) { case PHONE: gen.writeString(DesensitizeUtil.maskPhone(value)); break; case ID_CARD: gen.writeString(DesensitizeUtil.maskIdCard(value)); break; case NAME: gen.writeString(DesensitizeUtil.maskName(value)); break; default: gen.writeString(value); } } Override public JsonSerializer? createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { if (property ! null) { SensitiveField annotation property.getAnnotation(SensitiveField.class); if (annotation ! null) { return new SensitiveFieldSerializer(annotation.type()); } } return this; } public enum DesensitizeType { PHONE, ID_CARD, NAME } }在实体字段上添加注解public class PassengerInfo { private Long id; private String name; SensitiveField(type SensitiveFieldSerializer.DesensitizeType.ID_CARD) private String idCard; SensitiveField(type SensitiveFieldSerializer.DesensitizeType.PHONE) private String phone; // getter / setter 省略 }这样接口返回时就会自动脱敏无需在每个Controller方法里手动处理。4.4 日志审计与敏感信息保护4.4.1 日志脱敏的必要性很多安全性事件不是从数据库泄露的而是从日志文件泄露的。比如开发人员在调试时打印了完整的请求体日志采集系统将明文身份证号推到ELK运维人员或第三方日志服务商就能看到这些数据。所以日志中必须禁止打印完整敏感字段。这里给出Logback自定义转换器示例// 文件路径src/main/java/com/example/security/config/MaskingConverter.java package com.example.security.config; import ch.qos.logback.classic.pattern.MessageConverter; import ch.qos.logback.classic.spi.ILoggingEvent; import java.util.regex.Matcher; import java.util.regex.Pattern; public class MaskingConverter extends MessageConverter { private static final Pattern ID_CARD_PATTERN Pattern.compile((\\d{6})\\d{8}(\\d{3}[0-9Xx])); private static final Pattern PHONE_PATTERN Pattern.compile((1[3-9]\\d)(\\d{4})(\\d{4})); Override public String convert(ILoggingEvent event) { String message event.getFormattedMessage(); Matcher idMatcher ID_CARD_PATTERN.matcher(message); if (idMatcher.find()) { message idMatcher.replaceAll($1********$2); } Matcher phoneMatcher PHONE_PATTERN.matcher(message); if (phoneMatcher.find()) { message phoneMatcher.replaceAll($1****$3); } return message; } }然后在logback-spring.xml中配置configuration conversionRule conversionWordmsg converterClasscom.example.security.config.MaskingConverter / appender nameFILE classch.qos.logback.core.rolling.RollingFileAppender filelogs/passenger-security.log/file encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n/pattern /encoder /appender root levelINFO appender-ref refFILE / /root /configuration除了日志脱敏还需要做访问审计。重要的旅客信息查询请求应该记录查询人账号查询时间查询接口查询参数脱敏后的返回数据量客户端IP这些日志用于安全团队事后追溯。4.5 SQL注入与常见Web漏洞防护4.5.1 SQL注入防护使用 MyBatis Plus 的 LambdaQueryWrapper 可以避免大部分SQL注入风险但如果有自定义SQL需要特别注意。下面的写法就是危险的!-- 危险写法禁止使用 -- select idqueryByName resultTypePassengerInfo SELECT * FROM passenger_info WHERE name ${name} /select正确的做法是使用#{}预编译占位符!-- 安全写法 -- select idqueryByName resultTypePassengerInfo SELECT * FROM passenger_info WHERE name #{name} /select4.5.2 输入校验接口入参必须做合法性校验。比如查询接口的ID必须是正整数手机号必须符合格式航班号必须匹配规则。可以使用Hibernate Validator实现public class QueryPassengerRequest { NotNull(message 旅客ID不能为空) Positive(message 旅客ID必须为正数) private Long id; Pattern(regexp ^[A-Z0-9]{2}\\d{3,4}$, message 航班号格式不正确) private String flightNo; // getter / setter 省略 }4.6 数据库账号与权限管理4.6.1 分账号分权限很多项目为了方便所有服务共用同一个数据库账号甚至直接使用root账号。这是非常危险的做法。合理的账号规划账号用途权限app_read查询服务SELECTapp_write写入服务SELECT, INSERT, UPDATEadmin运维管理全部仅限跳板机使用backup备份SELECT, LOCK TABLES创建账号的SQL示例-- 只读账号 CREATE USER app_read% IDENTIFIED BY password; GRANT SELECT ON passenger_db.* TO app_read%; -- 读写账号 CREATE USER app_write% IDENTIFIED BY password; GRANT SELECT, INSERT, UPDATE ON passenger_db.* TO app_write%; -- 限制来源IP CREATE USER admin192.168.10.% IDENTIFIED BY password; GRANT ALL PRIVILEGES ON passenger_db.* TO admin192.168.10.%; FLUSH PRIVILEGES;4.6.2 数据备份与恢复演练旅客数据不能丢失。数据库必须开启定期备份而且要定期做恢复演练。不要等真的需要恢复时才发现备份文件是坏的。推荐方案每天全量备份每6小时增量备份备份文件加密存储保留至少30天每月进行一次恢复测试5. 完整实战搭建一个安全的旅客信息查询服务5.1 创建Spring Boot项目并添加依赖在pom.xml中添加核心依赖dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency /dependencies5.2 配置数据源和Redisserver: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/passenger_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: app_read password: your-password driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 database: 0 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 security: aes-key: abcdef0123456789abcdef0123456789 token-expire-hours: 24 white-list: - /api/auth/login - /api/auth/register5.3 统一响应结构// 文件路径src/main/java/com/example/security/common/Result.java package com.example.security.common; public class ResultT { private int code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } public static T ResultT error(int code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } // getter / setter 省略 }5.4 配置拦截器// 文件路径src/main/java/com/example/security/config/WebConfig.java package com.example.security.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.annotation.Resource; import java.util.Arrays; Configuration public class WebConfig implements WebMvcConfigurer { Resource private AuthInterceptor authInterceptor; Resource private RateLimitInterceptor rateLimitInterceptor; Value(${security.white-list}) private String whiteList; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns(/api/**) .excludePathPatterns(Arrays.asList(whiteList.split(,))); registry.addInterceptor(rateLimitInterceptor) .addPathPatterns(/api/passenger/**); } }5.5 运行验证启动项目后使用Postman或curl进行测试# 1. 登录获取Token curl -X POST http://localhost:8080/api/auth/login \ -H Content-Type: application/json \ -d {username:testuser,password:123456} # 2. 携带Token查询旅客信息正常情况 curl -X GET http://localhost:8080/api/passenger/1 \ -H Authorization: Bearer eyJhbGciOi... # 3. 不带Token访问应返回401 curl -X GET http://localhost:8080/api/passenger/1 # 4. 高频访问应返回429 for i in $(seq 1 15); do curl -X GET http://localhost:8080/api/passenger/1 \ -H Authorization: Bearer eyJhbGciOi... done预期的输出如下脱敏后的数据{ code: 200, message: success, data: { id: 1, name: 张*, idCard: 1101**********1234, phone: 138****5678, flightNo: CA1234, seatNo: 12A, travelDate: 2025-06-01 } }6. 常见问题与排查思路问题现象常见原因解决思路解密时报错AEADBadTagException密文被篡改或者密钥不一致检查加密时的密钥与解密密钥是否一致检查数据在传输过程中是否被截断手机号/身份证号无法模糊搜索加密存储导致无法使用LIKE查询方案一增加用于搜索的摘要字段如后4位方案二使用Elasticsearch等搜索引擎先解密后同步索引注意权限控制接口被刷导致数据库压力大缺少频率限制或限流策略接入Redis限流同时考虑服务网关层限流日志中仍出现明文身份证日志脱敏Converter未生效检查Logback配置中的conversionRule是否配置正确检查是否还有其他Appender未经过转换器用户反馈查询他人订单成功接口缺少数据归属校验在业务代码中增加数据归属判断确保只能查询当前登录用户有权限的数据Redis中Token失效不统一多节点部署时Redis未共享使用统一的Redis集群设置合理的过期时间7. 最佳实践与工程建议7.1 从项目启动阶段就引入安全设计旅客信息系统不应该在开发完成后再“打补丁”。安全设计需要前置需求评审阶段明确哪些字段是敏感字段哪些接口需要权限控制设计阶段确定加密方案、脱敏方案、日志方案开发阶段代码走查时重点检查越权和SQL注入风险测试阶段加入安全测试用例包括越权访问、恶意参数、高频调用上线阶段检查生产环境密钥管理、数据库账号权限、网络访问控制7.2 密钥管理不要把AES密钥写死在代码里也不要直接放在配置文件并提交到Git。推荐使用配置中心或密钥管理服务至少做到“环境隔离、定期轮换”。开发环境、测试环境、生产环境使用不同的密钥密钥至少每90天轮换一次密钥泄露时能够快速更新并重新加密存量数据访问密钥管理服务的权限需要独立审计7.3 第三方接口对接的注意事项航空旅客业务经常需要与第三方系统对接。此时要注意每个第三方系统分配独立的API Key而不是复用内部账号第三方的调用范围限制在最少的接口和字段对第三方的调用频率进行独立监控对接文档中明确数据保护责任边界7.4 定期做安全评估安全不是一次性的工作。建议每季度做一次接口越权测试每半年做一次渗透测试每次大版本发布前做依赖安全扫描如OWASP Dependency Check持续关注CVE公告及时升级存在安全漏洞的组件7.5 团队意识和培训再好的技术防护也挡不住内部人员的有意或无意的泄露。建议建立这样的机制高权限账号申请需要审批敏感数据查询操作需要记录审计日志离职员工账号即时禁用定期对开发和运维人员进行数据安全培训8. 总结与下一步学习建议这篇文章从航空旅客信息系统的实际威胁出发整理了敏感数据加密存储、接口鉴权与越权防护、脱敏展示、日志审计、SQL注入防护、数据库权限管理等一系列防护手段。核心思路是“纵深防御”和“最小权限”相结合在每一层都增加攻击成本而不是依赖某一个单一防御点。对于刚入门的同学建议先从掌握AES加密的基本用法、接口Token认证和简单的脱敏工具开始把它们应用到自己的项目中。对于有一定经验的开发者可以进一步研究Spring Security OAuth2 授权码模式在多端场景下的应用基于注解的细粒度权限控制如PreAuthorize大数据量下敏感字段的高性能解密方案基于AI的异常访问检测与用户行为分析最后想说一点旅客信息安全的本质不是“不被人看到”而是“即使被人看到也无法解读出真实内容即使解读出来也能追踪到是谁、通过什么方式、在什么时间看到了这些数据”。如果这篇文章能帮你建立起自己的旅客信息安全防护体系欢迎收藏备用。