using java configuration for n-factor authentication [closed] using java configuration for n-factor authentication [closed] spring spring

using java configuration for n-factor authentication [closed]


First, some explanation about the interfaces you are working with and the role they play in the authentication process:

  • Authentication - represents the result of authenticating a user. Holds the authorities granted to that user and any additional details that may be needed about the user. As there is no way for the framework to know, what details are going to be needed, the authentication object has a getDetails method that can return any object

  • AuthenticationProvider - object that can create an Authentication object in some way. To make them more reusable, some (or most) of AuthenticationProviders refrain from setting the user details on the Authentication object, as each application may need specific user details. Instead they delegate the process of resolving the user details to a settable UserDetailsService

  • UserDetailsService - a strategy for retrieving the user details required in your application.

So, if you are creating a custom AuthenticationProvider you may not even need to implement it in a way that requires a UserDetailsService. The decission is up to you and depends, on whether you plan on reusing your implementation in other projects.

As for the compilation problems in your code, you are mixing two ways of providing the UserDetailsService. In the CustomAuthenticationProvider you have annotated the userService field with the @Inject annotation .This means, that the container (Spring application context in your case) is to find a suitable implementation and inject it into that field at runtime using reflection. The process of setting this field by the context is called dependency injection. In the SecurityConfig class you are trying to provide the implementation yourself by setting the field through the setUserDetailsService method that does not exist in your class.

To resolve this problem you need to decide to use one of the ways to provide the UserDetails service and either:

  • remove the @Inject annotation and create the setUserDetailsService method, or
  • remove the line when you are calling the non-existant method and declare your implementation of the UserDetailsService as a bean

As for which of the ways should you choose, the dependecy injection way may by better if you can find a way of making your SecurityConfig class reusable in other projects. In that case you could just import it (by using the @Import annotaion) and declare a different UserDetailsSerice implementation as a bean in your next application and have it working.

Usually, classes like the SecurityConfig are not really reusable, so creating the setter and removing the dependency injection would probably be my first choice.

EDIT

A working, albeit a simplistic implementation (based heavily on this blog entry) would be:

public class CustomAuthenticationProvider implements AuthenticationProvider{    @Override    public Authentication authenticate(Authentication authentication) throws AuthenticationException {        String name = authentication.getName();        String password = authentication.getCredentials().toString();        List<GrantedAuthority> grantedAuths = new ArrayList<>();        if (name.equals("admin") && password.equals("system")) {            grantedAuths.add(new SimpleGrantedAuthority("ROLE_ADMIN"));          }         if(pincodeEntered(name)){            grantedAuths.add(new SimpleGrantedAuthority("ROLE_PINCODE_USER"));          }        Authentication auth = new UsernamePasswordAuthenticationToken(name, password, grantedAuths);    }    @Override    public boolean supports(Class<?> authentication) {        return authentication.equals(UsernamePasswordAuthenticationToken.class);    }    private boolean pincodeEntered(String userName){        // do your check here        return true;    }}

Then in your config class change the following method:

@BeanAuthenticationProvider customAuthenticationProvider() {        return new CustomAuthenticationProvider();}


The first thing we need to do is extend the UsernamePasswordAuthenticationFilter class so that it can handle a second input field.

public class TwoFactorAuthenticationFilter extends UsernamePasswordAuthenticationFilter{    private String extraParameter = "extra";    private String delimiter = ":";    //getters and setters    @Override    protected String obtainUsername(HttpServletRequest request)    {        String username = request.getParameter(getUsernameParameter());        String extraInput = request.getParameter(getExtraParameter());        String combinedUsername = username + getDelimiter() + extraInput;        return combinedUsername;    }}

obtainUsername() This method is to retrieve the username and “extra” input field from the HttpServletRequest object that’s passed in.

It then concatenates these two values into one string, separating them by the delimiter string (a colon, by default).

It then returns this combined string. The parameter from which the “extra” input field is read is extra by default.

UserDetailsService should look like this:

@Overridepublic UserDetails loadUserByUsername(String input) throws UsernameNotFoundException, DataAccessException{    String[] split = input.split(":");    if(split.length < 2)    {        throw new UsernameNotFoundException("Must specify both username and corporate domain");    }    String username = split[0];    String domain = split[1];    User user = userDao.findByUsernameAndDomain(username, domain);    if(user == null)    {        throw new UsernameNotFoundException("Invalid username or corporate domain");    }    return user;}

Split the given username into its two components: the username and the extra field. In this example, the extra field is the user’s corporate domain.

Once we have the username and the domain, we can use our DAO to find the matching user.

Last Puzzle:

TwoFactorAuthenticationFilter:

