EMQX配置ssl/tls双向认证+EMQX http客户端设备认证(Java实现)_真实业务实践 ...

打印 上一主题 下一主题

主题 528|帖子 528|积分 1584

一.利用docker搭建Emqx

1.拉取emqx镜像
  1. docker pull emqx/emqx:5.7
复制代码
2.运行
  1. docker run -d --name emqx  emqx/emqx:5.7
复制代码
3.拷贝 docker中 etc data log 到宿主机的 /opt/emqx 下
  1. mkdir -p /opt/emqx
  2. docker cp emqx:/opt/emqx/etc /opt/emqx
  3. docker cp emqx:/opt/emqx/log /opt/emqx
  4. docker cp emqx:/opt/emqx/data /opt/emqx
复制代码
4.重新摆设
  1. docker rm -f emqx
  2. ## 授权目录
  3. chmod -R 777 /opt/emqx/data /opt/emqx/log /opt/emqx/etc
  4. docker run -d  --memory 2G --read-only --name emqx  -v /opt/emqx/data:/opt/emqx/data -v /opt/emqx/etc:/opt/emqx/etc -v /opt/emqx/log:/opt/emqx/log  -p 1883:1883 -p 8083:8083 -p 8084:8084 -p 8883:8883 -p 18083:18083 emqx/emqx:5.7
复制代码
5.打开 1883(TCP)、8083、8084、8883(SSL)、18083 服务器安全组(端口);欣赏器输入IP:18083   默认账号密码:admin   public


二.配置ssl/tls双向认证

1.生成自签名的CA key和证书
  1. cd /opt/emqx/etc/certs
  2. openssl genrsa -out ca.key 2048
  3. openssl req -x509 -new -nodes -key ca.key -sha256 -days 36500 -out ca.pem -subj "/C=CN/ST=ZheJiang/L=HangZhou/O=HY/CN=SelfCA"
复制代码
2.生成服务器端的key和证书
  1. openssl genrsa -out emqx.key 2048
  2. openssl req -new -key ./emqx.key -config /etc/ssl/openssl.cnf -out emqx.csr
复制代码
注意这里因为 openssl.cnf 目次差别可能报错

 执行以下指令 openssl version -a 获取 openssl.cnf 目次 (如果没有则需要安装 openssl)

 修改一下 openssl.cnf 目次继承进行下一步
  1. openssl req -new -key ./emqx.key -config /etc/pki/tls/openssl.cnf -out emqx.csr
复制代码
  1. ## 注意 openssl.cnf 目录地址<br>openssl x509 -req -in ./emqx.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out emqx.pem -days 36500 -sha256 -extensions v3_req -extfile /etc/pki/tls/openssl.cnf
复制代码
3.生成客户端key和证书
  1. openssl genrsa -out client.key 2048
  2. openssl req -new -key client.key -out client.csr -subj "/C=CN/ST=ZheJiang/L=HangZhou/O=HY/CN=client"
  3. openssl x509 -req -days 36500 -in client.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out client.pem
复制代码
4.修改配置文件

通过以上三步生成了以下九个文件

 修改 vim /opt/emqx/etc/emqx.conf 文件:在末尾 参加以下配置
  1. listeners.tcp.default {
  2.   bind = "0.0.0.0:1883"
  3.   max_connections = 512000
  4. }
  5. listeners.ssl.default {
  6.   bind = "0.0.0.0:8883"
  7.   max_connections = 512000
  8.   ssl_options {
  9.     keyfile = "/opt/emqx/etc/certs/emqx.key"
  10.     certfile = "/opt/emqx/etc/certs/emqx.pem"
  11.     cacertfile = "/opt/emqx/etc/certs/ca.pem"
  12.     ## password = "123456"
  13.     verify = verify_peer
  14.     fail_if_no_peer_cert = true
  15.   }
  16. }
复制代码
5.重启emqx
  1. docker restart emqx
复制代码
6.测试工具毗连

下载 MQTTX桌面毗连工具:https://mqttx.app/zh/downloads ;双向认证需要 ca 证书、客户端证书、客户端证书key,如下图所示

 没有配置客户端认证,以是密码可以为空

 到此,MQTT搭建、双向认证已经完成!
三.HTTP客户端认证

1.EMQX认证简介

