在社交 App中,用户上传的照片被发现带有 GPS 坐标。一名用户通过下载其他用户的照片、提取 EXIF 信息,精确定位到了对方的家庭住址。事情闹大后,安全团队紧急处理——所有已上传的照片都没做 EXIF 处理,相机拍出来的每一张照片都带着 GPS 经纬度、设备型号、拍摄时间。
用户上传一张照片,本以为分享的是美景,实际上暴露了坐标、手机型号、拍摄参数。今天聊聊怎么在上传环节自动剥离 EXIF 信息,同时处理好照片的旋转问题。
EXIF 里藏着什么
一张 iPhone 拍的照片,EXIF 里可能包含:
- GPS 经纬度(GPSLatitude / GPSLongitude)—— 精确到米
- 设备信息(Make / Model)—— "iPhone 15 Pro"
- 拍摄时间(DateTimeOriginal)
- 旋转方向(Orientation)—— 手机横拍还是竖拍
前三个是隐私问题。最后一个是显示问题——手机竖着拍的照片,在电脑上可能横着显示,因为 Orientation 标记了旋转角度,但不是所有看图软件都会正确解析。
剥离 EXIF:转码后重新输出
最简单的方法:把图片用 ImageIO 读进来,再写出去。这个过程中 EXIF 信息会自动丢失:
public byte[] stripExif(byte[] imageBytes) throws IOException {
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes));
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write(image, "jpg", out);
return out.toByteArray();
}
原理是 ImageIO.write 会重新编码图片,重新编码时不会保留原始 EXIF。代价是图片被重新压缩了一次——如果原图是高质量 JPEG,重新编码可能损失画质。
不想损失画质的方案是用第三方库(如 Apache Commons Imaging 或 metadata-extractor)直接读写 EXIF 段,只删除敏感字段,不动图片数据本身。
自动旋转:根据 Orientation 纠正方向
手机竖拍的照片,EXIF Orientation 可能标记为 6(顺时针 90 度)或 8(逆时针 90 度)。剥离 EXIF 之后,后续如果新的图片查看器不知道方向信息,照片的方向会变成竖向存储,显示不出来正确方向——所以剥离前先按 Orientation 旋转:
public BufferedImage autoRotate(BufferedImage image, int orientation) {
return switch (orientation) {
case 6 -> rotate(image, 90); // 顺时针 90°
case 3 -> rotate(image, 180); // 旋转 180°
case 8 -> rotate(image, 270); // 逆时针 90°
default -> image; // 1 = 正常,不需要旋转
};
}
private BufferedImage rotate(BufferedImage src, int degrees) {
int w = src.getWidth(), h = src.getHeight();
int newW = (degrees == 90 || degrees == 270) ? h : w;
int newH = (degrees == 90 || degrees == 270) ? w : h;
BufferedImage dest = new BufferedImage(newW, newH, src.getType());
Graphics2D g = dest.createGraphics();
g.translate((newW - w) / 2.0, (newH - h) / 2.0);
g.rotate(Math.toRadians(degrees), w / 2.0, h / 2.0);
g.drawImage(src, 0, 0, null);
g.dispose();
return dest;
}
旋转完后再剥离 EXIF,图片方向就"固化"到了像素里——换任何看图软件打开都是正确的方向。
完整的处理流程
用户上传图片
├─ 1. 提取 EXIF Metadata
│ ├─ GPS 坐标 → 记录日志(可选,用于审计)
│ ├─ Orientation → 决定是否旋转
│ └─ 其他字段 → 后续要清理
│
├─ 2. 根据 Orientation 旋转图片(固化方向)
│
└─ 3. 剥离 EXIF(输出干净图片)
└─ 存储到 OSS / 本地
用到 metadata-extractor 提取 EXIF:
Metadata metadata = ImageMetadataReader.readMetadata(new ByteArrayInputStream(bytes));
ExifIFD0Directory exif = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
// 提取 Orientation
int orientation = exif != null ? exif.getInt(ExifIFD0Directory.TAG_ORIENTATION) : 1;
// 提取 GPS(记录日志后删掉)
GpsDirectory gps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
if (gps != null && gps.getGeoLocation() != null) {
log.info("图片包含 GPS: {}, 已剥离", gps.getGeoLocation());
}
需要注意的点
缩略图也有 EXIF。JPEG 文件里可能包含一个嵌入的缩略图,缩略图也有独立的 EXIF 数据。用 metadata-extractor 可以检测并清理。
PNG 也有元数据。PNG 的元数据不在 EXIF 段,而在 tEXt / iTXt / zTXt 块里。不过 GPS 信息通常不会出现在 PNG 里(手机拍的照片是 JPEG),所以这个风险较低。
画质损失可控。重新编码 JPEG 会损失画质,但如果质量设为 0.92~0.95,肉眼几乎看不出差异。如果追求无损,用 Apache Commons Imaging 直接操作 EXIF 段。
总结
图片上传 EXIF 泄露的三件事:
- 提取 Metadata —— metadata-extractor 读取 GPS、Orientation、设备信息
- 自动旋转 —— 根据 Orientation 旋转图片,方向固化进像素
- 剥离 EXIF —— 重新编码输出,敏感信息全部消失
用户上传一张照片,后端自动做这三步,返回一张方位正确、隐私干净的图片。成本和风险都很低,但能避免一次严重的隐私泄露事故。
