javaspring-bootspring-securityspring-filter

How to create a custom authentication filter in Spring Security?


I'm trying to create a custom Spring Security Authentication Filter in order to implement a custom authentication scheme. I've spent a couple hours reading up on Spring Security, but all of the guides I've found explain how to configure basic setups; I'm trying to write a custom setup, and I'm having trouble finding documentation on how to do so.

For the sake of example, lets say that my custom authentication scheme is as follows: If the client provides a "foo_username" header and a "foo_password" header in the http request (both unencrypted for the sake of example), then my custom filter needs to construct a UsernamePasswordAuthenticationToken. Of course if the password is wrong, that's an authentication error. If either header is missing that's an authentication error. If both headers are missing, I want to delegate down the filter chain without changing anything.

That seems simple enough in theory, but I don't know how to implement that in Spring. Am I intended to check the password against the DB myself? or is that the reponsibility of the UserDetailsPasswordService? Am I intended to modify the SecurityContextHolder.getContext().authentication field myself? What responsibilities do I delegate to the AuthenticationManager? Which exceptions do I throw when authentication fails in various ways? Do I implement Filter, OncePerRequestFilter, or AbstractAuthenticationFilter? Is there any documentation on how to do all this???

Admittedly, this is a duplciate of How to create your own security filter using Spring security?, but I'm not him, and he got no answers.

Thanks for the help!


Solution

  • Edit: This is the not best way to do things. It doesn't follow best practices.

    As others have pointed out, it's better to use Basic auth or OAuth2, both of which are built into Spring. But if you really want to implement a custom filter, you can do something like this. (Please correct me if I'm doing this wrong.) But don't do this exactly. This is not a very secure example; it's a simple example.

    class CustomAuthenticationFilter(val authManager: AuthenticationManager) : OncePerRequestFilter() {
    
        override fun doFilterInternal(request: HttpServletRequest,
                                      response: HttpServletResponse,
                                      chain: FilterChain) {
    
            val username = request.getHeader("foo_username")
            val password = request.getHeader("foo_password")
    
            if(username==null && password==null){
                // not our responsibility. delegate down the chain. maybe a different filter will understand this request.
                chain.doFilter(request, response) 
                return
            }else if (username==null || password==null) {
                // user is clearly trying to authenticate against the CustomAuthenticationFilter, but has done something wrong.
                response.status = 401
                return
            }
    
            // construct one of Spring's auth tokens
            val authentication =  UsernamePasswordAuthenticationToken(username, password, ArrayList())
            // delegate checking the validity of that token to our authManager
            val userPassAuth = this.authManager.authenticate(authRequest)
            // store completed authentication in security context
            SecurityContextHolder.getContext().authentication = userPassAuth
            // continue down the chain.
            chain.doFilter(request, response)
        }
    }
    

    Once you've created your auth filter, don't forget to add it to your HttpSecurity config, like this:

    override fun configure(http: HttpSecurity?) {
        http!!.addFilterBefore(CustomAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter::class.java)
    }