EMQX认证:MQTT每次毗连时会先走这里进行一个认证过程,EMQX提供了Password-Base、JWT、SCRAM三种认证方式;此中Password-Base又提供了 内置数据库、MySQL、MongoDB、PostgreSQL、Redis、LDAP、HTTP等多种认证过程,前几种都是基于数据库的认证,通过毗连时去执行对应的SQL来判断登录用户有没有在数据库中存在来进行简单的认证。HTTP 则是通过毗连时调用自定义的HTTP/HTTPS 接口来实现认证,利用HTTP认证可以更灵活的根据自己的业务流程来进行更复杂的认证。
2.具体认证流程

2.1.前置条件


 2.2.规约 参照阿里云IOT平台   https://help.aliyun.com/zh/iot/user-guide/establish-mqtt-connections-over-tcp?spm=a2c4g.11186623.0.0.6212282cO9B7oJ

2.2.1假设:

clientId = 666666,productkey=a1PzPc1bRRN,deviceName = 5D3B393432C3, timestamp=1719309618,signmethod=hmacsha1
 毗连时MQTT的三个入参:
clientid666666|signmethod=hmacsha1,timestamp=1719309618|
username5D3B393432C3&a1PzPc1bRRN
usernamehmacSha1
(clientid666666devicename5D3B393432C3productkeya1PzPc1bRRNtimestamp1719309618).toHexString 

 
 
 
  
2.2.2描述:

clientid:利用设备clientid + 签名方法 + 时间戳组成
username:设备DN + & + 产品PK组成
password:  clientid666666devicename5D3B393432C3productkeya1PzPc1bRRNtimestamp1719309618 字符串利用设备的 deviceSecret 为秘钥进行hmacSha1加密,然后转16进制字符串
2.3服务端收到MQTT毗连信息:

根据 username 信息获取到设备PK和DN ---> 到数据库查询秘钥信息 ----> 根据 clientid参数获取clientid、时间戳、签名方法  ---->  利用从数据库获取的秘钥对参数进行加密得到新得password ---->  比对两个密码是否相同 ---> 完成验证设备毗连到MQTT。
3.控制台配置





 4.HTTP认证代码示例
  1. /**
  2. * @description:  MQTT认证接口
  3. * @author: zhouhong
  4. * @date: 2024-06-20 18:20
  5. */
  6. @Log4j2
  7. @RestController
  8. @RequestMapping("/mqtt")
  9. public class MqttAuthController {
  10.     @Resource
  11.     private IotDeviceMapper iotDeviceMapper;
  12.     @RequestMapping("/check")
  13.     public MqttAuthRes check(@RequestBody MqttAuthParam mqttAuthParam) {
  14.         log.info("1.入参:" + mqttAuthParam.toString());
  15.         MqttAuthRes authRes = new MqttAuthRes();
  16.         if (ObjectUtil.isNotNull(mqttAuthParam)) {
  17.             // 超级用户、特殊用户,不需要进行设备 PK、DN 验证
  18.             if ("username".equals(mqttAuthParam.getUsername()) && "************".equals(mqttAuthParam.getPassword())) {
  19.                 authRes.setResult("allow");
  20.                 authRes.setIs_superuser(true);
  21.                 return authRes;
  22.             } else if ("admin".equals(mqttAuthParam.getUsername()) && "************".equals(mqttAuthParam.getPassword())) {
  23.                 authRes.setResult("allow");
  24.                 authRes.setIs_superuser(true);
  25.                 return authRes;
  26.             } else {
  27.                 // 对普通设备进行鉴权认证
  28.                 // 1. 根据PK、DN查询数据库设备的DS信息
  29.                 String username = mqttAuthParam.getUsername();
  30.                 if (!username.contains("&")) {
  31.                     throw new RuntimeException("设备登录MQTT账号格式错误");
  32.                 }
  33.                 String[] nameArr = username.split("&");
  34.                 String dn = nameArr[0];
  35.                 String pk = nameArr[1];
  36.                 log.info("3.PK、DN" + pk + "|" + dn);
  37.                 String deviceSecret = iotDeviceMapper.getDeviceSecretByPkDn(pk, dn);
  38.                 log.info("4.查询到的设备秘钥信息" + deviceSecret);
  39.                 if (ObjectUtil.isNotNull(deviceSecret)) {
  40.                     // 3. 校验加密后的结果与传进来的password是否一致
  41.                     String client = mqttAuthParam.getClientid();
  42.                     String[] clientArr = client.split("\\|");
  43.                     String clientid = clientArr[0];
  44.                     String otherParam = clientArr[1];
  45.                     // 时间戳
  46.                     String timestampStr = otherParam.split(",")[1];
  47.                     String timestamp = timestampStr.split("=")[1];
  48.                     // 加密方法
  49.                     String signmethodStr = otherParam.split(",")[0];
  50.                     String signmethod = signmethodStr.split("=")[1];
  51.                     String hexStr = "clientid"+clientid+"devicename"+dn+"productkey"+pk+"timestamp"+timestamp;
  52.                     log.info("5.加密前的字符串" + hexStr);
  53.                     // 对 hexStr 使用deviceSecret 进行加密
  54.                     String password = "";
  55.                     switch (signmethod.toLowerCase()) {
  56.                         case "hmacsha1" -> {
  57.                             password = HmacUtil.encrypt(hexStr, deviceSecret, HmacUtil.HMAC_SHA1);
  58.                         }
  59.                         case "hmacsha256" -> {
  60.                             password = HmacUtil.encrypt(hexStr, deviceSecret, HmacUtil.HMAC_SHA256);
  61.                         }
  62.                         case "hmacsha512" -> {
  63.                             password = HmacUtil.encrypt(hexStr, deviceSecret, HmacUtil.HMAC_SHA512);
  64.                         }
  65.                         case "hmacmd5" -> {
  66.                             password = HmacUtil.encrypt(hexStr, deviceSecret, HmacUtil.HMAC_MD5);
  67.                         }
  68.                         default -> {
  69.                         }
  70.                     }
  71.                     log.info("6.加密后的字符串" + password);
  72.                     if (!Objects.equals(password.toUpperCase(), "") && password.equalsIgnoreCase(mqttAuthParam.getPassword())) {
  73.                         authRes.setResult("allow");
  74.                         authRes.setIs_superuser(true);
  75.                         log.info("7.鉴权成功");
  76.                         return authRes;
  77.                     } else {
  78.                         authRes.setResult("deny");
  79.                     }
  80.                 }
  81.             }
  82.         }
  83.         authRes.setResult("deny");
  84.         return authRes;
  85.     }
  86. }
