Reputation: 1004
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)
By creating a custom authenticationProvider and perform user database checking in the authenticate method. Basically, moving my loadByUserName implementation from MyUserDetailsService to authenticate methode in MyAuthenticationProvider.
public class MyAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String name = ...
String password = ...
String group = ...
....//I am not sure how to do it though
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(MyUsernamePasswordGroupAuthenticationToken.class);
}
}
May be by passing multiple parameters to loadUserByName. I would like to have something like
loadUserByNameAndGroud(String name, String group)
but I have no idea how to make the custom authenticationProvider invoke loadUserByNameAndGroud instead of loadUserByName
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
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
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
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