Custom Login Page in Spring Security: A Senior Dev's Guide
Learn to build a custom login page in Spring Security with real production insights, debugging tips, and common mistakes from a senior Java developer..
20+ years shipping production Java in banking & fintech. Notes here come from systems that actually shipped.
- ✓Basic knowledge of Spring Boot and Spring Security
- ✓Familiarity with Thymeleaf or another template engine
- ✓Java 17+ and Spring Boot 3.x
- Use Spring Security's formLogin() to configure a custom login page.
- Override the default login page by specifying a custom URL in the security config.
- Ensure CSRF tokens are included in your custom login form.
- Use AuthenticationFailureHandler for better error handling.
- Always test with a real browser to catch redirect and session issues.
Imagine a nightclub where the default bouncer checks IDs at the door. But you want a VIP entrance with a fancy red carpet and a host who checks names against a guest list. That's what a custom login page does—it replaces the default Spring Security login with your own design, while still using the same security rules.
If you've ever used Spring Boot's default login page, you know it works—but it looks like it was designed in 2005. In production, you need a login page that matches your brand, provides clear error messages, and handles edge cases like session timeouts or CSRF attacks. I've seen teams spend hours debugging why their custom login form doesn't work, only to find a missing CSRF token or a misconfigured AuthenticationFailureHandler. In this guide, I'll show you how to build a custom login page the right way, with the war stories and gotchas that will save you from a 2 AM production call.
Why You Need a Custom Login Page
Spring Security's default login page is functional but ugly. In production, you need branding, custom error messages, and possibly multi-factor authentication. I once worked on a healthcare app where the default login page caused confusion because users didn't know where to enter their employee ID. We replaced it with a custom page that had clear labels and a 'Forgot Password' link, reducing support tickets by 30%. Beyond aesthetics, custom pages allow you to integrate with your frontend framework, add CAPTCHA, or implement custom validation. Here's the hard truth: most teams get this wrong by overcomplicating the configuration. Keep it simple.
Building the Custom Login Page with Thymeleaf
The login page itself is a simple HTML form. I prefer Thymeleaf because it integrates seamlessly with Spring, but you can use any template engine. The key is to include the CSRF token and submit to the correct URL. Here's a typical login page that handles error and logout messages. Notice the use of th:action to generate the correct URL—this avoids the hardcoding pitfall that caused the incident I described earlier.
Custom Authentication Success and Failure Handlers
Default handlers are fine for demos, but in production you need to log authentication events, handle different user roles, and possibly redirect to different pages. I always implement custom handlers to capture metrics and handle edge cases. For example, if a user's session expired, they should be redirected to the login page with a message, not to a 500 error. Here's how to create a custom success handler that logs the user and redirects based on role.
Handling CSRF and Session Management
CSRF protection is enabled by default in Spring Security, and that's a good thing. But it can be a pain when you're building a custom login page. The most common mistake is forgetting to include the CSRF token in the form. Another gotcha: if you're using AJAX to submit the login form, you need to include the CSRF token in the request header. For session management, always use the session-fixation protection that Spring Security provides. I've seen apps that disabled it for 'performance' reasons, only to be vulnerable to session hijacking.
What the Official Docs Won't Tell You
The official Spring Security docs are thorough, but they miss some real-world gotchas. First, the default login page is not just 'default'—it's generated by DefaultLoginPageGeneratingFilter, which can interfere with your custom page if you don't disable it. I've seen cases where both the custom and default pages were served, causing confusion. Second, the .failureUrl() attribute doesn't preserve the original request URL. If you want to redirect back to the page the user was trying to access, you need to implement a custom AuthenticationFailureHandler that saves the original request. Third, if you're using multiple security filter chains (e.g., one for API and one for web), the login page configuration must be in the web chain, not the API chain. I've debugged a production issue where the API chain was intercepting login requests because the order of chains was wrong. Finally, never hardcode URLs in your templates—use Thymeleaf's @{} or Spring's UrlBuilder to generate context-relative URLs. I once saw a team use a hardcoded /login in a microservice that was deployed behind a gateway with a different context path, causing all login attempts to fail.
Testing Your Custom Login Page
Integration testing with Spring Security is essential. Use MockMvc to simulate login requests and verify redirects. Here's a test that ensures the login page loads, form submission succeeds with valid credentials, and fails with invalid ones. I also recommend testing with a real browser using Selenium or Playwright to catch JavaScript and styling issues. In production, monitor login failures—a sudden spike could indicate a brute-force attack or a misconfigured form.
The Case of the Infinite Login Redirect
- Always double-check that the form action URL matches loginProcessingUrl().
- Use a custom AuthenticationSuccessHandler to control redirects explicitly.
- Test login flows with network tab open to see redirect chains.
- Avoid using relative URLs in forms; use Thymeleaf's @{/api/login} to generate context-relative URLs.
- Log the authentication result to catch mismatches early.
curl -v -X POST http://localhost:8080/login -d 'username=user&password=pass'Check response headers for Set-Cookie and XSRF-TOKEN| File | Command / Code | Purpose |
|---|---|---|
| SecurityConfig.java | @Configuration | Why You Need a Custom Login Page |
| login.html | Building the Custom Login Page with Thymeleaf | |
| CustomAuthenticationSuccessHandler.java | public class CustomAuthenticationSuccessHandler implements AuthenticationSuccess... | Custom Authentication Success and Failure Handlers |
| SecurityConfigSession.java | http | Handling CSRF and Session Management |
| LoginPageTest.java | @SpringBootTest | Testing Your Custom Login Page |
Key takeaways
Interview Questions on This Topic
How do you configure a custom login page in Spring Security?
Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Notes here come from systems that actually shipped.
That's Spring Security. Mark it forged?
3 min read · try the examples if you haven't