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