物联网设备认证服务
# 物联网设备认证服务
# 概述
设备认证服务是物联网安全体系的核心组件,负责验证设备身份、管理访问权限、防范安全威胁。本文档详细介绍设备认证服务的设计原理、实现方案和安全机制。
# 认证架构
设备认证服务
├── 身份认证
│ ├── 设备证书认证
│ ├── 密钥签名认证
│ └── 动态令牌认证
├── 权限管理
│ ├── 访问控制列表
│ ├── 角色权限管理
│ └── 资源授权
├── 安全防护
│ ├── 防重放攻击
│ ├── 防暴力破解
│ └── 异常检测
└── 会话管理
├── 令牌生成
├── 会话维持
└── 令牌刷新
# 认证方式
# 1. 基于签名的认证
@Service
public class SignatureAuthenticationService {
@Autowired
private DeviceManagementService deviceManagementService;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 签名认证
*/
public AuthenticationResult authenticateBySignature(SignatureAuthRequest request) {
String deviceId = request.getDeviceId();
String signature = request.getSignature();
String timestamp = request.getTimestamp();
String nonce = request.getNonce();
try {
// 1. 基础参数验证
validateBasicParams(deviceId, signature, timestamp, nonce);
// 2. 时间戳验证(防重放攻击)
validateTimestamp(timestamp);
// 3. 随机数验证(防重放攻击)
validateNonce(deviceId, nonce);
// 4. 获取设备信息
IoTDevice device = getAndValidateDevice(deviceId);
// 5. 签名验证
validateSignature(device, signature, timestamp, nonce);
// 6. 生成访问令牌
String accessToken = generateAccessToken(deviceId);
// 7. 缓存认证信息
cacheAuthenticationInfo(deviceId, accessToken);
// 8. 记录认证日志
recordAuthenticationLog(deviceId, true, "签名认证成功");
return AuthenticationResult.success(accessToken, "认证成功");
} catch (AuthenticationException e) {
recordAuthenticationLog(deviceId, false, e.getMessage());
return AuthenticationResult.failure(e.getMessage());
}
}
/**
* 验证基础参数
*/
private void validateBasicParams(String deviceId, String signature, String timestamp, String nonce) {
if (StringUtils.isAnyBlank(deviceId, signature, timestamp, nonce)) {
throw new AuthenticationException("认证参数不完整");
}
if (deviceId.length() > 100 || signature.length() > 256) {
throw new AuthenticationException("参数长度超限");
}
}
/**
* 验证时间戳
*/
private void validateTimestamp(String timestamp) {
try {
long ts = Long.parseLong(timestamp);
long currentTime = System.currentTimeMillis();
long diff = Math.abs(currentTime - ts);
// 允许5分钟的时间偏差
if (diff > 5 * 60 * 1000) {
throw new AuthenticationException("时间戳无效");
}
} catch (NumberFormatException e) {
throw new AuthenticationException("时间戳格式错误");
}
}
/**
* 验证随机数(防重放攻击)
*/
private void validateNonce(String deviceId, String nonce) {
String nonceKey = "auth:nonce:" + deviceId + ":" + nonce;
// 检查随机数是否已使用
if (redisTemplate.hasKey(nonceKey)) {
throw new AuthenticationException("随机数已使用");
}
// 缓存随机数,防止重复使用
redisTemplate.opsForValue().set(nonceKey, "used", Duration.ofMinutes(10));
}
/**
* 获取并验证设备
*/
private IoTDevice getAndValidateDevice(String deviceId) {
IoTDevice device = deviceManagementService.getDeviceById(deviceId);
if (device == null) {
throw new AuthenticationException("设备不存在");
}
if (device.getStatus() == DeviceStatus.DISABLED) {
throw new AuthenticationException("设备已被禁用");
}
if (device.getStatus() == DeviceStatus.INACTIVE) {
throw new AuthenticationException("设备未激活");
}
return device;
}
/**
* 验证签名
*/
private void validateSignature(IoTDevice device, String signature, String timestamp, String nonce) {
String expectedSignature = calculateSignature(
device.getDeviceId(),
device.getDeviceSecret(),
timestamp,
nonce
);
if (!signature.equals(expectedSignature)) {
throw new AuthenticationException("签名验证失败");
}
}
/**
* 计算签名
*/
private String calculateSignature(String deviceId, String deviceSecret, String timestamp, String nonce) {
// 签名算法: SHA256(deviceId + deviceSecret + timestamp + nonce)
String data = deviceId + deviceSecret + timestamp + nonce;
return DigestUtils.sha256Hex(data);
}
}
# 2. 基于证书的认证
@Service
public class CertificateAuthenticationService {
@Autowired
private DeviceCertificateRepository certificateRepository;
@Autowired
private CertificateValidator certificateValidator;
/**
* 证书认证
*/
public AuthenticationResult authenticateByCertificate(CertificateAuthRequest request) {
String deviceId = request.getDeviceId();
String certificateContent = request.getCertificate();
try {
// 1. 解析证书
X509Certificate certificate = parseCertificate(certificateContent);
// 2. 验证证书有效性
validateCertificate(certificate);
// 3. 验证证书与设备的绑定关系
validateDeviceCertificateBinding(deviceId, certificate);
// 4. 验证证书链
validateCertificateChain(certificate);
// 5. 生成访问令牌
String accessToken = generateAccessToken(deviceId);
// 6. 缓存认证信息
cacheAuthenticationInfo(deviceId, accessToken);
return AuthenticationResult.success(accessToken, "证书认证成功");
} catch (Exception e) {
log.error("证书认证失败, deviceId: {}", deviceId, e);
return AuthenticationResult.failure("证书认证失败: " + e.getMessage());
}
}
/**
* 解析证书
*/
private X509Certificate parseCertificate(String certificateContent) throws CertificateException {
byte[] certBytes = Base64.getDecoder().decode(certificateContent);
CertificateFactory factory = CertificateFactory.getInstance("X.509");
return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(certBytes));
}
/**
* 验证证书有效性
*/
private void validateCertificate(X509Certificate certificate) throws CertificateException {
// 检查证书是否过期
certificate.checkValidity();
// 验证证书签名
certificateValidator.validateSignature(certificate);
// 检查证书是否被吊销
if (certificateValidator.isRevoked(certificate)) {
throw new CertificateException("证书已被吊销");
}
}
/**
* 验证设备证书绑定关系
*/
private void validateDeviceCertificateBinding(String deviceId, X509Certificate certificate) {
DeviceCertificate deviceCert = certificateRepository.findByDeviceId(deviceId)
.orElseThrow(() -> new AuthenticationException("设备证书不存在"));
String certFingerprint = calculateCertificateFingerprint(certificate);
if (!certFingerprint.equals(deviceCert.getCertificateFingerprint())) {
throw new AuthenticationException("证书与设备不匹配");
}
}
/**
* 计算证书指纹
*/
private String calculateCertificateFingerprint(X509Certificate certificate) {
try {
byte[] encoded = certificate.getEncoded();
return DigestUtils.sha256Hex(encoded);
} catch (CertificateEncodingException e) {
throw new RuntimeException("计算证书指纹失败", e);
}
}
}
# 3. 动态令牌认证
@Service
public class TokenAuthenticationService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private JwtTokenProvider jwtTokenProvider;
/**
* 令牌认证
*/
public AuthenticationResult authenticateByToken(TokenAuthRequest request) {
String deviceId = request.getDeviceId();
String token = request.getToken();
try {
// 1. 验证令牌格式
validateTokenFormat(token);
// 2. 验证令牌有效性
Claims claims = jwtTokenProvider.validateToken(token);
// 3. 验证设备ID匹配
String tokenDeviceId = claims.getSubject();
if (!deviceId.equals(tokenDeviceId)) {
throw new AuthenticationException("设备ID不匹配");
}
// 4. 检查令牌是否在黑名单中
if (isTokenBlacklisted(token)) {
throw new AuthenticationException("令牌已失效");
}
// 5. 刷新令牌(如果需要)
String newToken = refreshTokenIfNeeded(token, claims);
return AuthenticationResult.success(newToken != null ? newToken : token, "令牌认证成功");
} catch (Exception e) {
log.error("令牌认证失败, deviceId: {}", deviceId, e);
return AuthenticationResult.failure("令牌认证失败: " + e.getMessage());
}
}
/**
* 验证令牌格式
*/
private void validateTokenFormat(String token) {
if (StringUtils.isBlank(token)) {
throw new AuthenticationException("令牌不能为空");
}
if (!token.startsWith("Bearer ")) {
throw new AuthenticationException("令牌格式错误");
}
}
/**
* 检查令牌黑名单
*/
private boolean isTokenBlacklisted(String token) {
String blacklistKey = "auth:blacklist:" + DigestUtils.sha256Hex(token);
return redisTemplate.hasKey(blacklistKey);
}
/**
* 刷新令牌(如果需要)
*/
private String refreshTokenIfNeeded(String token, Claims claims) {
Date expiration = claims.getExpiration();
Date now = new Date();
// 如果令牌在30分钟内过期,则刷新
long timeToExpire = expiration.getTime() - now.getTime();
if (timeToExpire < 30 * 60 * 1000) {
String deviceId = claims.getSubject();
return generateAccessToken(deviceId);
}
return null;
}
}
# 权限管理
# 访问控制服务
@Service
public class AccessControlService {
@Autowired
private DevicePermissionRepository permissionRepository;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 检查设备权限
*/
public boolean checkPermission(String deviceId, String resource, String action) {
// 1. 从缓存获取权限信息
Set<String> permissions = getCachedPermissions(deviceId);
if (permissions == null) {
// 2. 从数据库加载权限
permissions = loadDevicePermissions(deviceId);
// 3. 缓存权限信息
cachePermissions(deviceId, permissions);
}
// 4. 检查具体权限
String permission = resource + ":" + action;
return permissions.contains(permission) || permissions.contains(resource + ":*");
}
/**
* 获取缓存的权限信息
*/
@SuppressWarnings("unchecked")
private Set<String> getCachedPermissions(String deviceId) {
String cacheKey = "auth:permissions:" + deviceId;
return (Set<String>) redisTemplate.opsForValue().get(cacheKey);
}
/**
* 加载设备权限
*/
private Set<String> loadDevicePermissions(String deviceId) {
List<DevicePermission> permissions = permissionRepository.findByDeviceId(deviceId);
return permissions.stream()
.map(p -> p.getResource() + ":" + p.getAction())
.collect(Collectors.toSet());
}
/**
* 缓存权限信息
*/
private void cachePermissions(String deviceId, Set<String> permissions) {
String cacheKey = "auth:permissions:" + deviceId;
redisTemplate.opsForValue().set(cacheKey, permissions, Duration.ofHours(1));
}
/**
* 批量权限检查
*/
public Map<String, Boolean> batchCheckPermissions(String deviceId, List<PermissionRequest> requests) {
Set<String> permissions = getCachedPermissions(deviceId);
if (permissions == null) {
permissions = loadDevicePermissions(deviceId);
cachePermissions(deviceId, permissions);
}
Map<String, Boolean> results = new HashMap<>();
for (PermissionRequest request : requests) {
String permission = request.getResource() + ":" + request.getAction();
boolean hasPermission = permissions.contains(permission) ||
permissions.contains(request.getResource() + ":*");
results.put(permission, hasPermission);
}
return results;
}
}
# 安全防护
# 防暴力破解
@Component
public class BruteForceProtectionService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final int MAX_ATTEMPTS = 5;
private static final int LOCKOUT_DURATION_MINUTES = 30;
/**
* 检查是否被锁定
*/
public boolean isLocked(String deviceId) {
String lockKey = "auth:lock:" + deviceId;
return redisTemplate.hasKey(lockKey);
}
/**
* 记录认证失败
*/
public void recordFailedAttempt(String deviceId) {
String attemptKey = "auth:attempts:" + deviceId;
// 增加失败次数
Long attempts = redisTemplate.opsForValue().increment(attemptKey);
if (attempts == 1) {
// 设置过期时间
redisTemplate.expire(attemptKey, Duration.ofMinutes(LOCKOUT_DURATION_MINUTES));
}
// 如果失败次数达到阈值,锁定设备
if (attempts >= MAX_ATTEMPTS) {
lockDevice(deviceId);
}
}
/**
* 锁定设备
*/
private void lockDevice(String deviceId) {
String lockKey = "auth:lock:" + deviceId;
redisTemplate.opsForValue().set(lockKey, "locked", Duration.ofMinutes(LOCKOUT_DURATION_MINUTES));
log.warn("设备因多次认证失败被锁定, deviceId: {}", deviceId);
// 发送告警
sendBruteForceAlert(deviceId);
}
/**
* 清除失败记录
*/
public void clearFailedAttempts(String deviceId) {
String attemptKey = "auth:attempts:" + deviceId;
redisTemplate.delete(attemptKey);
}
/**
* 发送暴力破解告警
*/
private void sendBruteForceAlert(String deviceId) {
BruteForceAlert alert = new BruteForceAlert();
alert.setDeviceId(deviceId);
alert.setAttempts(MAX_ATTEMPTS);
alert.setLockoutTime(LocalDateTime.now());
alert.setAlertLevel(AlertLevel.HIGH);
// 发送到告警队列
rabbitTemplate.convertAndSend("iot.alert.exchange", "auth.bruteforce", alert);
}
}
# 异常检测
@Component
public class AuthenticationAnomalyDetector {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 检测认证异常
*/
public void detectAnomalies(String deviceId, String clientIp, String userAgent) {
// 1. 检测异常IP
detectAbnormalIP(deviceId, clientIp);
// 2. 检测异常用户代理
detectAbnormalUserAgent(deviceId, userAgent);
// 3. 检测异常时间
detectAbnormalTime(deviceId);
// 4. 检测频繁认证
detectFrequentAuthentication(deviceId);
}
/**
* 检测异常IP
*/
private void detectAbnormalIP(String deviceId, String clientIp) {
String ipHistoryKey = "auth:ip_history:" + deviceId;
Set<String> historicalIPs = redisTemplate.opsForSet().members(ipHistoryKey);
if (historicalIPs != null && !historicalIPs.isEmpty() && !historicalIPs.contains(clientIp)) {
// 新IP认证,发送告警
sendAnomalyAlert(deviceId, "异常IP认证", "设备从新IP地址认证: " + clientIp);
}
// 记录IP
redisTemplate.opsForSet().add(ipHistoryKey, clientIp);
redisTemplate.expire(ipHistoryKey, Duration.ofDays(30));
}
/**
* 检测频繁认证
*/
private void detectFrequentAuthentication(String deviceId) {
String frequencyKey = "auth:frequency:" + deviceId;
Long count = redisTemplate.opsForValue().increment(frequencyKey);
if (count == 1) {
redisTemplate.expire(frequencyKey, Duration.ofMinutes(5));
}
// 5分钟内认证超过10次视为异常
if (count > 10) {
sendAnomalyAlert(deviceId, "频繁认证", "5分钟内认证次数: " + count);
}
}
/**
* 发送异常告警
*/
private void sendAnomalyAlert(String deviceId, String anomalyType, String description) {
AuthenticationAnomalyAlert alert = new AuthenticationAnomalyAlert();
alert.setDeviceId(deviceId);
alert.setAnomalyType(anomalyType);
alert.setDescription(description);
alert.setDetectTime(LocalDateTime.now());
alert.setAlertLevel(AlertLevel.MEDIUM);
rabbitTemplate.convertAndSend("iot.alert.exchange", "auth.anomaly", alert);
}
}
# 会话管理
# JWT令牌提供者
@Component
public class JwtTokenProvider {
@Value("${iot.auth.jwt.secret}")
private String jwtSecret;
@Value("${iot.auth.jwt.expiration:3600000}") // 默认1小时
private long jwtExpiration;
/**
* 生成访问令牌
*/
public String generateAccessToken(String deviceId) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpiration);
return Jwts.builder()
.setSubject(deviceId)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
/**
* 生成刷新令牌
*/
public String generateRefreshToken(String deviceId) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpiration * 24); // 24小时
return Jwts.builder()
.setSubject(deviceId)
.setIssuedAt(now)
.setExpiration(expiryDate)
.claim("type", "refresh")
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
/**
* 验证令牌
*/
public Claims validateToken(String token) {
try {
return Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
} catch (JwtException | IllegalArgumentException e) {
throw new AuthenticationException("无效的令牌", e);
}
}
/**
* 从令牌获取设备ID
*/
public String getDeviceIdFromToken(String token) {
Claims claims = validateToken(token);
return claims.getSubject();
}
/**
* 检查令牌是否即将过期
*/
public boolean isTokenExpiringSoon(String token) {
try {
Claims claims = validateToken(token);
Date expiration = claims.getExpiration();
Date now = new Date();
// 如果在30分钟内过期,返回true
return (expiration.getTime() - now.getTime()) < 30 * 60 * 1000;
} catch (Exception e) {
return true;
}
}
}
# 数据库设计
# 设备权限表
CREATE TABLE `iot_device_permission` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`device_id` varchar(100) NOT NULL COMMENT '设备ID',
`resource` varchar(100) NOT NULL COMMENT '资源',
`action` varchar(50) NOT NULL COMMENT '操作',
`granted` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否授权',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_device_resource_action` (`device_id`, `resource`, `action`),
KEY `idx_device_id` (`device_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备权限表';
# 设备证书表
CREATE TABLE `iot_device_certificate` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`device_id` varchar(100) NOT NULL COMMENT '设备ID',
`certificate_content` text NOT NULL COMMENT '证书内容',
`certificate_fingerprint` varchar(64) NOT NULL COMMENT '证书指纹',
`issuer` varchar(500) NOT NULL COMMENT '颁发者',
`subject` varchar(500) NOT NULL COMMENT '主题',
`valid_from` datetime NOT NULL COMMENT '有效期开始',
`valid_to` datetime NOT NULL COMMENT '有效期结束',
`status` varchar(20) NOT NULL DEFAULT 'ACTIVE' COMMENT '状态',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_device_id` (`device_id`),
UNIQUE KEY `uk_certificate_fingerprint` (`certificate_fingerprint`),
KEY `idx_valid_to` (`valid_to`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备证书表';
# 认证日志表
CREATE TABLE `iot_authentication_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`device_id` varchar(100) NOT NULL COMMENT '设备ID',
`auth_type` varchar(20) NOT NULL COMMENT '认证类型',
`client_ip` varchar(50) DEFAULT NULL COMMENT '客户端IP',
`user_agent` varchar(500) DEFAULT NULL COMMENT '用户代理',
`success` tinyint(1) NOT NULL COMMENT '是否成功',
`failure_reason` varchar(500) DEFAULT NULL COMMENT '失败原因',
`auth_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '认证时间',
PRIMARY KEY (`id`),
KEY `idx_device_id_time` (`device_id`, `auth_time`),
KEY `idx_success` (`success`),
KEY `idx_auth_time` (`auth_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='认证日志表'
PARTITION BY RANGE (TO_DAYS(auth_time)) (
PARTITION p202401 VALUES LESS THAN (TO_DAYS('2024-02-01')),
PARTITION p202402 VALUES LESS THAN (TO_DAYS('2024-03-01')),
PARTITION p202403 VALUES LESS THAN (TO_DAYS('2024-04-01')),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
# 性能优化
# 认证缓存策略
@Component
public class AuthenticationCacheManager {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 缓存认证结果
*/
public void cacheAuthenticationResult(String deviceId, String token, long expireSeconds) {
String cacheKey = "auth:token:" + deviceId;
redisTemplate.opsForValue().set(cacheKey, token, Duration.ofSeconds(expireSeconds));
}
/**
* 获取缓存的认证结果
*/
public String getCachedToken(String deviceId) {
String cacheKey = "auth:token:" + deviceId;
return (String) redisTemplate.opsForValue().get(cacheKey);
}
/**
* 批量缓存设备权限
*/
public void batchCachePermissions(Map<String, Set<String>> devicePermissions) {
Map<String, Set<String>> cacheMap = devicePermissions.entrySet().stream()
.collect(Collectors.toMap(
entry -> "auth:permissions:" + entry.getKey(),
Map.Entry::getValue
));
// 使用Pipeline批量设置
redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
cacheMap.forEach((key, permissions) -> {
connection.set(key.getBytes(), SerializationUtils.serialize(permissions));
connection.expire(key.getBytes(), 3600); // 1小时过期
});
return null;
});
}
}
# 监控指标
# 认证监控
@Component
public class AuthenticationMetrics {
private final MeterRegistry meterRegistry;
private final Counter authSuccessCounter;
private final Counter authFailureCounter;
private final Timer authDurationTimer;
private final Gauge activeTokensGauge;
public AuthenticationMetrics(MeterRegistry meterRegistry, RedisTemplate<String, Object> redisTemplate) {
this.meterRegistry = meterRegistry;
this.authSuccessCounter = Counter.builder("iot.auth.success")
.description("认证成功次数")
.register(meterRegistry);
this.authFailureCounter = Counter.builder("iot.auth.failure")
.description("认证失败次数")
.register(meterRegistry);
this.authDurationTimer = Timer.builder("iot.auth.duration")
.description("认证耗时")
.register(meterRegistry);
this.activeTokensGauge = Gauge.builder("iot.auth.active_tokens")
.description("活跃令牌数量")
.register(meterRegistry, this, metrics -> {
Set<String> keys = redisTemplate.keys("auth:token:*");
return keys != null ? keys.size() : 0;
});
}
public void incrementAuthSuccess() {
authSuccessCounter.increment();
}
public void incrementAuthFailure() {
authFailureCounter.increment();
}
public Timer.Sample startAuthTimer() {
return Timer.start(meterRegistry);
}
}
# 总结
设备认证服务是物联网安全的第一道防线,通过多种认证方式、完善的权限管理、强大的安全防护机制,确保只有合法的设备能够接入系统。合理的缓存策略和监控机制保证了认证服务的高性能和高可用性。