复制代码
四. Java基于双向认证毗连MQTT
  1.     public static SSLSocketFactory getSocketFactory(final String caCrtFile, final String crtFile, final String keyFile, final String password) throws Exception {
  2.         InputStream caInputStream = null;
  3.         InputStream crtInputStream = null;
  4.         InputStream keyInputStream = null;
  5.         try {
  6.             Security.addProvider(new BouncyCastleProvider());
  7.             CertificateFactory cf = CertificateFactory.getInstance("X.509");
  8.             caInputStream = new ClassPathResource(caCrtFile).getInputStream();
  9.             X509Certificate caCert = null;
  10.             while (caInputStream.available() > 0) {
  11.                 caCert = (X509Certificate) cf.generateCertificate(caInputStream);
  12.             }
  13.             crtInputStream = new ClassPathResource(crtFile).getInputStream();
  14.             X509Certificate cert = null;
  15.             while (crtInputStream.available() > 0) {
  16.                 cert = (X509Certificate) cf.generateCertificate(crtInputStream);
  17.             }
  18.             keyInputStream = new ClassPathResource(keyFile).getInputStream();
  19.             PEMParser pemParser = new PEMParser(new InputStreamReader(keyInputStream));
  20.             Object object = pemParser.readObject();
  21.             PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
  22.             JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
  23.             KeyPair key;
  24.             if (object instanceof PEMEncryptedKeyPair) {
  25.                 key = converter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(decProv));
  26.             } else {
  27.                 key = converter.getKeyPair((PEMKeyPair) object);
  28.             }
  29.             pemParser.close();
  30.             KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
  31.             caKs.load(null, null);
  32.             caKs.setCertificateEntry("ca-certificate", caCert);
  33.             TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
  34.             tmf.init(caKs);
  35.             KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
  36.             ks.load(null, null);
  37.             ks.setCertificateEntry("certificate", cert);
  38.             ks.setKeyEntry("private-key", key.getPrivate(), password.toCharArray(), new java.security.cert.Certificate[]{cert});
  39.             KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
  40.             kmf.init(ks, password.toCharArray());
  41.             SSLContext context = SSLContext.getInstance("TLSv1.2");
  42.             context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
  43.             return context.getSocketFactory();
  44.         }
  45.         finally {
  46.             if (null != caInputStream) {
  47.                 caInputStream.close();
  48.             }
  49.             if (null != crtInputStream) {
  50.                 crtInputStream.close();
  51.             }
  52.             if (null != keyInputStream) {
  53.                 keyInputStream.close();
  54.             }
  55.         }
  56.     }
复制代码
这里只提供分析证书的代码,毗连时直接放进去即可,如果有需要可以私信我发全代码

 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

钜形不锈钢水箱

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表