Search code examples
javaspring-bootspring-securityoauth-2.0spring-authorization-server

Spring Authorization Server does not redirect back with authorization code grant when using filter


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.


Solution

  • 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.