| package org.apache.struts2.interceptor; |
| |
| import com.opensymphony.xwork2.ActionContext; |
| import com.opensymphony.xwork2.ActionInvocation; |
| import com.opensymphony.xwork2.interceptor.AbstractInterceptor; |
| import com.opensymphony.xwork2.interceptor.PreResultListener; |
| import com.opensymphony.xwork2.util.TextParseUtil; |
| |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| /** |
| * <!-- START SNIPPET: description --> |
| * |
| * |
| * Interceptor that implements Fetch Metadata policy on incoming requests used to protect against |
| * CSRF, XSSI, and cross-origin information leaks. Uses {@link DefaultResourceIsolationPolicy} to |
| * filter the requests allowed to be processed. |
| * |
| * @see <a href="https://web.dev/fetch-metadata/">https://web.dev/fetch-metadata/</a> |
| * |
| * <!-- END SNIPPET: description --> |
| * |
| * <!-- START SNIPPET: parameters --> |
| |
| * <ul> |
| * <li>exemptedPaths - Set of opt out endpoints that are meant to serve |
| * cross-site traffic</li> |
| * <li>resourceIsolationPolicy -Instance of {@link DefaultResourceIsolationPolicy} implementing |
| * the logic for the requests filtering</li> |
| * <li>VARY_HEADER_VALUE - static vary header value for each vary response headers</li> |
| * </ul> |
| * |
| * <!-- END SNIPPET: parameters --> |
| * |
| * <!-- START SNIPPET: extending --> |
| * |
| * No extensions |
| * |
| * <!-- END SNIPPET: extending --> |
| * |
| * <!-- START SNIPPET: example --> |
| * |
| * <action ... > |
| * <interceptor-ref name="defaultStack"/> |
| * <interceptor-ref name="fetchMetadata"> |
| * <param name="exemptedPaths"> path1,path2,path3 <para/> |
| * <interceptor-ref/> |
| * ... |
| * </action> |
| * |
| * <!-- END SNIPPET: example --> |
| **/ |
| |
| public class FetchMetadataInterceptor extends AbstractInterceptor implements PreResultListener { |
| |
| private final Set<String> exemptedPaths = new HashSet<String>(); |
| private final ResourceIsolationPolicy resourceIsolationPolicy = new DefaultResourceIsolationPolicy(); |
| private static final String VARY_HEADER_VALUE = String.format("%s,%s,%s", DefaultResourceIsolationPolicy.SEC_FETCH_DEST_HEADER, DefaultResourceIsolationPolicy.SEC_FETCH_SITE_HEADER, DefaultResourceIsolationPolicy.SEC_FETCH_MODE_HEADER); |
| private static final String SC_FORBIDDEN = String.valueOf(HttpServletResponse.SC_FORBIDDEN); |
| |
| public void setExemptedPaths(String paths){ |
| this.exemptedPaths.addAll(TextParseUtil.commaDelimitedStringToSet(paths)); |
| } |
| |
| @Override |
| public void beforeResult(ActionInvocation invocation, String resultCode) { |
| // Add Vary headers |
| HttpServletResponse response = invocation.getInvocationContext().getServletResponse(); |
| response.setHeader(DefaultResourceIsolationPolicy.VARY_HEADER, VARY_HEADER_VALUE); |
| } |
| |
| @Override |
| public String intercept(ActionInvocation invocation) throws Exception { |
| ActionContext context = invocation.getInvocationContext(); |
| HttpServletRequest request = context.getServletRequest(); |
| |
| // Adds listener that operates between interceptor and result rendering to set Vary headers |
| invocation.addPreResultListener(this); |
| |
| // Apply exemptions: paths/endpoints meant to be served cross-origin |
| if (exemptedPaths.contains(request.getContextPath())) { |
| return invocation.invoke(); |
| } |
| |
| // Check if request is allowed |
| if (resourceIsolationPolicy.isRequestAllowed(request)) { |
| return invocation.invoke(); |
| } |
| |
| beforeResult(invocation, SC_FORBIDDEN); |
| return SC_FORBIDDEN; |
| } |
| } |