|
|
@@ -5,10 +5,15 @@ import com.aijiuyi.admin.common.config.AuthProperties;
|
|
|
import com.aijiuyi.admin.common.constant.ResultCode;
|
|
|
import com.aijiuyi.admin.common.context.RequestContext;
|
|
|
import com.aijiuyi.admin.common.entity.Result;
|
|
|
+import com.aijiuyi.admin.entity.AdminPermission;
|
|
|
+import com.aijiuyi.admin.entity.AppUser;
|
|
|
+import com.aijiuyi.admin.mapper.AdminPermissionMapper;
|
|
|
+import com.aijiuyi.admin.mapper.AppUserMapper;
|
|
|
import com.aijiuyi.admin.common.util.LogUtil;
|
|
|
import com.aijiuyi.admin.common.util.RedisUtil;
|
|
|
import com.aijiuyi.admin.common.util.TokenUtil;
|
|
|
import com.alibaba.fastjson.JSON;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import io.jsonwebtoken.Claims;
|
|
|
import org.slf4j.MDC;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
@@ -21,6 +26,12 @@ import org.springframework.web.servlet.HandlerInterceptor;
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
import javax.servlet.http.HttpServletResponse;
|
|
|
import java.io.PrintWriter;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.LinkedHashSet;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Set;
|
|
|
import java.util.UUID;
|
|
|
|
|
|
/**
|
|
|
@@ -33,6 +44,9 @@ import java.util.UUID;
|
|
|
@Component
|
|
|
public class TokenInterceptor implements HandlerInterceptor {
|
|
|
|
|
|
+ private static final int ADMIN_USER_TYPE = 3;
|
|
|
+ private static final int SUPER_ADMIN_ROLE = 1;
|
|
|
+
|
|
|
/** Token 在请求头中的 key */
|
|
|
private static final String TOKEN_HEADER = "Authorization";
|
|
|
/** Redis 中 Token 的 key 前缀 */
|
|
|
@@ -42,12 +56,34 @@ public class TokenInterceptor implements HandlerInterceptor {
|
|
|
/** MDC 中请求ID的 key */
|
|
|
private static final String MDC_REQUEST_ID = "requestId";
|
|
|
|
|
|
+ /** 已登录管理员可直接访问的基础接口 */
|
|
|
+ private static final List<String> ADMIN_ALWAYS_ALLOWED_PATTERNS = Collections.unmodifiableList(Arrays.asList(
|
|
|
+ "/api/auth/logout",
|
|
|
+ "/api/auth/info",
|
|
|
+ "/api/auth/profile",
|
|
|
+ "/api/file/**"
|
|
|
+ ));
|
|
|
+
|
|
|
+ /** 仅超级管理员可访问的高风险接口 */
|
|
|
+ private static final List<String> SUPER_ADMIN_PATTERNS = Collections.unmodifiableList(Arrays.asList(
|
|
|
+ "/api/admin/**"
|
|
|
+ ));
|
|
|
+
|
|
|
+ /** 后台接口到前端页面权限的映射,靠前规则优先匹配 */
|
|
|
+ private static final List<RoutePermissionRule> ROUTE_PERMISSION_RULES = createRoutePermissionRules();
|
|
|
+
|
|
|
@Autowired
|
|
|
private RedisUtil redisUtil;
|
|
|
|
|
|
@Autowired
|
|
|
private AuthProperties authProperties;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private AppUserMapper appUserMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private AdminPermissionMapper adminPermissionMapper;
|
|
|
+
|
|
|
/**
|
|
|
* 请求进入 Controller 前处理
|
|
|
* 生成请求ID,验证 Token,解析用户信息
|
|
|
@@ -124,6 +160,10 @@ public class TokenInterceptor implements HandlerInterceptor {
|
|
|
RequestContext.setUserName(username);
|
|
|
RequestContext.setUserType(userType);
|
|
|
|
|
|
+ if (!checkAdminConsolePermission(request, response, requestUri, userId)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
LogUtil.debug(TokenInterceptor.class, "用户[{}]请求接口[{}]", username, requestUri);
|
|
|
return true;
|
|
|
}
|
|
|
@@ -159,4 +199,165 @@ public class TokenInterceptor implements HandlerInterceptor {
|
|
|
writer.flush();
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ private boolean checkAdminConsolePermission(HttpServletRequest request, HttpServletResponse response,
|
|
|
+ String requestUri, Long userId) throws Exception {
|
|
|
+ if (!isAdminConsoleRequest(requestUri)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ AppUser admin = appUserMapper.selectById(userId);
|
|
|
+ if (admin == null || !Integer.valueOf(ADMIN_USER_TYPE).equals(admin.getUserType())) {
|
|
|
+ writeErrorResponse(response, ResultCode.FORBIDDEN);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (admin.getStatus() != null && admin.getStatus() == 0) {
|
|
|
+ writeErrorResponse(response, ResultCode.USER_DISABLED);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (matchesAny(requestUri, ADMIN_ALWAYS_ALLOWED_PATTERNS)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ boolean superAdmin = Integer.valueOf(SUPER_ADMIN_ROLE).equals(admin.getAdminRole());
|
|
|
+ if (matchesAny(requestUri, SUPER_ADMIN_PATTERNS)) {
|
|
|
+ if (superAdmin) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ writeErrorResponse(response, ResultCode.FORBIDDEN);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (superAdmin) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ Set<String> routePermissions = getRoutePermissions(admin.getId());
|
|
|
+ for (RoutePermissionRule rule : ROUTE_PERMISSION_RULES) {
|
|
|
+ if (rule.matches(request.getMethod(), requestUri)) {
|
|
|
+ if (rule.isAllowed(routePermissions)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ writeErrorResponse(response, ResultCode.FORBIDDEN);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ writeErrorResponse(response, ResultCode.FORBIDDEN);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isAdminConsoleRequest(String requestUri) {
|
|
|
+ return requestUri != null
|
|
|
+ && requestUri.startsWith("/api/")
|
|
|
+ && !PATH_MATCHER.match("/api/app/**", requestUri);
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean matchesAny(String requestUri, List<String> patterns) {
|
|
|
+ for (String pattern : patterns) {
|
|
|
+ if (PATH_MATCHER.match(pattern, requestUri)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Set<String> getRoutePermissions(Long adminId) {
|
|
|
+ AdminPermission permission = adminPermissionMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<AdminPermission>()
|
|
|
+ .eq(AdminPermission::getAdminId, adminId)
|
|
|
+ );
|
|
|
+ if (permission == null || !StringUtils.hasText(permission.getRoutePaths())) {
|
|
|
+ return Collections.emptySet();
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ List<String> routePaths = JSON.parseArray(permission.getRoutePaths(), String.class);
|
|
|
+ Set<String> result = new LinkedHashSet<>();
|
|
|
+ for (String routePath : routePaths) {
|
|
|
+ String normalized = normalizeRoutePath(routePath);
|
|
|
+ if (StringUtils.hasText(normalized)) {
|
|
|
+ result.add(normalized);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ } catch (Exception e) {
|
|
|
+ LogUtil.warn(TokenInterceptor.class, "管理员[{}]权限配置解析失败: {}", adminId, permission.getRoutePaths());
|
|
|
+ return Collections.emptySet();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String normalizeRoutePath(String routePath) {
|
|
|
+ if (!StringUtils.hasText(routePath)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String path = routePath.trim();
|
|
|
+ if ("/admin".equals(path)) {
|
|
|
+ return "/system/admin";
|
|
|
+ }
|
|
|
+ if ("/log".equals(path)) {
|
|
|
+ return "/system/log";
|
|
|
+ }
|
|
|
+ return path;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static List<RoutePermissionRule> createRoutePermissionRules() {
|
|
|
+ List<RoutePermissionRule> rules = new ArrayList<>();
|
|
|
+ rules.add(RoutePermissionRule.get("/api/user/profile/options", "/device", "/user/profile"));
|
|
|
+ rules.add(RoutePermissionRule.get("/api/user/profile/list-all", "/user/plan", "/user/profile"));
|
|
|
+ rules.add(RoutePermissionRule.get("/api/user/profile/page", "/user/profile", "/simulation"));
|
|
|
+ rules.add(RoutePermissionRule.get("/api/plan/page", "/plan", "/simulation"));
|
|
|
+ rules.add(RoutePermissionRule.get("/api/user-plan/page", "/user/plan", "/simulation"));
|
|
|
+ rules.add(RoutePermissionRule.get("/api/user/category/list", "/system/user-category", "/user/profile", "/user/app"));
|
|
|
+ rules.add(RoutePermissionRule.get("/api/acupoint/list", "/acupoint", "/plan"));
|
|
|
+ rules.add(RoutePermissionRule.get("/api/moxibustion-technique/list", "/moxibustion", "/plan"));
|
|
|
+ rules.add(RoutePermissionRule.any("/api/user/profile/**", "/user/profile"));
|
|
|
+ rules.add(RoutePermissionRule.any("/api/user/acupoint/**", "/user/profile"));
|
|
|
+ rules.add(RoutePermissionRule.any("/api/user/device/**", "/user/profile"));
|
|
|
+ rules.add(RoutePermissionRule.any("/api/user/app/**", "/user/app"));
|
|
|
+ rules.add(RoutePermissionRule.any("/api/user-plan/**", "/user/plan"));
|
|
|
+ rules.add(RoutePermissionRule.any("/api/user/category/**", "/system/user-category"));
|
|
|
+ rules.add(RoutePermissionRule.any("/api/acupoint/**", "/acupoint"));
|
|
|
+ rules.add(RoutePermissionRule.any("/api/device/**", "/device"));
|
|
|
+ rules.add(RoutePermissionRule.any("/api/moxibustion-technique/**", "/moxibustion"));
|
|
|
+ rules.add(RoutePermissionRule.any("/api/plan/**", "/plan"));
|
|
|
+ rules.add(RoutePermissionRule.any("/api/simulation/**", "/simulation"));
|
|
|
+ rules.add(RoutePermissionRule.any("/api/content/articles/**", "/content"));
|
|
|
+ rules.add(RoutePermissionRule.any("/api/log/**", "/system/log"));
|
|
|
+ return Collections.unmodifiableList(rules);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class RoutePermissionRule {
|
|
|
+ private final String method;
|
|
|
+ private final String pattern;
|
|
|
+ private final Set<String> routePaths;
|
|
|
+
|
|
|
+ private RoutePermissionRule(String method, String pattern, String... routePaths) {
|
|
|
+ this.method = method;
|
|
|
+ this.pattern = pattern;
|
|
|
+ this.routePaths = new LinkedHashSet<>(Arrays.asList(routePaths));
|
|
|
+ }
|
|
|
+
|
|
|
+ static RoutePermissionRule any(String pattern, String... routePaths) {
|
|
|
+ return new RoutePermissionRule(null, pattern, routePaths);
|
|
|
+ }
|
|
|
+
|
|
|
+ static RoutePermissionRule get(String pattern, String... routePaths) {
|
|
|
+ return new RoutePermissionRule("GET", pattern, routePaths);
|
|
|
+ }
|
|
|
+
|
|
|
+ boolean matches(String requestMethod, String requestUri) {
|
|
|
+ return (method == null || method.equalsIgnoreCase(requestMethod))
|
|
|
+ && PATH_MATCHER.match(pattern, requestUri);
|
|
|
+ }
|
|
|
+
|
|
|
+ boolean isAllowed(Set<String> permissions) {
|
|
|
+ for (String routePath : routePaths) {
|
|
|
+ if (permissions.contains(routePath)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|