|
|
@@ -0,0 +1,88 @@
|
|
|
+package com.cyksj.security;
|
|
|
+
|
|
|
+import org.springframework.core.Ordered;
|
|
|
+import org.springframework.core.annotation.Order;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+import org.springframework.web.filter.OncePerRequestFilter;
|
|
|
+
|
|
|
+import javax.servlet.FilterChain;
|
|
|
+import javax.servlet.ServletException;
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
+import javax.servlet.http.HttpServletResponseWrapper;
|
|
|
+import java.io.IOException;
|
|
|
+import java.util.regex.Pattern;
|
|
|
+
|
|
|
+/** Sensitive API responses must never become shared CDN cache entries. */
|
|
|
+@Component
|
|
|
+@Order(Ordered.HIGHEST_PRECEDENCE)
|
|
|
+public class ApiCacheProtectionFilter extends OncePerRequestFilter {
|
|
|
+ private static final Pattern MATRIX_PATH = Pattern.compile(";|%(?:25)*3b", Pattern.CASE_INSENSITIVE);
|
|
|
+
|
|
|
+ private static final Pattern SENSITIVE_PATH = Pattern.compile(
|
|
|
+ "^/(?:manage|manager|auth|yhShop|applets/(?:user|order|register)|applet/coupon)(?:$|[/;.%])");
|
|
|
+ private static final Pattern STATIC_SUFFIX = Pattern.compile(
|
|
|
+ "\\.(?:css|js|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|map)$", Pattern.CASE_INSENSITIVE);
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected boolean shouldNotFilter(HttpServletRequest request) {
|
|
|
+ String uri = request.getRequestURI();
|
|
|
+ String context = request.getContextPath();
|
|
|
+ if (uri != null && context != null && !context.isEmpty() && uri.startsWith(context)) {
|
|
|
+ uri = uri.substring(context.length());
|
|
|
+ }
|
|
|
+ String servletPath = request.getServletPath();
|
|
|
+ return !(uri != null && SENSITIVE_PATH.matcher(uri).find())
|
|
|
+ && !(servletPath != null && SENSITIVE_PATH.matcher(servletPath).find());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
|
|
|
+ FilterChain chain) throws ServletException, IOException {
|
|
|
+ HttpServletResponseWrapper protectedResponse = new HttpServletResponseWrapper(response) {
|
|
|
+ private String protectedValue(String name, String value) {
|
|
|
+ if ("Cache-Control".equalsIgnoreCase(name)) return "no-store, private, max-age=0";
|
|
|
+ if ("Pragma".equalsIgnoreCase(name)) return "no-cache";
|
|
|
+ if ("Expires".equalsIgnoreCase(name)) return "0";
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+ @Override public void setHeader(String name, String value) {
|
|
|
+ super.setHeader(name, protectedValue(name, value));
|
|
|
+ }
|
|
|
+ @Override public void addHeader(String name, String value) {
|
|
|
+ if ("Cache-Control".equalsIgnoreCase(name) || "Pragma".equalsIgnoreCase(name)
|
|
|
+ || "Expires".equalsIgnoreCase(name)) setHeader(name, value);
|
|
|
+ else super.addHeader(name, value);
|
|
|
+ }
|
|
|
+ @Override public void setDateHeader(String name, long date) {
|
|
|
+ if ("Expires".equalsIgnoreCase(name)) setHeader(name, "0");
|
|
|
+ else super.setDateHeader(name, date);
|
|
|
+ }
|
|
|
+ @Override public void addDateHeader(String name, long date) {
|
|
|
+ if ("Expires".equalsIgnoreCase(name)) setHeader(name, "0");
|
|
|
+ else super.addDateHeader(name, date);
|
|
|
+ }
|
|
|
+ @Override public void reset() {
|
|
|
+ super.reset();
|
|
|
+ noCache(this);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ noCache(protectedResponse);
|
|
|
+ // Inspect the raw path, not query strings; never normalize an ambiguous path into an API call.
|
|
|
+ String uri = request.getRequestURI();
|
|
|
+ if (uri != null && (MATRIX_PATH.matcher(uri).find() || STATIC_SUFFIX.matcher(uri).find())) {
|
|
|
+ protectedResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
|
|
+ protectedResponse.setContentType("application/json;charset=UTF-8");
|
|
|
+ protectedResponse.getWriter().write("{\"code\":100002,\"msg\":\"Invalid request path\",\"flag\":false}");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ chain.doFilter(request, protectedResponse);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void noCache(HttpServletResponse response) {
|
|
|
+ response.setHeader("Cache-Control", "no-store, private, max-age=0");
|
|
|
+ response.setHeader("Pragma", "no-cache");
|
|
|
+ response.setHeader("Expires", "0");
|
|
|
+ response.setHeader("X-Content-Type-Options", "nosniff");
|
|
|
+ }
|
|
|
+}
|