Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to catch Spring Security login form before Spring gets it?

I have a Spring login form with username and password, which points to /myapp/j_spring_security_check.

I am hoping to intercept the form submission when someone submits the login form before Spring gets the request.

Basically, I am hoping to to be able to review user input to see if it meets certain requirements. If it does not, the application will take the user back to the login form. If user input meets the requirements, then the flow goes to Spring authentication.

How can I do this? In an efficient way?

Thanks!

like image 938
curious1 Avatar asked Jan 27 '12 03:01

curious1


2 Answers

You can do this using regular Spring Security functionality. The steps are:

  1. Implement a custom WebAuthenticationDetailsSource and WebAuthenticationDetails. The WebAuthenticationDetails will capture the extra form fields that you want to validate.

    Note: In Spring 3.0 you'll need to use a BeanPostProcessor to configure the WebAuthenticationDetailsSource into the UsernamePasswordAuthenticationFilter. In Spring 3.1 you can do this directly in the <form-login> namespace config.

  2. Implement a custom AuthenticationProvider and in authenticate() check the WebAuthenticationDetails, throwing a AuthenticationException if validation fails. In your login page check for this exception.

Alternatively, you can create a Filter that does your validation and add it before the Spring Security filter.

like image 78
sourcedelica Avatar answered Nov 27 '22 07:11

sourcedelica


Using the answer above as a guide, this is a working example I created of a post-processor that lets you specify which login form variables to provide to the authenticator, and an example custom authenticator that checks for a terms_of_service checkbox value in the login form.

In the Spring configuration:

<bean id="authFormDetailsPostProcessor" class="com.sefaira.authauth.AuthFormDetailsPostProcessor">
  <property name="formVarNames" value="terms_of_service_accepted"/>
</bean>

AuthFormDetailsPostProcessor.java:

public class AuthFormDetailsPostProcessor implements BeanPostProcessor {

    private String [] formVarNames;

    public void setFormVarNames (String formVarNames) { 
    this.formVarNames = formVarNames.split (",");
    }

    public static class Details extends WebAuthenticationDetails {

    private Map<String, String> map;

    public Details (HttpServletRequest request, String [] parameters) {
        super (request);

        this.map = new HashMap<String, String>();
        for (String parameter : parameters) {
        this.map.put (parameter.trim(), request.getParameter (parameter.trim()));
        }
    }

    public String get (String name) {
        return map.get(name);
    }
    }

    public Object postProcessAfterInitialization(Object bean, String name) {
        if (bean instanceof UsernamePasswordAuthenticationFilter) {
            ((UsernamePasswordAuthenticationFilter)bean).setAuthenticationDetailsSource(
                    new AuthenticationDetailsSource() {
                        public Object buildDetails(Object context) {

                if (formVarNames == null) {
                throw new RuntimeException ("AuthFormDetailsPostProcessor bean requires a formVarNames property, specifying a comma-delimited list of form vars to provide in the details object.");
                }

                return new Details ((HttpServletRequest) context, formVarNames);
                        }
                    });
        }
        return bean;
    }

    public Object postProcessBeforeInitialization(Object bean, String name) {
        return bean;
    }
}

This is the custom Authenticator that uses it:

public class AuthServiceAuthenticator implements AuthenticationProvider {

   @Override
    public Authentication authenticate (Authentication authentication) throws AuthenticationException {

    String email = (String) authentication.getPrincipal();  
    String password = (String) authentication.getCredentials();

    AuthFormDetailsPostProcessor.Details details = (AuthFormDetailsPostProcessor.Details) authentication.getDetails();

    // see if they checked the terms_of_service checkbox        
    String termsOfServiceVar = details.get ("terms_of_service_accepted");
    boolean termsOfServiceAccepted = (termsOfServiceVar != null && termsOfServiceVar.equals ("on"));

        // ... do your custom authentication ...

        return authentication;  // or a new authentication object
    }

    @Override
    public boolean supports(Class<? extends Object> authentication) {
        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
    }
}
like image 31
Jake Avatar answered Nov 27 '22 06:11

Jake



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!