TeFa
TeFa

Reputation: 1004

How to authenticate a user by multiple parameters and not just by name in Spring?

I am trying to authenticate a user on login using Spring Security 3.2.5

This is a simple task and I managed to find many examples how to do so. My problem is that in my database, a user is unique by (username and group). I need to provide both values to my custom UserDetailsService in order to retrieve a user.

Can someone please guide me to the best solution in this case?

Here is what I came up with so far (I am confused so excuse me if I am mistaken)

security.xml

<security:http use-expressions="true">
    <security:custom-filter ref="myAuthenticationFilter" position="FORM_LOGIN_FILTER"/>
    <security:intercept-url pattern="/views/login*" access="isAnonymous()"/>
    <security:intercept-url pattern="/views/**" access="isAuthenticated()"/>
    <security:form-login login-page="/views/login.faces"
                         default-target-url="/"
                         username-parameter="username"
                         password-parameter="password"/>
    <security:logout logout-url="/logout" delete-cookies="JSESSIONID"/>
 </security:http>

<security:authentication-manager>
    <security:authentication-provider user-service-ref="myUserDetailsService" >
        <security:password-encoder ref="myPasswordEncoder"/>
    </security:authentication-provider>
</security:authentication-manager>

UPDATE [Custom Filter]

I tried to implement the custom filter as suggested by holmis83 but i got the following exception

BeanDefinitionParsingException: Configuration problem: Filter beans '<myAuthenticationFilter>' and '<org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#0>' have the same 'order' value. When using custom filters, please make sure the positions do not conflict with default filters. Alternatively you can disable the default filters by removing the corresponding child elements from <http> and avoiding the use of <http auto-config='true'>.

The custom-filter element I added to my security.xml

<security:custom-filter ref="myAuthenticationFilter" position="FORM_LOGIN_FILTER"/>

What am I doing wrong?

Upvotes: 2

Views: 3316

Answers (3)

Grzegorz Solecki
Grzegorz Solecki

Reputation: 308

The idea of creating your own implementation AuthenticationProvider is quite right. Then you have a very high degree of freedom. You can even override DaoAuthenticationProvider and create your own version of such MyDaoAuthenticationProvider.

However the cleaner way to do it imho is to create a proper Principal object. It looks like your username and group is like compound username that is:

username = requestUsername + delimiter + requestGroup; 

Then in this case it would be ok to extend UsernamePasswordAuthenticationFilter to create a proper Principal object and put it into UsernamePasswordAuthenticationToken so getName() method of Principal returns compound username and then inside MyUserDetailsService you split it back to requestUsername and requestGroup again and execute repo/dao/jdbc that fetches your user details.

Upvotes: 0

holmis83
holmis83

Reputation: 16614

As you probably have understood, there is no groups or domains in Spring Security authentication.

One way to go around this is to append the group name to the username (with a slash delimiter for example), so the username internally is "username/group".

If you still want username and group to be separate fields in your login form, you can do a custom authentication filter like this to concatenate username and group:

public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    @Override
    protected String obtainUsername(HttpServletRequest request) {
        String username = super.obtainUsername(request);
        String group = request.getParameter("group");
        username += "/" + group;
        return username;
    }
}

Then in your UserDetailsService you need to split them apart:

public UserDetails loadUserByUsername(String username) {
    int index = username.indexOf("/");
    String group = username.substring(index + 1);
    username = username.substring(0, index);
    // find the user by username and group
}

Upvotes: 2

Master Slave
Master Slave

Reputation: 28539

try something like this

@Autowired
private UserDetailsService userDetailsService;


@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationServiceException {
    try {
        if (authentication instanceof MyUsernamePasswordGroupAuthenticationToken) {
            User user = userDetailsService.loadUserByNameAndGroup(((MyUsernamePasswordGroupAuthenticationToken) authentication).getName(), ((MyUsernamePasswordGroupAuthenticationToken) authentication).getGroup());
            if (user != null) {
                Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
                grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_AUTHENTICATED_USER"));
                Authentication endUserAuth = new MyUsernamePasswordGroupAuthenticationToken(user, grantedAuthorities);
                authentication = endUserAuth;
            } else {
                LOGGER.debug("user not found, throwing AuthenticationServiceException....");
                throw new AuthenticationServiceException("CUSTOM AUTHENTICATION FAILED, user not found");
            }
        } 
    } catch (Exception e) {
        LOGGER.debug("Exception occurred, rethrowing AuthenticationServiceException....");
        throw new AuthenticationServiceException("CUSTOM AUTHENTICATION FAILED: " + e.getMessage());
    }
    return authentication;
}

in addition the spring security conf I use is as follows

   <sec:authentication-manager alias="authenticationManager" erase-credentials="false">
        <sec:authentication-provider ref="customAuthenticationProvider"/>
    </sec:authentication-manager>

    <bean id="customAuthenticationProvider"
          class="com.example.security.MyAuthenticationProvider"/>
    </bean>

but I guess yours is OK as well, the rest is just to have your Token hold name, group and user and you should be fine, hope it helps

Upvotes: 0

Related Questions