设备和token

This commit is contained in:
liulu 2024-12-06 15:38:22 +08:00
parent bcf8dfb5f3
commit 04cfab2f82
25 changed files with 588 additions and 25 deletions

View File

@ -1,4 +1,4 @@
package com.sunyard.ssp.common.constant;
package com.sunyard.chsm.constant;
/**
* @author Exrickx

View File

@ -14,7 +14,7 @@ import java.util.Objects;
@AllArgsConstructor
public enum ManufacturerModelEnum {
enc001(ManufacturerEnum.SUNYARD, "enc001", "服务器密码机enc001"),
enc001(ManufacturerEnum.SUNYARD, "SYD-001", "服务器密码机"),
;
private final ManufacturerEnum manufacturer;

View File

@ -1,8 +1,13 @@
package com.sunyard.chsm.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sunyard.chsm.model.entity.Application;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import java.util.List;
/**
* @author liulu
@ -10,4 +15,15 @@ import org.apache.ibatis.annotations.Mapper;
*/
@Mapper
public interface ApplicationMapper extends BaseMapper<Application> {
default Application selectByAppKey(String appKey) {
List<Application> apps = selectList(new QueryWrapper<Application>().eq("app_key", appKey));
if (CollectionUtils.isEmpty(apps)) {
return null;
}
Assert.isTrue(apps.size() == 1, "app 数据异常");
return apps.iterator().next();
}
}

View File

@ -72,7 +72,7 @@ public class R<T> {
R<T> r = new R<>();
r.setSuccess(false);
r.setMessage(msg);
r.setCode(500);
r.setCode(400);
return r;
}

View File