    <http use-expressions="true" auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">        <intercept-url pattern="/secured" access="isAuthenticated()" />        <intercept-url pattern="/**" access="permitAll" />        <custom-filter position="FORM_LOGIN_FILTER" ref="twoFactorAuthenticationFilter" />        <logout logout-url="/logout" />    </http>    <authentication-manager alias="authenticationManager">        <authentication-provider ref="authenticationProvider" />    </authentication-manager>    <beans:bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">        <beans:property name="passwordEncoder">            <beans:bean class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" />        </beans:property>        <beans:property name="userDetailsService" ref="userService" />    </beans:bean>    <beans:bean id="userService" class="com.awnry.springexample.UserDetailsServiceImpl" />    <beans:bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">        <beans:property name="loginFormUrl" value="/login" />    </beans:bean>    <beans:bean id="twoFactorAuthenticationFilter" class="com.awnry.springexample.TwoFactorAuthenticationFilter">        <beans:property name="authenticationManager" ref="authenticationManager" />        <beans:property name="authenticationFailureHandler" ref="failureHandler" />        <beans:property name="authenticationSuccessHandler" ref="successHandler" />        <beans:property name="filterProcessesUrl" value="/processLogin" />        <beans:property name="postOnly" value="true" />        <beans:property name="extraParameter" value="domain" />    </beans:bean>    <beans:bean id="successHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">        <beans:property name="defaultTargetUrl" value="/login" />    </beans:bean>    <beans:bean id="failureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">        <beans:property name="defaultFailureUrl" value="/login?login_error=true" />    </beans:bean>

In twoFactorAuthenticationFilter bean definition, we set the extraParameter property to “domain” which is the name of the input field to use in our login form.

EDIT:

Have a look into constructors of User class.

If you don't know what a granted authority get into a look over this below link:

http://docs.spring.io/autorepo/docs/spring-security/3.2.1.RELEASE/apidocs/org/springframework/security/core/GrantedAuthority.html

Your coding gives a different mode applicable only for normal username and password. My code works for n factor authentication. Try switch over to my code if any problem keep on persists.


I'm very concious that this post has undergone 28 edits, so I may have missed some context. I'm also concious that you've amalgamated some code from the other answers into your question and that the problem has been somewhat "turned on its head" from "why won't a valid user authenticate?" to "why does every user authenticate?".

Current problem.

However, as written, your CustomAuthenticationProvider.authenticate() method will always return an Authentication object that returns auth.isAuthenticated() == true because you instantiate using this method which warns you about that very thing. Even if the collection you passed in as the third argument were empty, this would be the case. In fact, the collection always contains a GrantedAuthority for "registered", because pincodeEntered(name) always returns true. So, you need to correct your logic in those methods. authenticate() should return null if authentication is not successful.

Next steps

You've indicated in comments that what you want is a reference implementation of multi factor authentication. This is problematic - there is not necessarily agreement on what would constitute such a thing. For instance some would argue that multi factor should include a possession factor, rather than n knowledge factors on a single login page. It's also not really suited to an SO answer as it would need a blog post (or a series) - however generous the bounty.

There are working examples of multi factor authentication in spring on the web, here and here, for example. The latter I think you must have discovered as you appear to be using some of the code from there.

Making your CustomAuthenticationProvider work might take hours. Debugging might take even longer, as you have a mixture of methods in your example - it is not minimal. In particular, the TwoFactorAuthenticationFilter class is supposed to be used to intercept input on a request from the login page and concatenate the username and pin. In the example from the blog, this is set up in XML - you could add the security namespace to your business-config.xml and add those beans there for instance.

However, the SecurityConfig class and CustomAuthenticationProvider is a different method again.

Next, your project code references a j_security_check url, but that URL is not handled by anything. I am not sure of the intent behind that, or where it comes from. Finally, the MVC config for URL routing adds another element to the mix - one which I'm not familiar with.

I've played with your example for a while. There are too many mixed methods and too much complexity for me to fix quickly - maybe others can.

I strongly suggest that you start from the example in the blog exactly, then add the mvc config you want to over the top of that.

N.B. Setup for others trying to get the example to work

There were a couple of wrinkles in setting the project - it had an un-needed and unsatisfied dependency on javax.mail, you need to publish the maven dependencies to the server (in project->properties->deployment assembly) and you need to download and install adapters for the tomcat server if you don't already have it.

You also need to create the tables and columns in your database.