如何构建Spring MVC应用实现认证后重定向至原请求URL
Great question! This is a super common issue when building authentication flows with Spring Security, and it boils down to not preserving the user's original requested URL before redirecting them to the login page. Let's break down exactly how to fix this, along with the key Spring Security concepts involved.
Key Background: How Spring Security Handles Anonymous Users & Saved Requests
First, let's clarify the mechanics behind the scenes:
- Anonymous User Detection: Spring Security uses the
AnonymousAuthenticationFilterto automatically assign an anonymous authentication token to unauthenticated users. This lets the framework distinguish between unauthenticated requests and fully anonymous traffic, triggering the redirect to login when a protected resource is accessed. - Saved Request Storage: When an anonymous user tries to access a protected endpoint, the
ExceptionTranslationFiltercatches the resultingAuthenticationExceptionand saves the original request details (including the URL) to the user'sHttpSessionusing the keySPRING_SECURITY_SAVED_REQUEST. This is the critical piece we'll leverage to redirect back after login.
Solution 1: Use Spring's Built-In SavedRequestAwareAuthenticationSuccessHandler
Spring Security already has a built-in handler that handles this exact scenario—you might just be overriding it without realizing it. Here's how to configure it properly:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // Define access rules .authorizeRequests() .antMatchers("/login", "/css/**", "/js/**").permitAll() // Allow public access to login and static assets .anyRequest().authenticated() // All other endpoints require authentication .and() // Configure form login .formLogin() .loginPage("/login") // Your custom login page URL // Use the default handler that automatically reads the saved request .successHandler(new SavedRequestAwareAuthenticationSuccessHandler()) .and() // Configure logout (optional but recommended) .logout() .permitAll(); } }
This handler will automatically check the session for the saved request and redirect the user there after successful authentication. If no saved request exists (e.g., the user navigated directly to the login page), it will fall back to the default homepage.
Solution 2: Custom AuthenticationSuccessHandler for Advanced Logic
If you need to add custom behavior (like conditional redirects based on user roles, or validating redirect URLs), you can implement your own handler:
public class CustomAuthSuccessHandler implements AuthenticationSuccessHandler { private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { // Retrieve the saved request from the session SavedRequest savedRequest = (SavedRequest) request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST"); if (savedRequest != null) { // Redirect to the original requested URL String targetUrl = savedRequest.getRedirectUrl(); redirectStrategy.sendRedirect(request, response, targetUrl); } else { // Fallback to default homepage if no saved request exists redirectStrategy.sendRedirect(request, response, "/"); } // Optional: Invalidate the saved request to avoid reuse request.getSession().removeAttribute("SPRING_SECURITY_SAVED_REQUEST"); } }
Then register it in your security config:
.formLogin() .loginPage("/login") .successHandler(new CustomAuthSuccessHandler())
Solution 3: Stateless/API-Friendly Redirects (No Session)
If you're building a stateless application (like a REST API) where sessions aren't used, you can pass the original URL as a request parameter to the login page:
- Add a hidden field to your login form to carry the redirect URL:
<form action="/login" method="post"> <!-- Your username/password fields --> <input type="hidden" name="redirect" value="${param.redirect}"> <button type="submit">Login</button> </form>
- Update your custom success handler to read and validate this parameter (always validate to prevent open redirect attacks!):
@Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { String redirectUrl = request.getParameter("redirect"); // Validate the redirect URL to ensure it's within your domain if (StringUtils.hasText(redirectUrl) && isValidRedirectUrl(redirectUrl)) { redirectStrategy.sendRedirect(request, response, redirectUrl); } else { redirectStrategy.sendRedirect(request, response, "/"); } } // Helper method to validate redirect URLs private boolean isValidRedirectUrl(String url) { // Example: Ensure the URL starts with your app's base path return url.startsWith("/") && !url.contains("://"); }
Critical Notes to Avoid Issues
- Ensure Public Access to Login: Always allow anonymous access to your login page and related static assets (CSS, JS) to avoid infinite redirect loops.
- Clean Up Saved Requests: After redirecting, consider removing the saved request from the session to prevent unexpected behavior if the user logs in again later.
- Validate Custom Redirects: If using parameter-based redirects, never trust the input blindly—validate that it points to a URL within your application to avoid security risks.
内容的提问来源于stack exchange,提问作者Shubham Sharma