@ -21,16 +21,20 @@ public class Device {
private Integer managePort;
private String manufacturer;
private String manufacturerModel;
private Integer encKeyIdx;
private String accessCredentials;
private Boolean connected;
private LocalDateTime lastCheckTime;
private LocalDateTime lastConnectedTime;
private Long groupId;
private String groupName;
private Integer weight;
private String tmkStatus;
private String deviceSerial;
private String pubKey;
private String encTmk;
private Boolean connected;
private LocalDateTime lastCheckTime;
private LocalDateTime lastConnectedTime;
private String remark;
private LocalDateTime createTime;
private LocalDateTime updateTime;

View File

@ -0,0 +1,46 @@
package com.sunyard.chsm.utils;
import org.bouncycastle.util.encoders.Hex;
import java.util.Base64;
/**
* @author liulu
* @since 2024/12/6
*/
public abstract class CodecUtils {
public static String encodeBase64(byte[] data) {
return Base64.getEncoder().encodeToString(data);
}
public static byte[] decodeBase64(String str) {
try {
return Base64.getDecoder().decode(str);
} catch (Exception var3) {
if (str.length() > 32) {
str = str.substring(0, 32) + "...";
}
String format = String.format("参数[%s]不是正确的base64格式", str);
throw new IllegalArgumentException(format);
}
}
public static String encodeHex(byte[] data) {
return Hex.toHexString(data);
}
public static byte[] decodeHex(String str) {
try {
return Hex.decode(str);
} catch (Exception var3) {
if (str.length() > 32) {
str = str.substring(0, 32) + "...";
}
String format = String.format("参数[%s]不是正确的hex格式", str);
throw new IllegalArgumentException(format);
}
}
}

View File

@ -16,5 +16,13 @@
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,33 @@
package com.sunyard.chsm.param;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
* @author liulu
* @since 2024/11/13
*/
@Data
public class AppTokenReq {
/**
* 平台分配的appKey
*/
@NotBlank
@Size(min = 16, max = 64, message = "appKey长度必须为16-64")
private String appKey;
/**
* 时间戳毫秒数与服务器时间不能超过5分钟
*/
@NotNull
private Long random;
/**
* 使用appKey+random+appSecret作为原文使用appSecret作为key根据HmacSM3算法计算得到hmac值,转为Hex编码
*/
@NotBlank
@Size(min = 32, max = 64, message = "hmac长度在60-64之间")
private String hmac;
}

View File

@ -0,0 +1,15 @@
package com.sunyard.chsm.param;
import lombok.Data;
/**
* @author liulu
* @since 2024/11/13
*/
@Data
public class AppTokenResp {
/**
* 获取的token的值
*/
private String token;
}

View File

@ -73,6 +73,7 @@ public abstract class DeviceDTO {
* 管理端口
*/
private Integer managePort;
private Integer encKeyIdx;
/**
* 访问凭证
*/

View File

@ -2,7 +2,7 @@ package com.sunyard.config.security.jwt;
import cn.hutool.core.util.StrUtil;
import com.google.gson.Gson;
import com.sunyard.ssp.common.constant.SecurityConstant;
import com.sunyard.chsm.constant.SecurityConstant;
import com.sunyard.ssp.modules.user.entity.ScUser;
import com.sunyard.ssp.modules.user.service.IScUserService;
import com.sunyard.ssp.utils.ResponseUtil;

View File

@ -3,7 +3,7 @@ package com.sunyard.config.security.jwt;
import cn.hutool.core.util.StrUtil;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.sunyard.ssp.common.constant.SecurityConstant;
import com.sunyard.chsm.constant.SecurityConstant;
import com.sunyard.ssp.utils.ResponseUtil;
import com.sunyard.ssp.vo.TokenUser;
import io.jsonwebtoken.Claims;

View File

@ -5,10 +5,10 @@ import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.sunyard.chsm.constant.SecurityConstant;
import com.sunyard.ssp.common.PageVo;
import com.sunyard.ssp.common.Result;
import com.sunyard.ssp.common.constant.CommonConstant;
import com.sunyard.ssp.common.constant.SecurityConstant;
import com.sunyard.ssp.common.enums.UserHeaderTypeEnum;
import com.sunyard.ssp.modules.user.entity.ScDepartment;
import com.sunyard.ssp.modules.user.entity.ScDepartmentHeader;

View File

@ -2,8 +2,8 @@ package com.sunyard.ssp.utils;
import cn.hutool.core.util.StrUtil;
import com.google.gson.Gson;
import com.sunyard.chsm.constant.SecurityConstant;
import com.sunyard.ssp.common.constant.CommonConstant;
import com.sunyard.ssp.common.constant.SecurityConstant;
import com.sunyard.ssp.modules.user.entity.ScPermission;
import com.sunyard.ssp.modules.user.entity.ScRole;
import com.sunyard.ssp.modules.user.entity.ScUser;

View File

@ -49,6 +49,13 @@
<groupId>com.dm</groupId>
<artifactId>DmJdbcDriver</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>

View File

@ -0,0 +1,111 @@
package com.sunyard.chsm.auth;
import com.sunyard.chsm.model.R;
import com.sunyard.chsm.service.AppLoginService;
import com.sunyard.chsm.utils.JsonUtils;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
/**
* @author liulu
* @version V1.0
* @since 2023/8/4
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class AppTokenFilter extends OncePerRequestFilter {
public static final Collection<String> WHITE_URL = Arrays.asList(
"/appUser/getAppToken",
"/appUser/getAppTokenTest"
);
private final AppLoginService appLoginService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String requestURI = request.getRequestURI();
if (WHITE_URL.contains(requestURI)) {
filterChain.doFilter(request, response);
return;
}
String tokenValue = extractToken(request);
if (ObjectUtils.isEmpty(tokenValue)) {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getOutputStream().write(JsonUtils.toJsonBytes(R.error("请先获取token后再访问")));
return;
}
try {
AppUser user = appLoginService.verifyToken(tokenValue);
request.setAttribute("APP_USER", user);
filterChain.doFilter(request, response);
} catch (ExpiredJwtException ex) {
log.warn("token已过期: {}", requestURI);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getOutputStream().write(JsonUtils.toJsonBytes(R.error("token已过期")));
} catch (JwtException ex) {
log.warn("token格式错误: {}", requestURI);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getOutputStream().write(JsonUtils.toJsonBytes(R.error("token格式错误")));
} catch (Exception ex) {
log.error("未知异常: {}", requestURI, ex);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getOutputStream().write(JsonUtils.toJsonBytes(R.error("发生异常")));
}
}
protected String extractToken(HttpServletRequest request) {
// first check the header...
String token = extractHeaderToken(request);
// bearer type allows a request parameter as well
if (token == null) {
logger.debug("Token not found in headers. Trying request parameters.");
token = request.getParameter("access_token");
if (token == null) {
logger.debug("Token not found in request parameters. Not an OAuth2 request.");
}
}
return token;
}
private static final String BEARER_TYPE = "Bearer";
private String extractHeaderToken(HttpServletRequest request) {
Enumeration<String> headers = request.getHeaders("Authorization");
while (headers.hasMoreElements()) { // typically there is only one (most servers enforce that)
String value = headers.nextElement();
if (value.toLowerCase().startsWith(BEARER_TYPE.toLowerCase())) {
String authHeaderValue = value.substring(BEARER_TYPE.length()).trim();
int commaIndex = authHeaderValue.indexOf(',');
if (commaIndex > 0) {
authHeaderValue = authHeaderValue.substring(0, commaIndex);
}
return authHeaderValue;
}
}
return null;
}
}

View File

@ -0,0 +1,19 @@
package com.sunyard.chsm.auth;
import lombok.Data;
import java.util.List;
/**
* @author liulu
* @since 2024/12/6
*/
@Data
public class AppUser {
private Long appId;
private String name;
private List<Long> serviceIds;
}

View File

@ -1,4 +1,4 @@
package com.sunyard.chsm.config;
package com.sunyard.chsm.auth;
import org.springframework.web.servlet.HandlerInterceptor;

View File

@ -0,0 +1,19 @@
package com.sunyard.chsm.auth;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
/**
* @author liulu
* @since 2024/12/6
*/
public class UserContext {
public static AppUser getCurrentUser() {
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
return (AppUser) requestAttributes.getAttribute("APP_USER", RequestAttributes.SCOPE_REQUEST);
}
}

View File

@ -0,0 +1,96 @@
package com.sunyard.chsm.config;
import com.sunyard.chsm.model.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.IllegalFormatException;
import java.util.List;
import java.util.Optional;
/**
* 全局controller异常处理
*
* @auther: ZS
* @description:
* @param:
* @return:
* @date: 2020/5/25 16:24
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionResolver {
/**
* 校验器
*/
@ExceptionHandler(BindException.class)
@ResponseBody
public R<?> handleBindException(BindException e) {
log.info("校验器异常", e);
FieldError fieldError = e.getBindingResult().getFieldError();
return R.error(400, fieldError.getDefaultMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public R<?> validExceptionHandler(MethodArgumentNotValidException ex) {
return Optional.of(ex)
.map(MethodArgumentNotValidException::getFieldError)
.map(FieldError::getDefaultMessage)
.map(message -> R.error(400, message))
.get();
}
private static final List<Class<? extends Exception>> EX_CLASS = Arrays.asList(
IllegalArgumentException.class, IllegalStateException.class,
IllegalFormatException.class,
UnsupportedOperationException.class);
@ExceptionHandler(Exception.class)
public R<?> exceptionHandler(Exception ex, HttpServletRequest request) {
String errorMessage = buildErrorMessage(ex);
for (Class<? extends Exception> eClass : EX_CLASS) {
Exception exception = findException(eClass, ex);
if (exception == null) {
continue;
}
log.warn("Validation failed -> {} ", errorMessage);
return R.error(400, exception.getMessage());
}
log.error("request: {} - {}, 系统异常 -> ", request.getMethod(), request.getRequestURI(), ex);
return R.error("系统异常");
}
public static <T extends Exception> T findException(Class<T> exClass, Throwable ex) {
for (int i = 0; ex != null && i < 3; i++) {
if (exClass.isAssignableFrom(ex.getClass())) {
//noinspection unchecked
return (T) ex;
}
ex = ex.getCause();
}
return null;
}
public static String buildErrorMessage(Throwable ex) {
StringBuilder sb = new StringBuilder();
sb.append(ex.getMessage());
StackTraceElement[] stackTrace = ex.getStackTrace();
int i = 0;
for (StackTraceElement stackTraceElement : stackTrace) {
if (i++ >= 5) {
break;
}
sb.append("\n\t").append(stackTraceElement.toString());
}
return sb.toString();
}
}

View File

@ -1,17 +1,49 @@
package com.sunyard.chsm.controller;
import com.sunyard.chsm.param.AppTokenReq;
import com.sunyard.chsm.param.AppTokenResp;
import com.sunyard.chsm.service.AppLoginService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
/**
* 应用Token服务接口
*
* @author liulu
* @version V1.0
* @since 2023/8/4
* @since 2024/12/4
*/
@RestController
@RequestMapping
public class AppLoginController {
@Resource
private AppLoginService appLoginService;
/**
* 获取应用Token接口
* 密码服务平台为各个接入应用提供生成应用授权Token,
* 接入应用需在密码服务平台获取了到平台分配的appKey和appSecret
*
* @param appTokenReq 请求参数
* @return
*/
@PostMapping("/appUser/getAppToken")
public AppTokenResp getAppToken(@Valid @RequestBody AppTokenReq appTokenReq) {
return appLoginService.getAppToken(appTokenReq);
}
@PostMapping("/appUser/getAppTokenTest")
public AppTokenResp getAppTokenForTest(@Valid @RequestBody AppTokenReq appTokenReq) {
return appLoginService.getAppTokenForTest(appTokenReq);
}
}

View File

@ -0,0 +1,39 @@
package com.sunyard.chsm.controller;
import com.sunyard.chsm.model.R;
import com.sunyard.chsm.model.dto.KeyInfoDTO;
import com.sunyard.chsm.service.KeyInfoService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 密钥管理类接口
*
* @author liulu
* @since 2024/12/6
*/
@RestController
@RequestMapping("/key/manage")
public class KeyManageController {
@Resource
private KeyInfoService keyInfoService;
/**
* 启用密钥
*
* @param param 密钥id
* @return id
*/
@PostMapping("/enable")
public R<Void> enableKey(@Validated @RequestBody KeyInfoDTO.IDs param) {
keyInfoService.enableKey(param.getIds());
return R.ok();
}
}

View File

@ -0,0 +1,113 @@
package com.sunyard.chsm.service;
import com.sunyard.chsm.auth.AppUser;
import com.sunyard.chsm.constant.SecurityConstant;
import com.sunyard.chsm.enums.EnableStatus;
import com.sunyard.chsm.mapper.ApplicationMapper;
import com.sunyard.chsm.model.entity.Application;
import com.sunyard.chsm.param.AppTokenReq;
import com.sunyard.chsm.param.AppTokenResp;
import com.sunyard.chsm.utils.CodecUtils;
import com.sunyard.chsm.utils.gm.BCSM3Utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
/**
* @author liulu
* @since 2024/12/5
*/
@Slf4j
@Service
public class AppLoginService {
/**
* token 过期时间, 分钟
*/
@Value("${chsm.token.expireTime:720}")
private Integer tokenExpireTime;
@Resource
private ApplicationMapper applicationMapper;
public AppTokenResp getAppToken(AppTokenReq req) {
Long random = req.getRandom();
long now = System.currentTimeMillis();
Assert.isTrue(now - random <= 5 * 60 * 1000, "请求已超时");
String appKey = req.getAppKey();
Application application = applicationMapper.selectByAppKey(appKey);
Assert.isTrue(EnableStatus.ENABLED.getCode().equals(application.getStatus()), "此应用已停用");
String data = appKey + random + application.getAppSecret();
byte[] hmac = BCSM3Utils.hmac(application.getAppSecret().getBytes(), data.getBytes());
String serverHmac = CodecUtils.encodeHex(hmac);
if (!Objects.equals(req.getHmac(), serverHmac)) {
log.warn("appKey: {}, req hmac: {}, server hmac: {}", appKey, req.getHmac(), serverHmac);
throw new IllegalArgumentException("应用认证失败");
}
AppTokenResp resp = new AppTokenResp();
resp.setToken(genToken(application));
return resp;
}
private String genToken(Application app) {
Map<String, Object> claims = new HashMap<>();
claims.put("appId", app.getId());
claims.put("name", app.getName());
claims.put("serviceIds", Collections.singletonList(app.getId()));
Date now = new Date();
return Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + tokenExpireTime * 60 * 1000))
.setId(UUID.randomUUID().toString())
.signWith(SignatureAlgorithm.HS512, SecurityConstant.JWT_SIGN_KEY)
.compact();
}
public AppTokenResp getAppTokenForTest(@Valid AppTokenReq req) {
// Long random = req.getRandom();
// long now = System.currentTimeMillis();
// Assert.isTrue(now - random <= 5 * 60 * 1000, "请求已超时");
Application application = applicationMapper.selectByAppKey(req.getAppKey());
Assert.isTrue(EnableStatus.ENABLED.getCode().equals(application.getStatus()), "此应用已停用");
if (!Objects.equals(req.getHmac(), application.getAppSecret())) {
log.warn("appKey: {}, req hmac: {},", req.getAppKey(), req.getHmac());
throw new IllegalArgumentException("应用认证失败");
}
AppTokenResp resp = new AppTokenResp();
resp.setToken(genToken(application));
return resp;
}
public AppUser verifyToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(SecurityConstant.JWT_SIGN_KEY)
.parseClaimsJws(token)
.getBody();
AppUser user = new AppUser();
user.setAppId(claims.get("appId", Long.class));
user.setName(claims.get("name", String.class));
user.setServiceIds(claims.get("serviceIds", List.class));
return user;
}
}

