6

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!

Mathew Alden
  • 1,458
  • 2
  • 15
  • 34

2 Answers2

9

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)
}
Mathew Alden
  • 1,458
  • 2
  • 15
  • 34
1

I think what you want to do is implement AuthenticationProvider. It allows your code to explicitly manage the authentication portion. It has a fairly simple method signature to implement as well.

public class YourAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();
        ...
        return new UsernamePasswordAuthenticationToken(principal, password, principal.getAuthorities())
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

You can register it by adding it to the AuthenticationManagerBuilder in a config that extends WebSecurityConfigurerAdapter

@Configuration
class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        AuthenticationProvider provider= new YourAuthenticationProvider();
        auth.authenticationProvider(provider);
    }
}
Deadron
  • 5,135
  • 1
  • 16
  • 27
  • Thanks, but this doesn't really answer the question I was trying to ask. I can already use a `org.springframework.security.authentication.dao.DaoAuthenticationProvider` to construct a `UsernamePasswordAuthenticationToken`. My question is more along the lines of - what do I need to do in order to intercept http requests and pull the username and password out of the headers so that I can pass them into the `AuthenticationProvider`? And what do I do with the output of the `AuthenticationProvider`? – Mathew Alden Jan 13 '21 at 19:21
  • You don't intercept the requests. That is handled by Spring security for you automatically(assuming you don't have a weird config). Your AuthenticationProvider is registered to inform Spring Security that you want to manually handle the authentication portion of the request. You just need to make sure you return a proper Authentication object from your Provider. It will invoke your provider when needed and make the returned Authentication object available to various parts of the application. – Deadron Jan 13 '21 at 19:29
  • But the custom authentication scheme I need to implement (which is slightly more complex than the example I used for this question) includes the user/pass in specific headers that Spring won't know to read from by default. So I'll need some code to read from the headers; I just haven't figured out what. – Mathew Alden Jan 13 '21 at 19:33
  • 2
    If you are using something other than the Basic Auth header you may consider refactoring to use that instead. Reinventing an existing mechanic is only going to make your life more difficult. – Deadron Jan 13 '21 at 19:35
  • See, I wanted to refactor (either to Basic or OAuth2), but I was asked to write something custom. Thanks anyway though! – Mathew Alden Jan 13 '21 at 19:42
  • writing something custom is bad practice, and a high security risk which under any circumstances should not be done. Even if you get asked to do it. – Toerktumlare Jan 14 '21 at 13:09
  • @Toerktumlare In this case I think that there is little to no risk. Mostly its just nonstandard which makes it kind of a pain to use with an existing framework. It also might be more confusing to use for users. – Deadron Jan 14 '21 at 15:33
  • how to do this in latest version @Bean SecurityFilterChain defaultSecurityFilterChain – Feroz Siddiqui Sep 15 '22 at 20:21