I have a Grails application that is successfully using the latest spring-security-core:2.0-RC4 and spring-security-ldap:2.0-RC2. Users can login perfectly using
grails.plugin.springsecurity.ldap.search.base setting for LDAP login authentication.
There is a different setting for the rememberMe userDnBase (mapper) and that setting is:
grails.plugin.springsecurity.ldap.rememberMe.usernameMapper.userDnBase
The LDAP authentication grails.plugin.springsecurity.ldap.search.base is set to ou=people,dc=sitcudy,dc=edu. As mentioned above - the logins work fine because there is a property called searchSubtree that I have set to true. Unfortunately, the searchSubtree setting does not hold true and carry through consistently within the 'remember-me' portion of the code (.ldap.rememberMe)*. The remember-me portion of the code uses a map for the base DN, grails.plugin.springsecurity.ldap.rememberMe.usernameMapper.userDnBase
so I put in a string in the config.groovy file (the same as for the authentication piece) to map to the base DN of ou=people,dc=sitcudy,dc=edu.... which gets mapped to the DN for the LDAP user look up upon returning to the application for persistence cookie login.
Here's where my problem comes in, most users are segregated into different DIT's in our LDAP system. For example, some uses are in ou=staff,ou=people,dc=sitcudy,dc=edu while other users are in ou=students,ou=people,dc=sitcudy,dc=edu therefore, because of the remember me mapping, upon returning to the application, once verifying the cookie, the code tries to bind users in this format, uid=reuben_marcus,ou=people,dc=sitcudy,dc=edu which doesn't exist. What does exist is uid=reuben_marcus,ou=staff,ou=people,dc=sitcudy,dc=edu therefore the cookie is destroyed and the login (IS_AUTHENTICATED_REMEMBERED) never occurs.
If I change grails.plugin.springsecurity.ldap.rememberMe.usernameMapper.userDnBase
to ou=staff,ou=people,dc=sitcudy,dc=edu the remember me functionality works perfect for all staff members, but it doesn't work for all other people - students, faculty etc.
The main setting in question below for me in this issue is:
grails.plugin.springsecurity.ldap.rememberMe.usernameMapper.userDnBase
Since this is just a mapping and there isn't allowance for multiple userDNBases or searchSubtree search.. How is the ‘remember-me’ code supposed to find users that do not fall into this base DN setting...??
I wonder if I'm doing something wrong or if this is a feature request to have the ‘remember me’ code have options for multiple mapping userDNBases or allow it to have a searchSubtree search capability.
Relevant settings from my config.groovy:
grails.plugin.springsecurity.ldap.mapper.roleAttributes = 'sitPriRole,uid'
grails.plugin.springsecurity.ldap.context.managerDn = 'uid=SPS_bind,ou=People,dc=sitcudy,dc=edu'
grails.plugin.springsecurity.ldap.context.managerPassword = 'xxx'
grails.plugin.springsecurity.ldap.context.server = 'ldap://ds01.sitcudy.edu:389'
grails.plugin.springsecurity.ldap.authorities.groupSearchBase ='ou=Groups,dc=sitcudy,dc=edu'
grails.plugin.springsecurity.ldap.search.base = 'ou=People,dc=sitcudy,dc=edu'
grails.plugin.springsecurity.ldap.search.searchSubtree = true
grails.plugin.springsecurity.ldap.auth.hideUserNotFoundExceptions = false
grails.plugin.springsecurity.ldap.search.attributesToReturn = ['uid', 'sitPriRole', 'mail', 'displayName']
grails.plugin.springsecurity.providerNames = ['ldapAuthProvider', 'anonymousAuthenticationProvider', 'rememberMeAuthenticationProvider']
grails.plugin.springsecurity.ldap.authorities.retrieveGroupRoles = false
grails.plugin.springsecurity.ldap.authorities.retrieveDatabaseRoles = false
grails.plugin.springsecurity.password.algorithm = 'SHA-256'
grails.plugin.springsecurity.rememberMe.persistent = true
grails.plugin.springsecurity.rememberMe.persistentToken.domainClassName = 'od.PersistentLogin'
// role-specific LDAP config
// grails.plugin.springsecurity.ldap.useRememberMe = true
grails.plugin.springsecurity.ldap.rememberMe.detailsManager.attributesToRetrieve = null
grails.plugin.springsecurity.ldap.rememberMe.detailsManager.groupMemberAttributeName = 'uniquemember'
grails.plugin.springsecurity.ldap.rememberMe.detailsManager.groupRoleAttributeName = 'cn'
grails.plugin.springsecurity.ldap.rememberMe.detailsManager.groupSearchBase = 'ou=Groups,dc=sitcudy,dc=edu'
grails.plugin.springsecurity.ldap.rememberMe.detailsManager.passwordAttributeName = 'userPassword'
grails.plugin.springsecurity.ldap.rememberMe.usernameMapper.userDnBase = 'ou=People,dc=sitcudy,dc=edu'
grails.plugin.springsecurity.ldap.rememberMe.usernameMapper.usernameAttribute = 'uid'
This problem was mentioned here: Grails - Spring security plugin ldap: remember me not working
I found a workaround for this by registering custom TokenBasedRememberMeServices bean in resources.groovy.
I didn't use persistent logins functionality available in grails-spring-security-ldap plugin, because I found it incompatible with my Active Directory tree layout. Most probably, this could be customized by extending LdapUserDetailsManager but in my situation I found it unnecessary to store token in database.
I used regular spring security remember me cookie option but without storing user password in the cookie. I extended the following methods from TokenBasedRememberMeServices
makeTokenSignature - make token signature without password field
processAutoLoginCookie- if cookie exists, then retrieve username from cookie token and fetch ldap user details (I had to write my own method retrieveUserFromLdap() explained later)
onLoginSuccess - this gets triggered when user logs in with remember-me option checked. Here, I'm removing password and saving token signature to cookie.
To fetch user details and roles from LDAP it might depend on specific implementation but my method looks like this:
static protected UserDetails retrieveUserFromLdap(String username) {
def ldapUserSearch = Holders.applicationContext.getBean('ldapUserSearch')
def userContextMapper = Holders.applicationContext.getBean('ldapUserDetailsMapper')
def authoritiesPopulator = Holders.applicationContext.getBean('ldapAuthoritiesPopulator')
def userContext = ldapUserSearch.searchForUser(username)
def userAuthorities = authoritiesPopulator.getGrantedAuthorities(userContext,username)
userContextMapper.mapUserFromContext(userContext,username,userAuthorities)
}
Related
I have the Grails Spring Security plugin connecting to one Active Directory server with no problems. However, I need to connect to multiple servers. We have some users on one AD server and other users on a different server, so we need to try looking for users in both locations.
For example, in Java I have this working as below:
<authentication-manager>
<authentication-provider ref="provider1"/>
<authentication-provider ref="provider2"/>
...
</authentication-manager>
<ldap-server id="provider1"
url="ldap://LDAPSERVER1.mycompany.intranet"
manager-dn="OU=std_users,OU=users,DC=mycompany,DC=intranet"
manager-password="blah"/>
<ldap-server id="provider2"
url="ldap://DIFFERENT_LDAPSERVER.mycompany.intranet"
manager-dn="OU=std_users,OU=external_users,DC=mycompany,DC=intranet"
manager-password="blah"/>
In Grails I can configure one AD server but cannot work out how to configure more than one:
// LDAP config
grails.plugin.springsecurity.ldap.context.managerDn = 'CN=blah,OU=std_users,OU=users,DC=mycompany,DC=intranet'
grails.plugin.springsecurity.ldap.context.managerPassword = 'the_password'
grails.plugin.springsecurity.ldap.context.server = 'ldap://theserver.mycompany.intranet'
grails.plugin.springsecurity.ldap.authorities.ignorePartialResultException = true // typically needed for Active Directory
grails.plugin.springsecurity.ldap.search.base = 'OU=std_users,OU=users,DC=mycompany,DC=intranet'
grails.plugin.springsecurity.ldap.search.filter="sAMAccountName={0}" // for Active Directory you need this
grails.plugin.springsecurity.ldap.search.searchSubtree = true
grails.plugin.springsecurity.ldap.auth.hideUserNotFoundExceptions = false
I know that you can create a space-separated list of servers but this won't work for me as it will only try one of the servers once it has a connection, whereas I need it to try looking for users in both.
I think I probably need to get stuck into the resources.groovy file but don't know where to start with this - has anyone configured multiple AD locations?
The only other idea I have is to create a virtual directory which brings together all the users in one directory. Can anyone suggest a good way of doing this? I have been looking at http://myvd.sourceforge.net/usecases.html
Any help would be appreciated. Have been googling all day and I am no closer to a solution.
Andrew's answer pointed me in the right direction and I now have this working.
It was A LOT easier to make this work using ActiveDirectoryLdapAuthenticationProvider. This is done as below:
In resources.groovy:
// Domain 1
ldapAuthProvider1(ActiveDirectoryLdapAuthenticationProvider,
"mydomain.com",
"ldap://mydomain.com/"
)
// Domain 2
ldapAuthProvider2(ActiveDirectoryLdapAuthenticationProvider,
"mydomain2.com",
"ldap://mydomain2.com/"
)
In Config.groovy:
grails.plugin.springsecurity.providerNames = ['ldapAuthProvider1', 'ldapAuthProvider2']
This is all the code you need. You can pretty much remove all other grails.plugin.springsecurity.ldap.* settings in Config.groovy as they don't apply to this AD setup.
For documentation, see:
http://docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ldap-active-directory
If you aren't using AD and want the 'pure LDAP' version:
In resources.groovy:
// Create another ldap authentication provider
ldapAuthProvider2(org.springframework.security.ldap.authentication.LdapAuthenticationProvider,
ref("ldapAuthenticator2"),
ref("ldapAuthoritiesPopulator") // Use default
) {
// Can set other auth provider settings here
}
ldapAuthenticator2(org.springframework.security.ldap.authentication.BindAuthenticator, ref("contextSource2")) {
userSearch = ref("ldapUserSearch2")
}
// Set up the manager to read LDAP
contextSource2(DefaultSpringSecurityContextSource, grailsApplication.config.grails.plugin.springsecurity.ldap.context.server2) {
userDn = grailsApplication.config.grails.plugin.springsecurity.ldap.context.managerDn2 // Manager DN
password = grailsApplication.config.grails.plugin.springsecurity.ldap.context.managerPassword2
}
// Configuration for searching for user
ldapUserSearch2(FilterBasedLdapUserSearch, grailsApplication.config.grails.plugin.springsecurity.ldap.search.base2, grailsApplication.config.grails.plugin.springsecurity.ldap.search.filter2, ref('contextSource2')) {
}
And then in Config.groovy:
// Config for second LDAP AuthenticationProvider - used in resources.groovy
grails.plugin.springsecurity.ldap.context.managerDn2 = 'CN=MANAGER_USER,OU=Users,DC=mycompany,DC=com'
grails.plugin.springsecurity.ldap.context.managerPassword2 = 'manager_password'
grails.plugin.springsecurity.ldap.context.server2 = "ldap://the-ldap-server.com"
grails.plugin.springsecurity.ldap.search.base2 = 'OU=Users,DC=mycompany,DC=com'
grails.plugin.springsecurity.ldap.search.filter2 = "sAMAccountName={0}" // for Active Directory you need this
// Add the AuthenticationProvider to the list
grails.plugin.springsecurity.providerNames = ['ldapAuthProvider', 'ldapAuthProvider2']
This link was very useful for finding out how to set this up:
https://github.com/grails-plugins/grails-spring-security-ldap/blob/master/SpringSecurityLdapGrailsPlugin.groovy
The basic idea would be to construct a second, custom AuthenticationProvider bean within your application's grails-app/conf/spring/resources.groovy file, perhaps modeling it after ldapAuthProvider in https://github.com/grails-plugins/grails-spring-security-ldap/blob/master/SpringSecurityLdapGrailsPlugin.groovy Then, you could add this custom LDAP authenticator bean to the grails.plugin.springsecurity.providerNames list in Config.groovy (or equivalent)
I had a Posting on a blog about Sessions AND Cookies. Here are details
Sessions
Sessions are More Secure
Sessions are on the server
Cookies
Cookies are On client side
Less Secure
Once it is disable on browser the difficult to use.
On the basis of above argument i used sessions in Login system to keep UserId,UserName & roleName
Now on the the basis of roleName i will decide either this is Admin to enter to administrator section or not.
I have used this Code in Model in MVC
public bool LoginMe()
{
Int64 Error;
//create db
Database db = DatabaseFactory.CreateDatabase("DBContext");
DbCommand dbCommand = db.GetStoredProcCommand("ValidateUser");
db.AddInParameter(dbCommand, "#Username", DbType.String, this.UserName);
db.AddInParameter(dbCommand, "#Password", DbType.String, EncryptPassword(this.Password));
db.AddOutParameter(dbCommand, "#Error", DbType.Int64, 10);
DataSet dsResult = db.ExecuteDataSet(dbCommand);
Error = Convert.ToInt64(db.GetParameterValue(dbCommand, "#Error"));
if (Error == 1100)
{
try
{
var query = (from o in dsResult.Tables[0].AsEnumerable()
select new AllUser
{
UserId = o.Field<int>("UserId"),
UserName = o.Field<string>("UserName"),
roleName = o.Field<string>("roleName"),
}).Single(); // this will raise an exception if there isn't just one record returned
Session["UserId"] = query.UserId;
Session["UserName"] = query.UserName;
Session["roleName"] = query.roleName;
return true;
}
catch {
// do nothing and let method return false as something has gone wrong.
// add logging here if you are using it to show there has been a problem
}
}
return false;
}
I used it in View like #Session["UserId"]
Now an expert comment on this like
If you aren't using https and securing the session cookie then this might make it easy to hack your site, although that's the same for any session based site (nearly all of them)
It might be nice to add some check so that if you remove a user's rights, the session variables are deleted the next time that user requests something from the server,
otherwise they could carry on using the site even though their account it banned.You'd have to decide if this is likely and then how you want to do this (using an authorization filter maybe.)
Above comments confused me.Can any body make it clear?What is the best way to keep these information?
Session state uses client tickets to identify the server-side session, it may be susceptible to session ID spoofing and injection attacks.
So, to hack session values one would require hacking the remote-server.
And yes, for highly secure application(such as online banking) use https.
http://msdn.microsoft.com/en-us/magazine/cc163730.aspx#S9
Secure sockets layer (SSL) should be used to prevent network-level sniffing of session IDs, authentication tickets, application cookies, and other request/response information.
Can session value be hacked?
Use HTTPS if you application handles sensitive information(credit-card number,account num,passwords).
Store the User object (model with userId,username,role) in the session than separate attributes
Set setHttpOnly attribute for SESSION_ID.
It might be costly to refresh the User object stored in session before invoking every operation to reflect the current rights stored in database.
As a application administrator I would like to be able to log off any user, for example, after setting the flag "enabled = false" to the selected user. Is it possible in spring-security?
I should add that my application allows the use of "remember Me" for users.
I'm using:
grails 2.2.1
plugin spring-security-core 1.2.7.3
Settings spring-security-core (config.groovy):
grails.plugins.springsecurity.useHttpSessionEventP ublisher = true
grails.plugins.springsecurity.useSessionFixationPr evention = true
grails.plugins.springsecurity.userLookup.userDomai nClassName = 'com.app.User'
grails.plugins.springsecurity.userLookup.authority JoinClassName = 'com.app.UserRole'
grails.plugins.springsecurity.authority.className = 'com.app.Role'
grails.plugins.springsecurity.userLookup.usernameP ropertyName = 'email'
grails.plugins.springsecurity.securityConfigType = "Requestmap"
grails.plugins.springsecurity.rejectIfNoRule = true
grails.plugins.springsecurity.requestMap.className ='com.app.Requestmap'
grails.plugins.springsecurity.requestMap.urlField= 'url'
grails.plugins.springsecurity.requestMap.configAtt ributeField='configAttribute'
grails.plugins.springsecurity.rememberMe.cookieNam e = 'remember_me'
grails.plugins.springsecurity.cacheUsers = false
grails.plugins.springsecurity.rememberMe.tokenVali ditySeconds=604800
Has anyone had a similar problem may be?
thank you in advance :)
Setting the UserDetails.enabled flag to false should cause a DisabledException to be thrown on the user's next secure request. This can either send the user to the default authfail handler, or you can configure an exception handler in your UrlMappings to send the user to a custom controller or action.
If you're not using the enabled flag anywhere else in your application, you can direct the DisabledException to an action which clears the authentication session (and rememberMe), then resets the enabled flag.
Another possible way would be to create a custom filter and inject it into the spring security filter chain in your Bootstrap.
Both the url exception mapping and the filter configuration are described in the Spring Security Plugin Documentation
Thanks Codelark, I'm reading about filters and try to use them :)
Unfortunately, when I set enabled = false for the selected user application does not redirect it to "/login /authfail".
Best of all, the changes (enabled = false) are visible to the user profile but the lack of the desired effect.
I would add, I tried to set "expireNow" a user session:
def expireSession(User user) {
def orginalUser = springSecurityService?.principal.username
springSecurityService?.reauthenticate(user?.email) //modified user np: test#app.com
springSecurityService?.reauthenticate(orginalUser) //admin np: admin#app.com
sessionRegistry.getAllPrincipals()?.each { princ ->
sessionRegistry.getAllSessions(princ, false);
if(princ?.username?.equals(user?.email)) { //killing sessions only for user (test#app.com)
sessionRegistry.getAllSessions(princ, false)?.each { sess ->
sess.expireNow()
}
}//if
}//each
}//expireSession
Namely sessionRegistry really gets active sessions for each user, but by calling:
sess.expireNow()
The result is that calling expireSession (user) for the same user again, the session is no longer visible. Which is understandable because it has expired.
But in spite of expired user session. He may continue to work in the application. The application does not log you off
In my grails app I have customized the post authorization workflow by writing a custom auth success handler (in resources.groovy) as shown below.
authenticationSuccessHandler (MyAuthSuccessHandler) {
def conf = SpringSecurityUtils.securityConfig
requestCache = ref('requestCache')
defaultTargetUrl = conf.successHandler.defaultTargetUrl
alwaysUseDefaultTargetUrl = conf.successHandler.alwaysUseDefault
targetUrlParameter = conf.successHandler.targetUrlParameter
useReferer = conf.successHandler.useReferer
redirectStrategy = ref('redirectStrategy')
superAdminUrl = "/admin/processSuperAdminLogin"
adminUrl = "/admin/processAdminLogin"
userUrl = "/admin/processUserLogin"
}
As you can from the last three lines in the closure above, depending on the Role granted to the logging in User I am redirecting her to separate actions within the AdminController where a custom UserSessionBean is created and stored in the session.
It works fine for a regular login case which in my app is like so:
User comes to the app via either http://localhost:8080/my-app/ OR http://localhost:8080/my-app/login/auth
She enters her valid login id and password and proceeds.
The app internally accesses MyAuthSuccessHandler which redirects to AdminController considering the Role granted to this User.
The UserSessionBean is created and stored it in the session
User is taken to the app home page
I have also written a custom MyUserDetailsService by extending GormUserDetailsService which is correctly accessed in the above flow.
PROBLEM SCENARIO:
Consider a user directly accessing a protected resource (in this case the controller is secured with #Secured annotation) within the app.
User clicks http://localhost:8080/my-app/inbox/index
App redirects her to http://localhost:8080/my-app/login/auth
User enters her valid login id and password
User is taken to http://localhost:8080/my-app/inbox/index
The MyAuthSuccessHandler is skipped entirely in this process and hence my UserSessionBean is not created leading to errors upon further use in places where the UserSessionBean is accessed.
QUESTIONS:
In the problem scenario, does the app skip the MyAuthSuccessHandler because there is a target URL for it to redirect to upon login?
Can we force the process to always pass through MyAuthSuccessHandler even with the target URL present?
If the answer to 2 is no, is there an alternative as to how and where the UserSessionBean can still be created?
You can implement a customized eventListener to handle the post-login process, without disrupting the original user requested url.
In config.groovy, insert a config item:
grails.plugins.springsecurity.useSecurityEventListener = true
In you resources.groovy, add a bean like this:
import com.yourapp.auth.LoginEventListener
beans = {
loginEventListener(LoginEventListener)
}
And create a eventListener in src/groovy like this:
package com.yourapp.auth
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent
import org.springframework.web.context.request.RequestContextHolder as RCH
class LoginEventListener implements
ApplicationListener<InteractiveAuthenticationSuccessEvent> {
//deal with successful login
void onApplicationEvent(InteractiveAuthenticationSuccessEvent event) {
User.withTransaction {
def user = User.findByUsername(event.authentication.principal.username)
def adminRole = Role.findByAuthority('ROLE_ADMIN')
def userRole = Role.findByAuthority('ROLE_USER')
def session = RCH.currentRequestAttributes().session //get httpSession
session.user = user
if(user.authorities.contains(adminRole)){
processAdminLogin()
}
else if(user.authorities.contains(userRole)){
processUserLogin()
}
}
}
private void processAdminLogin(){ //move admin/processAdminLogin here
.....
}
private void processUserLogin(){ //move admin/processUserLogin here
.....
}
}
Done.
1) Yes, because it is an "on-demand" log in.
2) Yes, you can set it to always use default. The spring security plugin has a setting for it "successHandler.alwaysUseDefault" change that to true it defaults to false.
Also if you need more details check out the spring docs look for the Setting a Default Post-Login Destination section.
3) If you want to still create the user session bean and then redirect to the original URL you have two options create the bean in an earlier filter or expose the needed data via a custom UserDetailsService. Personally I would go the route of a custom details service.
I'm used to program with WIF and the pattern usually goes like this:
- Add a STS Ref
- Set the location of the web that needs to be autorized (or decorate actions in MVC with [Autorize]
- if the user access the "reserved" section, they are redirected to the STS
But let's say I need to actually build a web page that offer the user a choice of STS in order to logon. Not by accessing a page that triggers the redirection by configuration, but by actively loggin in on the page.
If I do this, what would be the form of the url to my STS ? What are the params I need to set to make the user login then redirect to let'S say the index page ?
WIF can help you with this.
Here's a code snippet of what it looks like:
WSFederationAuthenticationModule fam = FederatedAuthentication.WSFederationAuthenticationModule;
var signInRequest = new SignInRequestMessage(new Uri(fam.Issuer), fam.Realm)
{
AuthenticationType = fam.AuthenticationType,
Freshness = fam.Freshness,
Realm = "some realm",
Context = GetAReturnUrl(),
HomeRealm = "A Home Realm"
};
Then you can retrieve the URL with:
signInRequest.WriteQueryString()