View File

@ -12,7 +12,7 @@ spring:
# 数据源
datasource:
driverClassName: dm.jdbc.driver.DmDriver
url: jdbc:dm://172.16.18.44:5236?schema=SSP&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=UTF-8
url: jdbc:dm://172.16.18.44:5236?schema=SUNYARD_SSP&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=UTF-8
username: SUNYARD
# Jasypt加密 可到common-utils中找到JasyptUtil加解密工具类生成加密结果 格式为ENC(加密结果)
password: 123456

View File

@ -344,22 +344,26 @@ INSERT INTO SC_ROLE_PERMISSION (PERMISSION_ID, ROLE_ID) VALUES (189, 1);
-- 密码设备
CREATE TABLE sp_device (
id BIGINT NOT NULL COMMENT 'id',
name VARCHAR(255) COMMENT '名称',
device_number VARCHAR(255) COMMENT '编号',
manufacturer VARCHAR(255) COMMENT '制造厂商',
manufacturer_model VARCHAR(255) COMMENT '制造厂商型号',
service_ip VARCHAR(30) COMMENT '服务ip',
name VARCHAR(255) DEFAULT '' COMMENT '名称',
manufacturer VARCHAR(255) DEFAULT '' COMMENT '制造厂商',
manufacturer_model VARCHAR(255) DEFAULT '' COMMENT '制造厂商型号',
service_ip VARCHAR(30) DEFAULT '' COMMENT '服务ip',
service_port INT COMMENT '服务端口',
manage_ip VARCHAR(30) COMMENT '管理ip',
manage_ip VARCHAR(30) DEFAULT '' COMMENT '管理ip',
manage_port INT COMMENT '管理端口',
access_credentials VARCHAR(1000) COMMENT '访问凭证',
connected TINYINT NOT NULL DEFAULT 0,
last_connected_time TIMESTAMP ,
last_check_time TIMESTAMP ,
enc_key_idx INT COMMENT '加密密钥索引',
access_credentials VARCHAR(255) DEFAULT '' COMMENT '访问凭证',
status VARCHAR(25) DEFAULT '' COMMENT '设备状态',
group_id BIGINT NOT NULL DEFAULT 0 COMMENT '设备组id',
group_name VARCHAR(255) NOT NULL DEFAULT '' COMMENT '设备组名称',
weight INT DEFAULT 1 COMMENT '负载时权重',
tmk_status VARCHAR(25) DEFAULT '' COMMENT 'tmk状态',
device_serial VARCHAR(25) NOT NULL DEFAULT '' COMMENT '设备序列号',
pub_key VARCHAR(400) NOT NULL DEFAULT '' COMMENT '设备公钥',
enc_tmk VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'tmk密文',
connected TINYINT NOT NULL DEFAULT 0,
last_connected_time TIMESTAMP ,
last_check_time TIMESTAMP ,
remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),