How to Build an Extensible Form Login with Spring Security
This tutorial walks through customizing Spring Security to create a scalable, extensible form‑login mechanism, covering the basic login flow, Spring Security's built‑in login options, FormLoginConfigurer settings, practical controller code, security configuration, and how to add multiple login types such as JSON or CAPTCHA.
1. Introduction
Previous articles about Spring Security were a warm‑up. The first step of secure access is authentication, and the first step of authentication is login. This article shows how to customize Spring Security to create an extensible, scalable form‑login.
2. Form login flow
Basic flow of a form login is illustrated below:
Any form login can be transformed into this flow. Next we see how Spring Security handles it.
3. Login in Spring Security
In the previous “WebSecurityConfigurerAdapter” article we saw that custom access control is usually built with HttpSecurity. Spring Security provides three login methods: formLogin() – standard form login oauth2Login() – OAuth2.0 authentication/authorization openidLogin() – OpenID authentication
All three are implemented by AbstractAuthenticationFilterConfigurer.
4. Form login with HttpSecurity
Form login can be enabled in two ways: by constructing a custom AbstractAuthenticationFilterConfigurer via HttpSecurity.apply(...) (advanced) or by using the common http.formLogin() which creates a FormLoginConfigurer. The article focuses on the latter.
4.1 FormLoginConfigurer
The class provides several configuration methods: loginPage(String) – custom login page (default “/login”) loginProcessingUrl(String) – URL that receives the username/password (handled by UsernamePasswordAuthenticationFilter) usernameParameter(String) – name of the username parameter (default “username”) passwordParameter(String) – name of the password parameter (default “password”) failureUrl(String) – redirect on failure (rarely used in SPA) failureForwardUrl(String) – forward on failure (can be handled by a controller) defaultSuccessUrl(String, boolean) – redirect after success; alwaysUse determines whether it is always used successForwardUrl(String) – forward on success (similar to defaultSuccessUrl with alwaysUse=true) successHandler(AuthenticationSuccessHandler) – custom success handling failureHandler(AuthenticationFailureHandler) – custom failure handling permitAll(boolean) – whether the form login endpoint is publicly accessible
With these options we can build a customized login.
5. Practical implementation
Endpoints must return JSON on success or failure. Example controller:
@RestController
@RequestMapping("/login")
public class LoginController {
@Resource
private SysUserService sysUserService;
@PostMapping("/failure")
public Rest loginFailure() {
return RestBody.failure(HttpStatus.UNAUTHORIZED.value(), "登录失败了,老哥");
}
@PostMapping("/success")
public Rest loginSuccess() {
User principal = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String username = principal.getUsername();
SysUser sysUser = sysUserService.queryByUsername(username);
sysUser.setEncodePassword("[PROTECT]");
return RestBody.okData(sysUser, "登录成功");
}
}Custom security configuration disables CSRF and sets the processing URL, success and failure forwards:
@Configuration
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class CustomSpringBootWebSecurityConfiguration {
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER)
static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.cors()
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/process")
.successForwardUrl("/login/success")
.failureForwardUrl("/login/failure");
}
}
}Testing with Postman:
http://localhost:8080/process?username=Felordcn&password=12345returns user data with status 200. Changing the password returns status 401 with an error message.
6. Extensible login types
To support multiple login styles (JSON, CAPTCHA, etc.) an adapter is added before UsernamePasswordAuthenticationFilter. Define an enum:
public enum LoginTypeEnum {
/** Original form login */
FORM,
/** JSON submission */
JSON,
/** CAPTCHA */
CAPTCHA
}Define a LoginPostProcessor interface to obtain username and password for each type, and implement a default processor for FORM. Then create a PreLoginFilter that selects the processor based on a request parameter login_type and populates the standard Spring Security parameters before delegating to the filter chain.
public class PreLoginFilter extends GenericFilterBean {
private static final String LOGIN_TYPE_KEY = "login_type";
private RequestMatcher requiresAuthenticationRequestMatcher;
private Map<LoginTypeEnum, LoginPostProcessor> processors = new HashMap<>();
public PreLoginFilter(String loginProcessingUrl, Collection<LoginPostProcessor> loginPostProcessors) {
// initialization logic…
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
ParameterRequestWrapper wrapper = new ParameterRequestWrapper((HttpServletRequest) request);
if (requiresAuthenticationRequestMatcher.matches((HttpServletRequest) request)) {
LoginTypeEnum type = getTypeFromReq(request);
LoginPostProcessor processor = processors.get(type);
String username = processor.obtainUsername(request);
String password = processor.obtainPassword(request);
wrapper.setAttribute(SPRING_SECURITY_FORM_USERNAME_KEY, username);
wrapper.setAttribute(SPRING_SECURITY_FORM_PASSWORD_KEY, password);
}
chain.doFilter(wrapper, response);
}
}Submitting a POST request with login_type=0 (FORM) works, and additional login types can be added by implementing LoginPostProcessor and registering it with the filter.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
