I'd like to protect my login for with a captcha, therefore I configured a filter for the authentication process. Captcha validation and authentication still works as I can see from the logs but after authentication I'm not redirected back to the client. Here is my configuration:
@Bean
@Order(2)
fun defaultSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
val authFilter = authenticationFilter()
if (captchaEnabled) {
http.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter::class.java)
}
val filterChain =
http.authorizeHttpRequests { authorize ->
authorize
.requestMatchers(
"/api/v1/auth-service/.well-known/jwks.json",
"/error",
"/login",
"/images/**",
"/css/**",
"/js/**",
"/templates/**",
"favicon.ico",
).permitAll()
.anyRequest().authenticated()
}
.formLogin { it.loginPage("/login").permitAll() }
.cors { it.configurationSource(corsConfigurationSource()) }.build()
if (captchaEnabled) {
val authManager = http.getSharedObject(AuthenticationManager::class.java)
authFilter.setAuthenticationManager(authManager)
}
return filterChain
}
fun authenticationFilter() =
CaptchaLoginFilter(captchaService).apply {
setAuthenticationFailureHandler(SimpleUrlAuthenticationFailureHandler("/login?error=true"))
}
If I disable the feature flag captchaEnabled
or just comment out the line starting with http.addFilterBefore
, it works as expected.
The code of my filter:
class CaptchaLoginFilter(
private val captchaService: CaptchaService?,
) : UsernamePasswordAuthenticationFilter() {
override fun attemptAuthentication(
request: HttpServletRequest,
response: HttpServletResponse,
): Authentication? {
if (!request.method.equals("POST")) {
throw AuthenticationServiceException("Authentication method not supported: " + request.method)
}
val recaptchaFormResponse = request.getParameter("g-recaptcha-response")
try {
captchaService?.processResponse(
recaptchaFormResponse,
request.remoteAddr,
null,
request.getHeader("User-Agent"),
)
} catch (e: Exception) {
when (e) {
is RecaptchaException -> {
// TODO
// request.getRequestDispatcher("otp_login").forward(request, response)
}
else ->
try {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.message)
} catch (ioException: IOException) {
ioException.printStackTrace()
}
}
return null
}
try {
return super.attemptAuthentication(request, response)
} catch (e: Exception) {
when (e) {
is AuthenticationException -> {
throw e
}
else -> {
logger.warn("Unexpected exception during authentication", e)
response.sendRedirect("/login?error")
return null
}
}
}
}
}
I can see from the logs that authentication succeeds just as if this filter was disabled so this means that super.attemptAuthentication(request, response) is called properly at the end. And I don't touch the request and response objects in this scenario so I don't know what changes the behavior. Any ideas? Thanks in advance.
You replaced the entire usernamepasswordfilter
. This resulted in some default configurations not being loaded. You can refer to FormLoginConfigurer
and AbstractAuthenticationFilterConfigurer
for configuration. Here, you don't have a valid successHandler
.