1.数据权限优化

2.mqtt增加权限校验
This commit is contained in:
sdy 2026-05-26 19:40:28 +08:00
parent d92c6e1f2f
commit dbee9fe94a
16 changed files with 486 additions and 60 deletions

View File

@ -7,9 +7,11 @@ import com.multictrl.common.constant.Constant;
import com.multictrl.common.exception.ErrorCode;
import com.multictrl.common.exception.RenException;
import com.multictrl.common.interceptor.DataScope;
import com.multictrl.common.interceptor.DataScopeContext;
import com.multictrl.modules.security.user.SecurityUser;
import com.multictrl.modules.security.user.UserDetail;
import com.multictrl.modules.sys.enums.SuperAdminEnum;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@ -26,6 +28,7 @@ import java.util.Map;
*
* @author Sdy
*/
@Slf4j
@Aspect
@Component
public class DataFilterAspect {
@ -37,8 +40,8 @@ public class DataFilterAspect {
@Before("dataFilterCut()")
public void dataFilter(JoinPoint point) {
Object params = point.getArgs()[0];
if (params instanceof Map) {
// Object params = point.getArgs()[0];
// if (params != null && params instanceof Map) {
UserDetail user = SecurityUser.getUser();
//如果是超级管理员则不进行数据过滤
@ -46,19 +49,30 @@ public class DataFilterAspect {
return;
}
try {
/* try {
//否则进行数据过滤
Map map = (Map) params;
String sqlFilter = getSqlFilter(user, point);
// 放入 ThreadLocal
DataScopeContext.setDataScope(new DataScope(sqlFilter));
map.put(Constant.SQL_FILTER, new DataScope(sqlFilter));
} catch (Exception ignored) {
}
} catch (Exception e) {
}*/
try {
String sqlFilter = getSqlFilter(user, point);
// 放入 ThreadLocal
DataScopeContext.setDataScope(new DataScope(sqlFilter));
return;
} catch (Exception e) {
throw new RenException(e.getMessage());
}
throw new RenException(ErrorCode.DATA_SCOPE_PARAMS_ERROR);
// return;
// }
// throw new RenException(ErrorCode.DATA_SCOPE_PARAMS_ERROR);
}
/**
@ -75,8 +89,8 @@ public class DataFilterAspect {
tableAlias += ".";
}
StringBuilder sqlFilter = new StringBuilder();
sqlFilter.append(" (");
StringBuilder sqlFilter = new StringBuilder(" ");
// sqlFilter.append("(");
//部门ID列表
List<Long> deptIdList = user.getDeptIdList();
@ -84,16 +98,20 @@ public class DataFilterAspect {
sqlFilter.append(tableAlias).append(dataFilter.deptId());
sqlFilter.append(" in(").append(CollUtil.join(deptIdList, ",")).append(")");
} else {
throw new RenException("暂无数据权限,无法访问");
}
//查询本人数据
if (CollUtil.isNotEmpty(deptIdList)) {
/* if (CollUtil.isNotEmpty(deptIdList)) {
sqlFilter.append(" or ");
}
sqlFilter.append(tableAlias).append(dataFilter.userId()).append("=").append(user.getId());
sqlFilter.append(tableAlias).append(dataFilter.userId()).append("=").append(user.getId());*/
sqlFilter.append(")");
// sqlFilter.append(")");
String sqlFilterString = sqlFilter.toString();
log.debug("数据过滤SQL{}", sqlFilterString);
return sqlFilter.toString();
return sqlFilterString;
}
}

View File

@ -0,0 +1,23 @@
package com.multictrl.common.interceptor;
/**
* 数据鉴权
*
* @author Sdy
* @since 1.0.0 2026/5/26
*/
public class DataScopeContext {
private static final ThreadLocal<DataScope> DATA_SCOPE_HOLDER = new ThreadLocal<>();
public static void setDataScope(DataScope dataScope) {
DATA_SCOPE_HOLDER.set(dataScope);
}
public static DataScope getDataScope() {
return DATA_SCOPE_HOLDER.get();
}
public static void clear() {
DATA_SCOPE_HOLDER.remove();
}
}

View File

@ -7,6 +7,7 @@ import com.multictrl.modules.business.dao.DockDao;
import com.multictrl.modules.business.dto.DockDTO;
import com.multictrl.modules.business.entity.DockEntity;
import java.util.List;
import java.util.Map;
/**
@ -23,6 +24,9 @@ public interface DockService extends CrudService<DockEntity, DockDTO> {
//分页查询
PageData<DockDTO> pageList(Map<String, Object> params);
//获取机库列表
List<DockDTO> getUserDockList(Long userId);
//获取dao
DockDao getDao();
}

View File

@ -1,11 +1,13 @@
package com.multictrl.modules.business.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import cn.hutool.core.util.StrUtil;
import com.multictrl.common.constant.DockMode;
import com.multictrl.common.page.PageData;
import com.multictrl.common.service.impl.CrudServiceImpl;
import com.multictrl.common.utils.ConvertUtils;
import com.multictrl.modules.business.dao.DeviceDicDao;
import com.multictrl.modules.business.dao.DockDao;
import com.multictrl.modules.business.dao.DockDeviceDao;
@ -15,9 +17,11 @@ import com.multictrl.modules.business.entity.DockDeviceEntity;
import com.multictrl.modules.business.entity.DockEntity;
import com.multictrl.modules.business.service.DJIBaseService;
import com.multictrl.modules.business.service.DockService;
import com.multictrl.modules.security.service.ShiroService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -33,6 +37,7 @@ public class DockServiceImpl extends CrudServiceImpl<DockDao, DockEntity, DockDT
private final DJIBaseService djiBaseService;
private final DockDeviceDao dockDeviceDao;
private final DeviceDicDao deviceDicDao;
private final ShiroService shiroService;
@Override
public QueryWrapper<DockEntity> getWrapper(Map<String, Object> params) {
@ -93,6 +98,17 @@ public class DockServiceImpl extends CrudServiceImpl<DockDao, DockEntity, DockDT
return page;
}
@Override
public List<DockDTO> getUserDockList(Long userId) {
List<Long> dataScopeList = shiroService.getDataScopeList(userId);
List<DockEntity> list = new ArrayList<>();
if(CollUtil.isNotEmpty(dataScopeList)){
list = baseDao.selectList(new QueryWrapper<DockEntity>().in("dept_id", dataScopeList));
}
return ConvertUtils.sourceToTarget(list, DockDTO.class);
}
@Override
public DockDao getDao() {

View File

@ -73,6 +73,7 @@ public class ShiroConfig {
filterMap.put("/captcha", "anon");
filterMap.put("/", "anon");
filterMap.put("/srs/**", "anon");
filterMap.put("/mqtt/auth", "anon");
filterMap.put("/**", "oauth2");
return filterMap;
}

View File

@ -6,17 +6,22 @@ import com.multictrl.common.utils.IpUtils;
import com.multictrl.common.utils.Result;
import com.multictrl.common.validator.AssertUtils;
import com.multictrl.common.validator.ValidatorUtils;
import com.multictrl.modules.business.dto.DockDTO;
import com.multictrl.modules.business.service.DockService;
import com.multictrl.modules.log.entity.SysLogLoginEntity;
import com.multictrl.modules.log.enums.LoginOperationEnum;
import com.multictrl.modules.log.enums.LoginStatusEnum;
import com.multictrl.modules.log.service.SysLogLoginService;
import com.multictrl.modules.security.dto.LoginDTO;
import com.multictrl.modules.security.dto.MqttAuthDTO;
import com.multictrl.modules.security.dto.MqttAuthVO;
import com.multictrl.modules.security.password.PasswordUtils;
import com.multictrl.modules.security.service.CaptchaService;
import com.multictrl.modules.security.service.SysUserTokenService;
import com.multictrl.modules.security.user.SecurityUser;
import com.multictrl.modules.security.user.UserDetail;
import com.multictrl.modules.sys.dto.SysUserDTO;
import com.multictrl.modules.sys.enums.SuperAdminEnum;
import com.multictrl.modules.sys.enums.UserStatusEnum;
import com.multictrl.modules.sys.service.SysUserService;
import io.swagger.v3.oas.annotations.Operation;
@ -25,7 +30,7 @@ import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@ -33,7 +38,9 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 登录
@ -42,12 +49,14 @@ import java.util.Date;
*/
@RestController
@Tag(name = "登录管理")
@AllArgsConstructor
@RequiredArgsConstructor
public class LoginController {
private final SysUserService sysUserService;
private final SysUserTokenService sysUserTokenService;
private final CaptchaService captchaService;
private final SysLogLoginService sysLogLoginService;
private final DockService dockService;
@GetMapping("captcha")
@Operation(summary = "验证码")
@ -142,4 +151,49 @@ public class LoginController {
return new Result<Object>();
}
@PostMapping("/mqtt/auth")
@Operation(summary = "mqtt认证", hidden = true)
public MqttAuthVO mqttAuth(@RequestBody MqttAuthDTO login) {
if (login.getUsername() == null || login.getPassword() == null) {
return new MqttAuthVO().setResult("deny");
}
//给机场配置专属用户名密码
if (login.getUsername().equals("dock") && login.getPassword().equals("Dock@2023")) {
return new MqttAuthVO().setResult("allow").setIs_superuser(true).setAcl(new ArrayList<>());
}
//用户账号密码校验
SysUserDTO user = sysUserService.getByUsername(login.getUsername());
if (user == null || !PasswordUtils.matches(login.getPassword(), user.getPassword())
|| user.getStatus() == UserStatusEnum.DISABLE.value()) {
return new MqttAuthVO().setResult("deny");
}
//分配权限
if (SuperAdminEnum.YES.value() == user.getSuperAdmin()) {
return new MqttAuthVO().setResult("allow").setIs_superuser(true).setAcl(new ArrayList<>());
} else {
String topic = "thing/product/%s/#";
List<MqttAuthVO.Acl> aclList = new ArrayList<>();
List<DockDTO> dockList = dockService.getUserDockList(user.getId());
for (DockDTO dto : dockList) {
MqttAuthVO.Acl acl = new MqttAuthVO.Acl();
acl.setAction("publish");
acl.setPermission("allow");
acl.setTopic(topic.formatted(dto.getDockSn()));
aclList.add(acl);
MqttAuthVO.Acl acl2 = new MqttAuthVO.Acl();
acl2.setAction("subscribe");
acl2.setPermission("allow");
acl2.setTopic(topic.formatted(dto.getDockSn()));
aclList.add(acl2);
}
aclList.add(new MqttAuthVO.Acl().setAction("publish").setPermission("deny").setTopic("#"));
aclList.add(new MqttAuthVO.Acl().setAction("subscribe").setPermission("deny").setTopic("#"));
return new MqttAuthVO().setResult("allow").setIs_superuser(false).setAcl(aclList);
}
}
}

View File

@ -0,0 +1,21 @@
package com.multictrl.modules.security.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* mqtt认证参数
*
* @author Sdy
* @since 1.0.0 2026/5/26
*/
@Data
@Schema(name = "mqtt认证参数")
public class MqttAuthDTO {
@Schema(description = "用户名")
private String username;
@Schema(description = "密码")
private String password;
}

View File

@ -0,0 +1,43 @@
package com.multictrl.modules.security.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* mqtt认证返回
*
* @author Sdy
* @since 1.0.0 2026/5/26
*/
@Data
@Accessors(chain = true)
@Schema(name = "mqtt认证参数")
public class MqttAuthVO {
@Schema(description = "认证结果 allow允许 deny拒绝")
private String result;
@Schema(description = "是否是超级用户 true超级用户 false普通用户")
private Boolean is_superuser;
@Schema(description = "访问控制列表," +
"数组中的每个对象是一条规则EMQX 会按顺序匹配,一旦匹配到某条规则就停止继续匹配,并使用该规则的 permission 值。")
private List<Acl> acl;
@Data
@Schema(name = "acl")
public static class Acl {
@Schema(description = "主题")
private String topic;
@Schema(description = "动作 publish subscribe")
private String action;
@Schema(description = "权限 allow deny")
private String permission;
}
}

View File

@ -1,7 +1,9 @@
package com.multictrl.modules.security.oauth2;
import com.multictrl.common.exception.ErrorCode;
import com.multictrl.common.utils.CacheUtils;
import com.multictrl.common.utils.ConvertUtils;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.common.utils.MessageUtils;
import com.multictrl.modules.security.entity.SysUserTokenEntity;
import com.multictrl.modules.security.service.ShiroService;
@ -15,7 +17,9 @@ import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
@ -40,8 +44,18 @@ public class Oauth2Realm extends AuthorizingRealm {
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
UserDetail user = (UserDetail) principals.getPrimaryPrincipal();
Set<String> permsSet;
Object object = CacheUtils.get(user.getToken() + "_perms");
if (object == null) {
//用户权限列表
Set<String> permsSet = shiroService.getUserPermissions(user);
permsSet = shiroService.getUserPermissions(user);
Map<String, Object> map = new HashMap<>();
map.put("perms", JsonUtils.toJsonString(permsSet));
CacheUtils.set(user.getToken() + "_perms", map);
} else {
String perms = (String) ((Map<String, Object>) object).get("perms");
permsSet = JsonUtils.parseObject(perms, Set.class);
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permsSet);
@ -55,8 +69,12 @@ public class Oauth2Realm extends AuthorizingRealm {
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String accessToken = (String) token.getPrincipal();
UserDetail userDetail;
Object object = CacheUtils.get(accessToken);
if (object == null) {
//根据accessToken查询用户信息
SysUserTokenEntity tokenEntity = shiroService.getByToken(accessToken);
//token失效
if (tokenEntity == null || tokenEntity.getExpireDate().getTime() < System.currentTimeMillis()) {
throw new IncorrectCredentialsException(MessageUtils.getMessage(ErrorCode.TOKEN_INVALID));
@ -64,21 +82,33 @@ public class Oauth2Realm extends AuthorizingRealm {
//查询用户信息
SysUserEntity userEntity = shiroService.getUser(tokenEntity.getUserId());
//转换成UserDetail对象
UserDetail userDetail = ConvertUtils.sourceToTarget(userEntity, UserDetail.class);
//获取用户对应的部门数据权限
List<Long> deptIdList = shiroService.getDataScopeList(userDetail.getId());
userDetail.setDeptIdList(deptIdList);
userDetail = ConvertUtils.sourceToTarget(userEntity, UserDetail.class);
userDetail.setToken(accessToken);
//账号锁定
if (userDetail.getStatus() == 0) {
throw new LockedAccountException(MessageUtils.getMessage(ErrorCode.ACCOUNT_LOCK));
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userDetail, accessToken, getName());
return info;
//获取用户对应的部门数据权限
List<Long> deptIdList = shiroService.getDataScopeList(userDetail.getId());
userDetail.setDeptIdList(deptIdList);
Map<String, Object> map = new HashMap<>();
map.put("user", userDetail);
map.put("token", tokenEntity);
CacheUtils.set(accessToken, map, 1000 * 60);
} else {
userDetail = (UserDetail) ((Map<String, Object>) object).get("user");
SysUserTokenEntity tokenEntity = (SysUserTokenEntity) ((Map<String, Object>) object).get("token");
//token失效
if (tokenEntity == null || tokenEntity.getExpireDate().getTime() < System.currentTimeMillis()) {
throw new IncorrectCredentialsException(MessageUtils.getMessage(ErrorCode.TOKEN_INVALID));
}
}
return new SimpleAuthenticationInfo(userDetail, accessToken, getName());
}
}

View File

@ -32,4 +32,7 @@ public class UserDetail implements Serializable {
*/
private List<Long> deptIdList;
//token
private String token;
}

View File

@ -42,15 +42,15 @@ minio:
mqtt:
client1:
url: tcp://${host.ip}:61627
username: admin
password: Aros2023
username: dock
password: Dock@2023
subClientId: dj-one-sub${random.int(10)}
subTopic: thing/product/#,sys/product/#,thing/device/#
pubClientId: dj-one-pub${random.int(10)}
client2:
url: tcp://${host.ip}:61637
username: admin
password: Aros2023
username: dock
password: Dock@2023
subClientId: dj-two-sub${random.int(10)}
subTopic: thing/product/#
pubClientId: dj-two-pub${random.int(10)}

View File

@ -24,7 +24,13 @@ cp -r ./file/nginx/cert/* /root/dj_multictrl_data/nginx/cert/
cp -r ./file/nginx/html/* /root/dj_multictrl_data/nginx/html/
mkdir -p /root/dj_multictrl_data/emqx/logs
mkdir -p /root/dj_multictrl_data/emqx/data
mkdir -p /root/dj_multictrl_data/emqx/etc
cp ./file/emqx/etc/emqx.conf /root/dj_multictrl_data/emqx/etc/emqx.conf
mkdir -p /root/dj_multictrl_data/emqx2/logs
mkdir -p /root/dj_multictrl_data/emqx2/data
mkdir -p /root/dj_multictrl_data/emqx2/etc
cp ./file/emqx2/etc/emqx.conf /root/dj_multictrl_data/emqx2/etc/emqx.conf
mkdir -p /root/dj_multictrl_data/srs/conf
mkdir -p /data/dj_multictrl_data/live_record

View File

@ -74,9 +74,15 @@ services:
- '61628:8083'
environment:
- TZ=Asia/Shanghai
- EMQX_ADMIN_PASSWORD=Aros2023
# 关键变量 1: 固定节点名避免因容器IP变化导致数据失效
- EMQX_NODE_NAME=emqx@dj-multictrl-emqx
# 关键变量 2: 正确设置 Dashboard 密码 (针对 5.x 版本)
- EMQX_DASHBOARD__DEFAULT_PASSWORD=Multictrl2023
#- EMQX_ADMIN_PASSWORD=2023
volumes:
- /root/dj_multictrl_data/emqx/logs:/opt/emqx/log
- /root/dj_multictrl_data/emqx/data:/opt/emqx/data
- /root/dj_multictrl_data/emqx/etc/emqx.conf:/opt/emqx/etc/emqx.conf
- /etc/localtime:/etc/localtime:ro
dj-multictrl-emqx2:
@ -90,9 +96,12 @@ services:
- '61638:8083'
environment:
- TZ=Asia/Shanghai
- EMQX_ADMIN_PASSWORD=Aros2023
- EMQX_NODE_NAME=emqx@dj-multictrl-emqx2
- EMQX_DASHBOARD__DEFAULT_PASSWORD=Multictrl2023
volumes:
- /root/dj_multictrl_data/emqx2/logs:/opt/emqx/log
- /root/dj_multictrl_data/emqx2/data:/opt/emqx/data
- /root/dj_multictrl_data/emqx2/etc:/opt/emqx/etc
- /etc/localtime:/etc/localtime:ro
dj-multictrl-srs:

View File

@ -42,15 +42,15 @@ minio:
mqtt:
client1:
url: tcp://dj-multictrl-emqx:1883
username: admin
password: Aros2023
username: dock
password: Dock@2023
subClientId: dj-one-sub
subTopic: thing/product/#,sys/product/#
pubClientId: dj-one-pub
client2:
url: tcp://${host.ip}:61637
username: admin
password: Aros2023
username: dock
password: Dock@2023
subClientId: dj-two-sub
subTopic: thing/product/#
pubClientId: dj-two-pub

View File

@ -0,0 +1,99 @@
## NOTE:
## This config file overrides data/configs/cluster.hocon,
## and is merged with environment variables which start with 'EMQX_' prefix.
##
## Config changes made from EMQX dashboard UI, management HTTP API, or CLI
## are stored in data/configs/cluster.hocon.
## To avoid confusion, please do not store the same configs in both files.
##
## See https://www.emqx.io/docs/en/latest/configuration/configuration.html for more details.
## Configuration full example can be found in etc/examples
node {
name = "emqx@127.0.0.1"
cookie = "emqxsecretcookie"
data_dir = "data"
}
cluster {
name = emqxcl
discovery_strategy = manual
}
## EMQX provides support for two primary log handlers: `file` and `console`, with an additional `audit` handler specifically designed to always direct logs to files.
## The system's default log handling behavior can be configured via the environment variable `EMQX_DEFAULT_LOG_HANDLER`, which accepts the following settings:
##
## - `file`: Directs log output exclusively to files.
## - `console`: Channels log output solely to the console.
##
## It's noteworthy that `EMQX_DEFAULT_LOG_HANDLER` is set to `file` when EMQX is initiated via systemd `emqx.service` file.
## In scenarios outside systemd initiation, `console` serves as the default log handler.
## Read more about configs here: https://www.emqx.io/docs/en/latest/configuration/logs.html
log {
# file {
# level = warning
# }
# console {
# level = warning
# }
}
dashboard {
listeners {
http {
## Comment out 'bind' (or set bind=0) to disable listener.
bind = 18083
}
https {
## Uncomment to enable
# bind = 18084
ssl_options {
certfile = "${EMQX_ETC_DIR}/certs/cert.pem"
keyfile = "${EMQX_ETC_DIR}/certs/key.pem"
}
}
}
}
# 必须禁止匿名访问,是开启认证的必备步骤
allow_anonymous = false
# 启用http协议的认证方式
authentication = [
{
# 后端类型http 服务
backend = "http"
enable = true
mechanism = "password_based"
# ----- 请求配置 -----
# 请求方法POST 或 GET推荐 POST更安全
method = "post"
# 你的认证服务 URL请替换为实际地址
url = "http://dj-multictrl-api:8080/api/mqtt/auth"
# 请求头
headers {
"Content-Type" = "application/json"
"Accept" = "application/json"
}
# 请求体模板(支持占位符)
body {
username = "${username}"
password = "${password}"
}
# ----- 性能与超时 -----
request_timeout = "5s" # HTTP 请求超时时间
pool_size = 8 # 连接池大小
# ----- 认证结果判断 -----
# 服务端需返回 JSON 格式,包含 result 字段:
# {"result": "allow"} → 允许连接
# {"result": "deny"} → 拒绝连接
# {"result": "ignore"} → 忽略,继续后续认证链
# 如果返回 HTTP 4xx/5xx 状态码,视为 ignore
}
]

View File

@ -0,0 +1,99 @@
## NOTE:
## This config file overrides data/configs/cluster.hocon,
## and is merged with environment variables which start with 'EMQX_' prefix.
##
## Config changes made from EMQX dashboard UI, management HTTP API, or CLI
## are stored in data/configs/cluster.hocon.
## To avoid confusion, please do not store the same configs in both files.
##
## See https://www.emqx.io/docs/en/latest/configuration/configuration.html for more details.
## Configuration full example can be found in etc/examples
node {
name = "emqx@127.0.0.1"
cookie = "emqxsecretcookie"
data_dir = "data"
}
cluster {
name = emqxcl
discovery_strategy = manual
}
## EMQX provides support for two primary log handlers: `file` and `console`, with an additional `audit` handler specifically designed to always direct logs to files.
## The system's default log handling behavior can be configured via the environment variable `EMQX_DEFAULT_LOG_HANDLER`, which accepts the following settings:
##
## - `file`: Directs log output exclusively to files.
## - `console`: Channels log output solely to the console.
##
## It's noteworthy that `EMQX_DEFAULT_LOG_HANDLER` is set to `file` when EMQX is initiated via systemd `emqx.service` file.
## In scenarios outside systemd initiation, `console` serves as the default log handler.
## Read more about configs here: https://www.emqx.io/docs/en/latest/configuration/logs.html
log {
# file {
# level = warning
# }
# console {
# level = warning
# }
}
dashboard {
listeners {
http {
## Comment out 'bind' (or set bind=0) to disable listener.
bind = 18083
}
https {
## Uncomment to enable
# bind = 18084
ssl_options {
certfile = "${EMQX_ETC_DIR}/certs/cert.pem"
keyfile = "${EMQX_ETC_DIR}/certs/key.pem"
}
}
}
}
# 必须禁止匿名访问,是开启认证的必备步骤
allow_anonymous = false
# 启用http协议的认证方式
authentication = [
{
# 后端类型http 服务
backend = "http"
enable = true
mechanism = "password_based"
# ----- 请求配置 -----
# 请求方法POST 或 GET推荐 POST更安全
method = "post"
# 你的认证服务 URL请替换为实际地址
url = "http://dj-multictrl-api:8080/api/mqtt/auth"
# 请求头
headers {
"Content-Type" = "application/json"
"Accept" = "application/json"
}
# 请求体模板(支持占位符)
body {
username = "${username}"
password = "${password}"
}
# ----- 性能与超时 -----
request_timeout = "5s" # HTTP 请求超时时间
pool_size = 8 # 连接池大小
# ----- 认证结果判断 -----
# 服务端需返回 JSON 格式,包含 result 字段:
# {"result": "allow"} → 允许连接
# {"result": "deny"} → 拒绝连接
# {"result": "ignore"} → 忽略,继续后续认证链
# 如果返回 HTTP 4xx/5xx 状态码,视为 ignore
}
]