如何用拦截式Servlet Filter触发登录弹窗?Java Web应用URL保护
Great question! Implementing this kind of protected resource access without disrupting the parent page is totally doable with a Servlet Filter paired with frontend handling. Let's walk through the solution step by step:
1. Create the Servlet Filter
First, we'll build a Filter that targets all /private/* resources. The core logic here is to check for a logged-in user in the session; if missing, return a 401 Unauthorized response instead of redirecting (which would break the parent page).
@WebFilter("/private/*") public class PrivateResourceFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpReq = (HttpServletRequest) request; HttpServletResponse httpResp = (HttpServletResponse) response; // Check if a logged-in user exists in the session Object loggedInUser = httpReq.getSession().getAttribute("loggedInUser"); if (loggedInUser != null) { // User is authenticated: pass the request through chain.doFilter(request, response); } else { // User not logged in or session expired: return 401 without redirecting httpResp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // Optional: send a JSON message to help frontend handle the error httpResp.setContentType("application/json"); httpResp.getWriter().write("{\"error\":\"Authentication required to access this resource\"}"); // Prevent browser's default auth popup by setting a custom WWW-Authenticate header httpResp.setHeader("WWW-Authenticate", "Custom"); } } @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void destroy() {} }
2. Frontend: Intercept 401 Responses and Show Login Modal
Since we're returning a 401 instead of redirecting, we need frontend code to catch this status code and trigger your custom login popup. This works best if all requests to /private/* are made via AJAX/fetch (so the browser doesn't handle the 401 natively).
Here's a global fetch interceptor to handle this:
// Override the default fetch to intercept responses const originalFetch = window.fetch; window.fetch = async function(url, options) { const response = await originalFetch(url, options); // Check if the request was to a private resource and returned 401 if (response.status === 401 && url.includes('/private/')) { // Trigger your custom login modal showLoginPopup(); // Return a modified response to prevent default error handling return new Response(JSON.stringify({unauthorized: true}), {status: 401}); } return response; }; // Example function to show your login modal (adjust to match your UI) function showLoginPopup() { // If using Bootstrap modals: const loginModal = new bootstrap.Modal(document.getElementById('loginModal')); loginModal.show(); // For a custom modal, replace with your own logic: // document.getElementById('loginModal').style.display = 'block'; }
3. Handle Login Success and Retry Requests
After the user logs in successfully, you'll want to re-fetch the private resource that triggered the 401, so the parent page updates without reloading.
First, the login form handler:
document.getElementById('loginForm').addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(e.target); const loginResponse = await fetch('/login', { method: 'POST', body: formData }); if (loginResponse.ok) { // Close the login modal const loginModal = bootstrap.Modal.getInstance(document.getElementById('loginModal')); loginModal.hide(); // Retry the last failed private resource request (optional) // You can store the failed request details before showing the modal retryLastPrivateRequest(); } else { alert('Login failed! Please check your credentials.'); } }); // Example: Store and retry the failed request let lastFailedRequest = null; // Modify the fetch interceptor to store failed requests: window.fetch = async function(url, options) { const response = await originalFetch(url, options); if (response.status === 401 && url.includes('/private/')) { lastFailedRequest = {url, options}; showLoginPopup(); return new Response(JSON.stringify({unauthorized: true}), {status: 401}); } return response; }; function retryLastPrivateRequest() { if (lastFailedRequest) { fetch(lastFailedRequest.url, lastFailedRequest.options) .then(res => res.json()) .then(data => { // Update the parent page with the fetched data updatePrivateContent(data); lastFailedRequest = null; }); } } function updatePrivateContent(data) { // Replace with your logic to update the page's private section document.getElementById('privateContentContainer').textContent = JSON.stringify(data); }
Key Notes
- Avoid redirects/forwards: Using a 401 response ensures the parent page's URL and state stay intact. Redirecting would break the user's current context.
- Session expiration handling: When the session expires,
session.getAttribute("loggedInUser")returns null, so the Filter automatically treats it as an unauthenticated request—no extra code needed. - Async requests only: If users navigate directly to
/private/*via a link, the browser will handle the 401 natively (showing its own auth popup). To prevent this, ensure all private resource access is done via AJAX/fetch, or add a check on initial page load to redirect to the homepage if unauthenticated (but that's a separate flow).
内容的提问来源于stack exchange,提问作者R. Dafoe

