11 changed files with 254 additions and 16 deletions
@ -0,0 +1,32 @@ |
|||||
|
package org.dromara.web.config; |
||||
|
|
||||
|
import org.dromara.web.interceptor.HttpRequestLoggingInterceptor; |
||||
|
import org.dromara.web.interceptor.UserAccessRestrictionInterceptor; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; |
||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; |
||||
|
|
||||
|
/** |
||||
|
* Web拦截器配置 |
||||
|
* <p> |
||||
|
* 注册自定义的 HTTP 请求日志拦截器和用户访问限制拦截器。 |
||||
|
* </p> |
||||
|
* |
||||
|
* @author zdxt |
||||
|
*/ |
||||
|
@Configuration |
||||
|
public class WebInterceptorConfig implements WebMvcConfigurer { |
||||
|
|
||||
|
@Override |
||||
|
public void addInterceptors(InterceptorRegistry registry) { |
||||
|
// 全局HTTP请求日志
|
||||
|
registry.addInterceptor(new HttpRequestLoggingInterceptor()) |
||||
|
.addPathPatterns("/**") |
||||
|
.order(0); |
||||
|
|
||||
|
// 用户访问限制:特定用户仅允许访问报表接口
|
||||
|
registry.addInterceptor(new UserAccessRestrictionInterceptor()) |
||||
|
.addPathPatterns("/**") |
||||
|
.order(1); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,77 @@ |
|||||
|
package org.dromara.web.interceptor; |
||||
|
|
||||
|
import jakarta.servlet.http.HttpServletRequest; |
||||
|
import jakarta.servlet.http.HttpServletResponse; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.web.servlet.HandlerInterceptor; |
||||
|
import org.springframework.web.servlet.ModelAndView; |
||||
|
|
||||
|
/** |
||||
|
* HTTP 请求日志拦截器 |
||||
|
* <p> |
||||
|
* 拦截所有 HTTP 请求,打印请求方法、URI、客户端IP、请求头等关键信息, |
||||
|
* 以及响应状态码和耗时,便于调试和问题排查。 |
||||
|
* </p> |
||||
|
* |
||||
|
* @author zdxt |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public class HttpRequestLoggingInterceptor implements HandlerInterceptor { |
||||
|
|
||||
|
private static final ThreadLocal<Long> START_TIME = new ThreadLocal<>(); |
||||
|
|
||||
|
@Override |
||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { |
||||
|
START_TIME.set(System.currentTimeMillis()); |
||||
|
|
||||
|
String method = request.getMethod(); |
||||
|
String uri = request.getRequestURI(); |
||||
|
String queryString = request.getQueryString(); |
||||
|
String clientIp = getClientIp(request); |
||||
|
String userAgent = request.getHeader("User-Agent"); |
||||
|
String contentType = request.getContentType(); |
||||
|
|
||||
|
log.info("[HTTP请求] {} {} | IP: {} | Content-Type: {} | User-Agent: {} | Query: {}", |
||||
|
method, uri, clientIp, contentType, userAgent, queryString); |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { |
||||
|
// do nothing
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { |
||||
|
Long startTime = START_TIME.get(); |
||||
|
if (startTime != null) { |
||||
|
long duration = System.currentTimeMillis() - startTime; |
||||
|
int status = response.getStatus(); |
||||
|
log.info("[HTTP响应] {} {} | 状态码: {} | 耗时: {}ms", |
||||
|
request.getMethod(), request.getRequestURI(), status, duration); |
||||
|
START_TIME.remove(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取客户端真实IP(支持代理转发场景) |
||||
|
*/ |
||||
|
private String getClientIp(HttpServletRequest request) { |
||||
|
String ip = request.getHeader("X-Forwarded-For"); |
||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { |
||||
|
ip = request.getHeader("X-Real-IP"); |
||||
|
} |
||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { |
||||
|
ip = request.getHeader("Proxy-Client-IP"); |
||||
|
} |
||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { |
||||
|
ip = request.getRemoteAddr(); |
||||
|
} |
||||
|
// X-Forwarded-For 可能包含多个IP,取第一个
|
||||
|
if (ip != null && ip.contains(",")) { |
||||
|
ip = ip.split(",")[0].trim(); |
||||
|
} |
||||
|
return ip; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,102 @@ |
|||||
|
package org.dromara.web.interceptor; |
||||
|
|
||||
|
import cn.dev33.satoken.stp.StpUtil; |
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import jakarta.servlet.http.HttpServletRequest; |
||||
|
import jakarta.servlet.http.HttpServletResponse; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.dromara.common.core.domain.R; |
||||
|
import org.dromara.common.satoken.utils.LoginHelper; |
||||
|
import org.springframework.http.HttpStatus; |
||||
|
import org.springframework.http.MediaType; |
||||
|
import org.springframework.web.servlet.HandlerInterceptor; |
||||
|
|
||||
|
import java.io.PrintWriter; |
||||
|
import java.util.Objects; |
||||
|
|
||||
|
/** |
||||
|
* 用户访问限制拦截器 |
||||
|
* <p> |
||||
|
* 针对特定用户ID(如17608642193),仅允许访问报表相关接口 |
||||
|
* (/web/supervision/report/**),其余接口一律拒绝访问。 |
||||
|
* </p> |
||||
|
* |
||||
|
* @author zdxt |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public class UserAccessRestrictionInterceptor implements HandlerInterceptor { |
||||
|
|
||||
|
/** |
||||
|
* 受限用户ID(仅允许访问报表接口) |
||||
|
*/ |
||||
|
private static final Long RESTRICTED_USER_ID = 17608642193L; |
||||
|
|
||||
|
/** |
||||
|
* 允许访问的路径关键字列表(使用contains匹配,兼容不同context-path前缀如/sup-ep) |
||||
|
*/ |
||||
|
private static final String[] ALLOWED_PATH_KEYWORDS = { |
||||
|
"/web/supervision/report/", |
||||
|
"/system/dict/data/type/", |
||||
|
"/web/enforcementcode/educationServices/", |
||||
|
"/web/supervision/carouselBanner/", |
||||
|
"/web/supervision/noticeInfo/", |
||||
|
"/web/supervision/noticeInfo/", |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* 额外允许访问的精确路径列表 |
||||
|
*/ |
||||
|
private static final String[] ALLOWED_EXACT_PATHS = { |
||||
|
"/sup-ep/system/user/getInfo", |
||||
|
"/sup-ep/system/menu/getRouters", |
||||
|
"/sup-ep/auth/tenant/list", |
||||
|
"/sup-ep/system/user/profile", |
||||
|
"/sup-ep/system/social/list", |
||||
|
"/sup-ep/monitor/online", |
||||
|
}; |
||||
|
|
||||
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); |
||||
|
|
||||
|
@Override |
||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { |
||||
|
// 未登录的请求不在此拦截器处理(交给Sa-Token认证拦截器)
|
||||
|
if (!LoginHelper.isLogin()) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
Long userId = LoginHelper.getUserId(); |
||||
|
if (!Objects.equals(RESTRICTED_USER_ID, userId)) { |
||||
|
// 非受限用户,放行
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
String uri = request.getRequestURI(); |
||||
|
// 检查允许的路径关键字
|
||||
|
for (String keyword : ALLOWED_PATH_KEYWORDS) { |
||||
|
if (uri.contains(keyword)) { |
||||
|
log.debug("[访问限制] 用户ID={} 访问允许路径: {}, 允许", userId, uri); |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 检查额外允许的精确路径
|
||||
|
for (String allowedPath : ALLOWED_EXACT_PATHS) { |
||||
|
if (uri.equals(allowedPath)) { |
||||
|
log.debug("[访问限制] 用户ID={} 访问白名单路径: {}, 允许", userId, uri); |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 受限用户访问非允许路径,拦截
|
||||
|
log.warn("[访问限制] 用户ID={} 尝试访问非授权路径: {}, 已拒绝", userId, uri); |
||||
|
response.setStatus(HttpStatus.FORBIDDEN.value()); |
||||
|
response.setContentType(MediaType.APPLICATION_JSON_VALUE); |
||||
|
response.setCharacterEncoding("UTF-8"); |
||||
|
|
||||
|
R<Void> result = R.fail(HttpStatus.FORBIDDEN.value(), "当前用户仅允许访问报表相关接口"); |
||||
|
PrintWriter writer = response.getWriter(); |
||||
|
writer.write(OBJECT_MAPPER.writeValueAsString(result)); |
||||
|
writer.flush(); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue