parent
d92c6e1f2f
commit
dbee9fe94a
|
|
@ -7,9 +7,11 @@ import com.multictrl.common.constant.Constant;
|
||||||
import com.multictrl.common.exception.ErrorCode;
|
import com.multictrl.common.exception.ErrorCode;
|
||||||
import com.multictrl.common.exception.RenException;
|
import com.multictrl.common.exception.RenException;
|
||||||
import com.multictrl.common.interceptor.DataScope;
|
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.SecurityUser;
|
||||||
import com.multictrl.modules.security.user.UserDetail;
|
import com.multictrl.modules.security.user.UserDetail;
|
||||||
import com.multictrl.modules.sys.enums.SuperAdminEnum;
|
import com.multictrl.modules.sys.enums.SuperAdminEnum;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.aspectj.lang.JoinPoint;
|
import org.aspectj.lang.JoinPoint;
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
import org.aspectj.lang.annotation.Before;
|
import org.aspectj.lang.annotation.Before;
|
||||||
|
|
@ -26,6 +28,7 @@ import java.util.Map;
|
||||||
*
|
*
|
||||||
* @author Sdy
|
* @author Sdy
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Aspect
|
@Aspect
|
||||||
@Component
|
@Component
|
||||||
public class DataFilterAspect {
|
public class DataFilterAspect {
|
||||||
|
|
@ -37,8 +40,8 @@ public class DataFilterAspect {
|
||||||
|
|
||||||
@Before("dataFilterCut()")
|
@Before("dataFilterCut()")
|
||||||
public void dataFilter(JoinPoint point) {
|
public void dataFilter(JoinPoint point) {
|
||||||
Object params = point.getArgs()[0];
|
// Object params = point.getArgs()[0];
|
||||||
if (params instanceof Map) {
|
// if (params != null && params instanceof Map) {
|
||||||
UserDetail user = SecurityUser.getUser();
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
|
||||||
//如果是超级管理员,则不进行数据过滤
|
//如果是超级管理员,则不进行数据过滤
|
||||||
|
|
@ -46,19 +49,30 @@ public class DataFilterAspect {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
/* try {
|
||||||
//否则进行数据过滤
|
//否则进行数据过滤
|
||||||
Map map = (Map) params;
|
Map map = (Map) params;
|
||||||
String sqlFilter = getSqlFilter(user, point);
|
String sqlFilter = getSqlFilter(user, point);
|
||||||
|
// 放入 ThreadLocal
|
||||||
|
DataScopeContext.setDataScope(new DataScope(sqlFilter));
|
||||||
|
|
||||||
map.put(Constant.SQL_FILTER, 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;
|
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 += ".";
|
tableAlias += ".";
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder sqlFilter = new StringBuilder();
|
StringBuilder sqlFilter = new StringBuilder(" ");
|
||||||
sqlFilter.append(" (");
|
// sqlFilter.append("(");
|
||||||
|
|
||||||
//部门ID列表
|
//部门ID列表
|
||||||
List<Long> deptIdList = user.getDeptIdList();
|
List<Long> deptIdList = user.getDeptIdList();
|
||||||
|
|
@ -84,16 +98,20 @@ public class DataFilterAspect {
|
||||||
sqlFilter.append(tableAlias).append(dataFilter.deptId());
|
sqlFilter.append(tableAlias).append(dataFilter.deptId());
|
||||||
|
|
||||||
sqlFilter.append(" in(").append(CollUtil.join(deptIdList, ",")).append(")");
|
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(" 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import com.multictrl.modules.business.dao.DockDao;
|
||||||
import com.multictrl.modules.business.dto.DockDTO;
|
import com.multictrl.modules.business.dto.DockDTO;
|
||||||
import com.multictrl.modules.business.entity.DockEntity;
|
import com.multictrl.modules.business.entity.DockEntity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -23,6 +24,9 @@ public interface DockService extends CrudService<DockEntity, DockDTO> {
|
||||||
//分页查询
|
//分页查询
|
||||||
PageData<DockDTO> pageList(Map<String, Object> params);
|
PageData<DockDTO> pageList(Map<String, Object> params);
|
||||||
|
|
||||||
|
//获取机库列表
|
||||||
|
List<DockDTO> getUserDockList(Long userId);
|
||||||
|
|
||||||
//获取dao
|
//获取dao
|
||||||
DockDao getDao();
|
DockDao getDao();
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
package com.multictrl.modules.business.service.impl;
|
package com.multictrl.modules.business.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.json.JSONObject;
|
import cn.hutool.json.JSONObject;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.multictrl.common.constant.DockMode;
|
import com.multictrl.common.constant.DockMode;
|
||||||
import com.multictrl.common.page.PageData;
|
import com.multictrl.common.page.PageData;
|
||||||
import com.multictrl.common.service.impl.CrudServiceImpl;
|
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.DeviceDicDao;
|
||||||
import com.multictrl.modules.business.dao.DockDao;
|
import com.multictrl.modules.business.dao.DockDao;
|
||||||
import com.multictrl.modules.business.dao.DockDeviceDao;
|
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.entity.DockEntity;
|
||||||
import com.multictrl.modules.business.service.DJIBaseService;
|
import com.multictrl.modules.business.service.DJIBaseService;
|
||||||
import com.multictrl.modules.business.service.DockService;
|
import com.multictrl.modules.business.service.DockService;
|
||||||
|
import com.multictrl.modules.security.service.ShiroService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
@ -33,6 +37,7 @@ public class DockServiceImpl extends CrudServiceImpl<DockDao, DockEntity, DockDT
|
||||||
private final DJIBaseService djiBaseService;
|
private final DJIBaseService djiBaseService;
|
||||||
private final DockDeviceDao dockDeviceDao;
|
private final DockDeviceDao dockDeviceDao;
|
||||||
private final DeviceDicDao deviceDicDao;
|
private final DeviceDicDao deviceDicDao;
|
||||||
|
private final ShiroService shiroService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryWrapper<DockEntity> getWrapper(Map<String, Object> params) {
|
public QueryWrapper<DockEntity> getWrapper(Map<String, Object> params) {
|
||||||
|
|
@ -93,6 +98,17 @@ public class DockServiceImpl extends CrudServiceImpl<DockDao, DockEntity, DockDT
|
||||||
return page;
|
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
|
@Override
|
||||||
public DockDao getDao() {
|
public DockDao getDao() {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ public class ShiroConfig {
|
||||||
filterMap.put("/captcha", "anon");
|
filterMap.put("/captcha", "anon");
|
||||||
filterMap.put("/", "anon");
|
filterMap.put("/", "anon");
|
||||||
filterMap.put("/srs/**", "anon");
|
filterMap.put("/srs/**", "anon");
|
||||||
|
filterMap.put("/mqtt/auth", "anon");
|
||||||
filterMap.put("/**", "oauth2");
|
filterMap.put("/**", "oauth2");
|
||||||
return filterMap;
|
return filterMap;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,22 @@ import com.multictrl.common.utils.IpUtils;
|
||||||
import com.multictrl.common.utils.Result;
|
import com.multictrl.common.utils.Result;
|
||||||
import com.multictrl.common.validator.AssertUtils;
|
import com.multictrl.common.validator.AssertUtils;
|
||||||
import com.multictrl.common.validator.ValidatorUtils;
|
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.entity.SysLogLoginEntity;
|
||||||
import com.multictrl.modules.log.enums.LoginOperationEnum;
|
import com.multictrl.modules.log.enums.LoginOperationEnum;
|
||||||
import com.multictrl.modules.log.enums.LoginStatusEnum;
|
import com.multictrl.modules.log.enums.LoginStatusEnum;
|
||||||
import com.multictrl.modules.log.service.SysLogLoginService;
|
import com.multictrl.modules.log.service.SysLogLoginService;
|
||||||
import com.multictrl.modules.security.dto.LoginDTO;
|
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.password.PasswordUtils;
|
||||||
import com.multictrl.modules.security.service.CaptchaService;
|
import com.multictrl.modules.security.service.CaptchaService;
|
||||||
import com.multictrl.modules.security.service.SysUserTokenService;
|
import com.multictrl.modules.security.service.SysUserTokenService;
|
||||||
import com.multictrl.modules.security.user.SecurityUser;
|
import com.multictrl.modules.security.user.SecurityUser;
|
||||||
import com.multictrl.modules.security.user.UserDetail;
|
import com.multictrl.modules.security.user.UserDetail;
|
||||||
import com.multictrl.modules.sys.dto.SysUserDTO;
|
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.enums.UserStatusEnum;
|
||||||
import com.multictrl.modules.sys.service.SysUserService;
|
import com.multictrl.modules.sys.service.SysUserService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
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 io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
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 org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录
|
* 登录
|
||||||
|
|
@ -42,12 +49,14 @@ import java.util.Date;
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@Tag(name = "登录管理")
|
@Tag(name = "登录管理")
|
||||||
@AllArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class LoginController {
|
public class LoginController {
|
||||||
private final SysUserService sysUserService;
|
private final SysUserService sysUserService;
|
||||||
private final SysUserTokenService sysUserTokenService;
|
private final SysUserTokenService sysUserTokenService;
|
||||||
private final CaptchaService captchaService;
|
private final CaptchaService captchaService;
|
||||||
private final SysLogLoginService sysLogLoginService;
|
private final SysLogLoginService sysLogLoginService;
|
||||||
|
private final DockService dockService;
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("captcha")
|
@GetMapping("captcha")
|
||||||
@Operation(summary = "验证码")
|
@Operation(summary = "验证码")
|
||||||
|
|
@ -142,4 +151,49 @@ public class LoginController {
|
||||||
return new Result<Object>();
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.multictrl.modules.security.oauth2;
|
package com.multictrl.modules.security.oauth2;
|
||||||
|
|
||||||
import com.multictrl.common.exception.ErrorCode;
|
import com.multictrl.common.exception.ErrorCode;
|
||||||
|
import com.multictrl.common.utils.CacheUtils;
|
||||||
import com.multictrl.common.utils.ConvertUtils;
|
import com.multictrl.common.utils.ConvertUtils;
|
||||||
|
import com.multictrl.common.utils.JsonUtils;
|
||||||
import com.multictrl.common.utils.MessageUtils;
|
import com.multictrl.common.utils.MessageUtils;
|
||||||
import com.multictrl.modules.security.entity.SysUserTokenEntity;
|
import com.multictrl.modules.security.entity.SysUserTokenEntity;
|
||||||
import com.multictrl.modules.security.service.ShiroService;
|
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.apache.shiro.subject.PrincipalCollection;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -40,8 +44,18 @@ public class Oauth2Realm extends AuthorizingRealm {
|
||||||
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
|
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
|
||||||
UserDetail user = (UserDetail) principals.getPrimaryPrincipal();
|
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();
|
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
|
||||||
info.setStringPermissions(permsSet);
|
info.setStringPermissions(permsSet);
|
||||||
|
|
@ -55,8 +69,12 @@ public class Oauth2Realm extends AuthorizingRealm {
|
||||||
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
|
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
|
||||||
String accessToken = (String) token.getPrincipal();
|
String accessToken = (String) token.getPrincipal();
|
||||||
|
|
||||||
|
UserDetail userDetail;
|
||||||
|
Object object = CacheUtils.get(accessToken);
|
||||||
|
if (object == null) {
|
||||||
//根据accessToken,查询用户信息
|
//根据accessToken,查询用户信息
|
||||||
SysUserTokenEntity tokenEntity = shiroService.getByToken(accessToken);
|
SysUserTokenEntity tokenEntity = shiroService.getByToken(accessToken);
|
||||||
|
|
||||||
//token失效
|
//token失效
|
||||||
if (tokenEntity == null || tokenEntity.getExpireDate().getTime() < System.currentTimeMillis()) {
|
if (tokenEntity == null || tokenEntity.getExpireDate().getTime() < System.currentTimeMillis()) {
|
||||||
throw new IncorrectCredentialsException(MessageUtils.getMessage(ErrorCode.TOKEN_INVALID));
|
throw new IncorrectCredentialsException(MessageUtils.getMessage(ErrorCode.TOKEN_INVALID));
|
||||||
|
|
@ -64,21 +82,33 @@ public class Oauth2Realm extends AuthorizingRealm {
|
||||||
|
|
||||||
//查询用户信息
|
//查询用户信息
|
||||||
SysUserEntity userEntity = shiroService.getUser(tokenEntity.getUserId());
|
SysUserEntity userEntity = shiroService.getUser(tokenEntity.getUserId());
|
||||||
|
|
||||||
//转换成UserDetail对象
|
//转换成UserDetail对象
|
||||||
UserDetail userDetail = ConvertUtils.sourceToTarget(userEntity, UserDetail.class);
|
userDetail = ConvertUtils.sourceToTarget(userEntity, UserDetail.class);
|
||||||
|
userDetail.setToken(accessToken);
|
||||||
//获取用户对应的部门数据权限
|
|
||||||
List<Long> deptIdList = shiroService.getDataScopeList(userDetail.getId());
|
|
||||||
userDetail.setDeptIdList(deptIdList);
|
|
||||||
|
|
||||||
//账号锁定
|
//账号锁定
|
||||||
if (userDetail.getStatus() == 0) {
|
if (userDetail.getStatus() == 0) {
|
||||||
throw new LockedAccountException(MessageUtils.getMessage(ErrorCode.ACCOUNT_LOCK));
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -32,4 +32,7 @@ public class UserDetail implements Serializable {
|
||||||
*/
|
*/
|
||||||
private List<Long> deptIdList;
|
private List<Long> deptIdList;
|
||||||
|
|
||||||
|
//token
|
||||||
|
private String token;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -42,15 +42,15 @@ minio:
|
||||||
mqtt:
|
mqtt:
|
||||||
client1:
|
client1:
|
||||||
url: tcp://${host.ip}:61627
|
url: tcp://${host.ip}:61627
|
||||||
username: admin
|
username: dock
|
||||||
password: Aros2023
|
password: Dock@2023
|
||||||
subClientId: dj-one-sub${random.int(10)}
|
subClientId: dj-one-sub${random.int(10)}
|
||||||
subTopic: thing/product/#,sys/product/#,thing/device/#
|
subTopic: thing/product/#,sys/product/#,thing/device/#
|
||||||
pubClientId: dj-one-pub${random.int(10)}
|
pubClientId: dj-one-pub${random.int(10)}
|
||||||
client2:
|
client2:
|
||||||
url: tcp://${host.ip}:61637
|
url: tcp://${host.ip}:61637
|
||||||
username: admin
|
username: dock
|
||||||
password: Aros2023
|
password: Dock@2023
|
||||||
subClientId: dj-two-sub${random.int(10)}
|
subClientId: dj-two-sub${random.int(10)}
|
||||||
subTopic: thing/product/#
|
subTopic: thing/product/#
|
||||||
pubClientId: dj-two-pub${random.int(10)}
|
pubClientId: dj-two-pub${random.int(10)}
|
||||||
|
|
|
||||||
|
|
@ -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/
|
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/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/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 /root/dj_multictrl_data/srs/conf
|
||||||
mkdir -p /data/dj_multictrl_data/live_record
|
mkdir -p /data/dj_multictrl_data/live_record
|
||||||
|
|
|
||||||
|
|
@ -74,9 +74,15 @@ services:
|
||||||
- '61628:8083'
|
- '61628:8083'
|
||||||
environment:
|
environment:
|
||||||
- TZ=Asia/Shanghai
|
- 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:
|
volumes:
|
||||||
- /root/dj_multictrl_data/emqx/logs:/opt/emqx/log
|
- /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
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
|
||||||
dj-multictrl-emqx2:
|
dj-multictrl-emqx2:
|
||||||
|
|
@ -90,9 +96,12 @@ services:
|
||||||
- '61638:8083'
|
- '61638:8083'
|
||||||
environment:
|
environment:
|
||||||
- TZ=Asia/Shanghai
|
- TZ=Asia/Shanghai
|
||||||
- EMQX_ADMIN_PASSWORD=Aros2023
|
- EMQX_NODE_NAME=emqx@dj-multictrl-emqx2
|
||||||
|
- EMQX_DASHBOARD__DEFAULT_PASSWORD=Multictrl2023
|
||||||
volumes:
|
volumes:
|
||||||
- /root/dj_multictrl_data/emqx2/logs:/opt/emqx/log
|
- /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
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
|
||||||
dj-multictrl-srs:
|
dj-multictrl-srs:
|
||||||
|
|
|
||||||
|
|
@ -42,15 +42,15 @@ minio:
|
||||||
mqtt:
|
mqtt:
|
||||||
client1:
|
client1:
|
||||||
url: tcp://dj-multictrl-emqx:1883
|
url: tcp://dj-multictrl-emqx:1883
|
||||||
username: admin
|
username: dock
|
||||||
password: Aros2023
|
password: Dock@2023
|
||||||
subClientId: dj-one-sub
|
subClientId: dj-one-sub
|
||||||
subTopic: thing/product/#,sys/product/#
|
subTopic: thing/product/#,sys/product/#
|
||||||
pubClientId: dj-one-pub
|
pubClientId: dj-one-pub
|
||||||
client2:
|
client2:
|
||||||
url: tcp://${host.ip}:61637
|
url: tcp://${host.ip}:61637
|
||||||
username: admin
|
username: dock
|
||||||
password: Aros2023
|
password: Dock@2023
|
||||||
subClientId: dj-two-sub
|
subClientId: dj-two-sub
|
||||||
subTopic: thing/product/#
|
subTopic: thing/product/#
|
||||||
pubClientId: dj-two-pub
|
pubClientId: dj-two-pub
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
Loading…
Reference in New Issue