View Full Version : Domain style logins (not NT domains!)
CameronBraid
Aug 22nd, 2004, 11:19 AM
I need to support domain based logins.
To me, a domain is a collection of unique users, each having a unique username.
Therefore it is possible to have two users in the system with the same username, with a different domain.
I have a custom, simple, security system that implements this via a domain property on the user object. I want to migrate to acegi. I am wondering if I should just prefix the username with the domain name, separated by a delimiter. Or if a domain property could be introduced into acegi ?
I guess the issue is, would this sort of use case be a good thing to integrate into acegi core ?
Thanks,
Cameron
Ben Alex
Aug 22nd, 2004, 07:26 PM
I would encourage the prefix approach, because most authentication infrastructure is geared to work with a single username and single password. By using a prefix you can tie into that existing infrastructure, such as BASIC authentication and CAS authentication.
A prefix is easy enough to work with. Your AuthenticationDao would be the place to tokenize the presented username, assuming you want a separate database column for the domain. Your DaoAuthenticationProvider could optionally be subclassed and the createSuccessAuthentication overridden to return a new Authentication object which has a convenience getter for the domain. If using form-based authentication you'd need to override the AuthenticationProcessingFilter to prepend the domain and delimiter.
I'm happy to add any contributions which make it easier to do something like the above.
CameronBraid
Aug 22nd, 2004, 07:38 PM
thanks for your informative and prompt response.
I think your approach will work very well.
I'll let you know how it goes.
Thanks agin.
jingyangpeng
Jun 22nd, 2005, 12:28 PM
Now a days, more and more Applications need manage multi - domain, the username will be not unique. I think it is not good to use a prefix approach. We need a new Authentication implementation which has a "domain" property, and change the
public UserDetails loadUserByUsername(String username) of AuthenticationDao to
public UserDetails loadUserByUsername(Authentication authentication ) or add
public UserDetails loadUserByUsername(Authentication authentication ) into AuthenticationDao
and same with the private UserDetails getUserFromBackend(String username) of DaoAuthenticationProvider
Ben Alex
Jun 25th, 2005, 07:15 PM
Your proposal is already in effect offered by the AuthenticationProvider and AuthenticationManager interfaces. If you had more complex needs than could be addressed via a prefix approach, you'd simply implement either of these interfaces (typically AuthenticationProvider, so it can still be used in a chained configuration and swapped easily with other AuthenticationProviders).
ag97and03
Jun 26th, 2005, 01:08 AM
We are working on project that has multiple administrative domains. Our planned approach is to decorate AuthenticationProviders with a regular expression based domain matcher. Users use an email style username. We plan to use the information in the username to determine which AuthenticationProvider should be used. Then we map the username to the format required by the underlying AuthenticationProvider (and user store).
I believe this approach should work with DaoAuthenticationProvider.
Questions:
1. Are we making a new wheel or is similar functionality already part of Acegi? Without additional code?
2. Does this seem like a reasonable approach to the Acegi experts?
Feedback is appreciated.
The Spring context will look something like this:
<bean id="department1AuthenticationProvider"
class="RegexMatchingProvider" />
<constructor-arg>
<ref local=" department1DaoAuthenticationProvider"/>
</constructor-arg>
<property name="domainRegex">
<value>\w+@department1.company.com</value>
</property>
<property name="usernameRegex">
<value>(\w+)@department1.company.com</value>
</property>
<property name="usernameReplacement">
<value>$1</value>
</property>
</bean>
<bean id="department2AuthenticationProvider"
class="RegexMatchingProvider" />
<constructor-arg>
<ref local=" department2DaoAuthenticationProvider"/>
</constructor-arg>
<property name="domainRegex">
<value>department2\\\w+</value>
</property>
</bean>
<bean id="authenticationManager"
class="net.sf.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="department1AuthenticationProvider" />
<ref local="department2AuthenticationProvider" />
</list>
</property>
</bean>
/**
* An <code>AuthenticationProvider</code> decorator used to support multiple
* administrative domains.
*
* This decorator only works with <code>AuthenticationProvider</code>s that
* support <code>UsernamePasswordAuthenticationToken</code> using either a
* <code>String</code> or <code>User</code> as the Principal.
*
* A JDK regular expression is used to determine if a username provided in the
* Principal is in an administrative domain. If the username is
* <code>null</code> or does not match the regular expression,
* <code>authenticate()</code> returns null indicating this
* <code>AuthenticationProvider</code> does not support authentication for
* this domain. If the username matched the regular expression it may be
* modified before being delegated to the underlying
* <code>AuthenticationProvider</code>. See
* <code>setUsernameRegex()</code> for further information. The
* original username is replaced before the <code>Authentication</code> is
* returned.
*
* For example, <code>user1@department1.company.com</code> can be mapped to
* <code>user1</code> then authenticated against depatment1’s user store.
* Another AuthenticationProvider can be decorated to only authenticate
* usernames in the form <code>department2\\user</code> against department2’s
* user store.
*
*/
public class RegexMatchingProvider implements AuthenticationProvider {
// impl removed
public RegexMatchingProvider(AuthenticationProvider authenticationProvider) {
// impl removed
}
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// impl removed
}
/**
*
* The expression used to determine if this AuthenticationProvider should be
* used for authentication.
*
* @param domainRegex
* The JDK regular expression to use to determine if this
* AuthenticationProvider should be used for authentication.
*/
public void setDomainRegex(String domainRegex) {
// impl removed
}
/**
*
* The expression provided here is used in the matching part of the
* substitution operation. Think Perl 5:
* s/$usernameRegex/$usernameReplacement/g
*
* See replaceAll() in java.util.regex.Matcher for further information.
*
* @param usernameRegex
* The JDK regular expression used to extract the domain specific
* username from the username provided.
*
*/
public void setUsernameRegex(String usernameRegex) {
// impl removed
}
/**
*
* The expression provided here is used as the substitution string in the
* substitution operation. Think Perl 5:
* s/$usernameRegex/$usernameReplacement/g
*
* See replaceAll() in java.util.regex.Matcher for further information.
*
* @param usernameReplacement
* The username will be replaced according to the expression
* provided. Dollar signs may be treated as references to
* captured subsequences.
*
*/
public void setUsernameReplacement(String usernameReplacement) {
// impl removed
}
}
Ben Alex
Jun 26th, 2005, 10:12 PM
It looks a reasonable approach, although I would consider moving the logic to the AuthenticationDao instead of the AuthenticationProvider, and possibly using a PropertyEditor so that you can have, for example:
<bean id="departmentAwareAuthenticationDao" class="AuthDao">
<property name="dataSource"><ref local="dataSource"/></property>
<property name="departments">
<value>
\w+@department1.company.com=(\w+)@department1.comp any.com,$1
\w+@department2.company.com=(\w+)@department2.comp any.com,$1
</value>
</property>
</bean>
kexkey
Mar 9th, 2007, 10:12 PM
Hi,
I was wondering, almost 2 years later, if the prefix approach is still the way to go or if there is some better approach to authenticate a user using more than the usual username/password pair.
In my case, the username is unique in the system, but the roles associated to the user depend on the third parameter (the domain). So I need to get the user info from the DB with this in mind, but once I have the user with the authorities related to the supplied domain, the rest of the authentication is the same.
I see 2 interesting ways to do that :
1. Don't prefix the username with the domain.
1.1. Subclass AuthenticationProcessingFilter (override attemptAuthentication(...)) and UsernamePasswordAuthenticationToken to take care of and add the third property (domain).
1.2. Subclass DaoAuthenticationProvider and override retrieveUser(...) to call UserDetailsService.loadUserByAuthentication(authen tication) instead of loadUserByUsername(username).
1.3. Subclass JdbcDaoImpl (my UserDetailsService) and add loadUserByAuthentication(Authentication) and take care of the domain in the authoritiesByUsernameQuery.
2. Prefix the username with the domain.
2.1. Subclass AuthenticationProcessingFilter to forge the concatenated username for UsernamePasswordAuthenticationToken.
2.2. Subclass JdbcDaoImpl (my UserDetailsService) to unforge the domain from the username, get the user with that.
2.3. I wonder if I have to put back the real unconcatenated username in the Authentication for future use in the application?? If it is put in the SecurityContextHolder with the prefix, then I will have to unforge the username each time I want to use it in the application? If I put the unforged username in it, and I don't have any caching, will it try to authenticate later without the prefix and won't be able to?
Does that make sense? Do you have any more advice?
Thanks in advance.
karldmoore
Mar 10th, 2007, 06:04 AM
IMHO, I think the prefix is still the easier way to go. It should be pretty quick and straight forward to implement.
kexkey
Mar 11th, 2007, 09:39 AM
Thanks karldmoore for your advice.
I decided to go with the concatenated way. Now I have a question.
If for instance John's username is "john" with domain_id "1", the UsernamePasswordAuthenticationToken's username field will be "1:john". But we know that in the DB, the username is "john". So, in the UserDetailsService.loadUserByUsername(String username) method, the loaded username will be "john". At the end of the method, we have:
String returnUsername = user.getUsername(); // this is "john"
if (!isUsernameBasedPrimaryKey()) {
returnUsername = username; // this is "1:john" or whatever...
}
return new User(returnUsername, user.getPassword(), user.isEnabled(), true, true, true, arrayAuths);
So the database username of "john" will be put back in the Authentication object (via the principal -- User).
Now the question : is the Authentication object, stored in the SecurityContext, used later, after the login, by Acegi for authentication purposes -- for example, if I don't have caching, will it go through the whole authentication process again?
If so, we all know it won't work because "john" will be used instead of "1:john".
Actually, I was wondering why there is the "returnUsername" thing at the end of the method.
What are your thoughts? Thanks for reading.
karldmoore
Mar 11th, 2007, 03:17 PM
I think your User objects needs to contain the domain:username style value. This value is used in all sorts of places so it needs to contain the fully qualified username to work. As for checking the authentication everytime, this will only happen if you configure it to do this. I'm pretty sure it doesn't do this by default. I'm not quite sure what the question about "returnUsername" is.
kexkey
Apr 16th, 2007, 09:20 AM
Hi there!
Just wanted to tell you that I finally decided to go with the domain:username concatenation way. It works well. I just had to subclass three classes:
org.acegisecurity.ui.webapp.AuthenticationProcessi ngFilter
- to parse the domain, username and password and concatenate the domain and the real username in the "username" field -- attemptAuthentication(...), obtainUsername(...).
org.acegisecurity.userdetails.jdbc.JdbcDaoImpl
- to split the concatenated username and use the domain and real username for getting the user details from the DB -- loadUserByUsername(...)
org.acegisecurity.userdetails.User
- to add the domain and realUsername.
Please note: DO NOT use ":" delimiter if you intend to use the Remember Me functionality, because the latter function won't work as it uses ":" as its delimiter and won't split the cookie correctly if ":" is found in *our* concatenated username.
Thank you Karl Moore for your feedbacks. You were right, it is very important to keep the username concatenated in the User object, and add a new field ie realUsername to get the unconcatenated username.
Have a nice development! :)
- Kexkey
karldmoore
Apr 16th, 2007, 12:25 PM
Thanks for posting back, glad you got it all working! That was a good point about RememberMe I hadn't really thought about that.
vBulletin® v3.7.3, Copyright ©2000-2008, Jelsoft Enterprises Ltd.