Spring Security 3.0中如何实现CSRF防护(无法升级Spring版本)
Hey there, I totally get your frustration—Spring Security 3.0 doesn’t have the built-in <csrf> tag or automatic CSRF protection that later versions offer, and being stuck in an enterprise environment with no immediate upgrade path makes this trickier. Let’s walk through a manual implementation that works with your Spring 3, Java 8, and JBoss 5 setup.
Since the framework doesn’t handle this out of the box, we’ll build the protection in three key steps: generating a CSRF token, injecting it into your forms, and validating it on incoming requests.
1. Generate & Store CSRF Tokens in the Session
First, we need a way to create a unique CSRF token for each user session and store it securely. You can add this logic to a utility class or a request interceptor:
import java.util.UUID; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; public class CsrfTokenManager { private static final String CSRF_SESSION_KEY = "_csrf"; public static String getOrGenerateCsrfToken(HttpServletRequest request) { HttpSession session = request.getSession(); String csrfToken = (String) session.getAttribute(CSRF_SESSION_KEY); // Generate a new token if none exists in the session if (csrfToken == null) { csrfToken = UUID.randomUUID().toString(); session.setAttribute(CSRF_SESSION_KEY, csrfToken); } return csrfToken; } }
To ensure a token is always available, you can add this to a Spring HandlerInterceptor that runs on every request:
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; public class CsrfTokenInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { CsrfTokenManager.getOrGenerateCsrfToken(request); return true; } }
Register the interceptor in your Spring MVC config:
<mvc:interceptors> <bean class="com.yourcompany.web.interceptor.CsrfTokenInterceptor"/> </mvc:interceptors>
2. Inject the Token into Forms & AJAX Requests
Next, you need to include the CSRF token in all requests that modify data (POST/PUT/DELETE).
For HTML Forms
Add a hidden input field to every form that uses a non-safe HTTP method:
<form method="post" action="/submit-form"> <!-- CSRF Token --> <input type="hidden" name="_csrf" value="${sessionScope._csrf}"/> <!-- Your form fields here --> <input type="text" name="username"/> <button type="submit">Submit</button> </form>
For AJAX Requests
If your app uses AJAX, send the token in a request header (e.g., X-CSRF-Token). You can pull it from the session or a meta tag:
<!-- Add this meta tag in your base template --> <meta name="_csrf" content="${sessionScope._csrf}"/>
Then in your JavaScript:
const csrfToken = document.querySelector('meta[name="_csrf"]').content; fetch('/api/update-data', { method: 'POST', headers: { 'X-CSRF-Token': csrfToken, 'Content-Type': 'application/json' }, body: JSON.stringify({ data: 'example' }) });
3. Validate the Token with a Custom Filter
Create a Spring OncePerRequestFilter to check the CSRF token on non-safe requests. This filter will sit in the Spring Security chain to block invalid requests:
import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.filter.OncePerRequestFilter; public class CsrfValidationFilter extends OncePerRequestFilter { private static final String CSRF_PARAM_NAME = "_csrf"; private static final String CSRF_HEADER_NAME = "X-CSRF-Token"; private static final String CSRF_SESSION_KEY = "_csrf"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // Skip validation for safe HTTP methods (GET/HEAD/OPTIONS) String method = request.getMethod(); if ("GET".equalsIgnoreCase(method) || "HEAD".equalsIgnoreCase(method) || "OPTIONS".equalsIgnoreCase(method)) { filterChain.doFilter(request, response); return; } // Get token from session String sessionToken = (String) request.getSession().getAttribute(CSRF_SESSION_KEY); if (sessionToken == null) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "CSRF Token not found in session"); return; } // Get token from request (either parameter or header) String requestToken = request.getParameter(CSRF_PARAM_NAME); if (requestToken == null) { requestToken = request.getHeader(CSRF_HEADER_NAME); } // Validate tokens match if (requestToken == null || !sessionToken.equals(requestToken)) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid or missing CSRF Token"); return; } // Continue with the filter chain if valid filterChain.doFilter(request, response); } }
Register this filter in your Spring Security XML config, placing it before the form login filter to catch invalid requests early:
<http auto-config="true"> <!-- Register custom CSRF filter --> <custom-filter ref="csrfValidationFilter" before="FORM_LOGIN_FILTER"/> <!-- Your existing security rules here --> <intercept-url pattern="/**" access="ROLE_USER"/> </http> <beans:bean id="csrfValidationFilter" class="com.yourcompany.security.CsrfValidationFilter"/>
Key Notes
- Token Expiry: The token will expire when the user’s session expires (default JBoss session settings apply). You can add logic to rotate tokens on each request if needed for extra security.
- Edge Cases: Make sure to cover all non-safe requests—including those from third-party libraries or hidden forms.
- Upgrade Path: Using the
_csrfkey matches later Spring Security versions, so if you upgrade eventually, you can easily replace this manual implementation with the built-in one.
内容的提问来源于stack exchange,提问作者Beth




