Refactor jaas login sequence to only fetch role data if user is authenticated according to that module.

This commit is contained in:
Jan Bartel 2015-11-25 13:58:27 +11:00
parent e296995b2f
commit c7ab05a0b8
11 changed files with 508 additions and 63 deletions

View File

@ -21,7 +21,6 @@ package org.eclipse.jetty.jaas.spi;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -61,6 +60,24 @@ public abstract class AbstractDatabaseLoginModule extends AbstractLoginModule
public abstract Connection getConnection () throws Exception;
public class JDBCUserInfo extends UserInfo
{
public JDBCUserInfo (String userName, Credential credential)
{
super(userName, credential);
}
@Override
public List<String> doFetchRoles ()
throws Exception
{
return getRoles(getUserName());
}
}
/* ------------------------------------------------ */
/** Load info from database
@ -92,8 +109,22 @@ public abstract class AbstractDatabaseLoginModule extends AbstractLoginModule
return null;
}
return new JDBCUserInfo (userName, Credential.getCredential(dbCredential));
}
}
public List<String> getRoles (String userName)
throws Exception
{
List<String> roles = new ArrayList<String>();
try (Connection connection = getConnection())
{
//query for role names
List<String> roles = new ArrayList<String>();
try (PreparedStatement statement = connection.prepareStatement (rolesQuery))
{
statement.setString (1, userName);
@ -107,11 +138,14 @@ public abstract class AbstractDatabaseLoginModule extends AbstractLoginModule
}
}
return new UserInfo (userName, Credential.getCredential(dbCredential), roles);
}
return roles;
}
public void initialize(Subject subject,
CallbackHandler callbackHandler,
Map<String,?> sharedState,

View File

@ -54,6 +54,12 @@ public abstract class AbstractLoginModule implements LoginModule
private JAASUserInfo currentUser;
private Subject subject;
/**
* JAASUserInfo
*
* This class unites the UserInfo data with jaas concepts
* such as Subject and Principals
*/
public class JAASUserInfo
{
private UserInfo user;
@ -62,7 +68,8 @@ public abstract class AbstractLoginModule implements LoginModule
public JAASUserInfo (UserInfo u)
{
setUserInfo(u);
this.user = u;
this.principal = new JAASPrincipal(u.getUserName());
}
public String getUserName ()
@ -75,18 +82,6 @@ public abstract class AbstractLoginModule implements LoginModule
return this.principal;
}
public void setUserInfo (UserInfo u)
{
this.user = u;
this.principal = new JAASPrincipal(u.getUserName());
this.roles = new ArrayList<JAASRole>();
if (u.getRoleNames() != null)
{
Iterator<String> itor = u.getRoleNames().iterator();
while (itor.hasNext())
this.roles.add(new JAASRole((String)itor.next()));
}
}
public void setJAASInfo (Subject subject)
{
@ -106,6 +101,18 @@ public abstract class AbstractLoginModule implements LoginModule
{
return this.user.checkCredential(suppliedCredential);
}
public void fetchRoles() throws Exception
{
this.user.fetchRoles();
this.roles = new ArrayList<JAASRole>();
if (this.user.getRoleNames() != null)
{
Iterator<String> itor = this.user.getRoleNames().iterator();
while (itor.hasNext())
this.roles.add(new JAASRole((String)itor.next()));
}
}
}
public Subject getSubject ()
@ -174,7 +181,6 @@ public abstract class AbstractLoginModule implements LoginModule
*/
public boolean commit() throws LoginException
{
if (!isAuthenticated())
{
currentUser = null;
@ -252,7 +258,10 @@ public abstract class AbstractLoginModule implements LoginModule
setAuthenticated(currentUser.checkCredential(webCredential));
if (isAuthenticated())
{
currentUser.fetchRoles();
return true;
}
else
throw new FailedLoginException();
}

View File

@ -176,6 +176,28 @@ public class LdapLoginModule extends AbstractLoginModule
private DirContext _rootContext;
public class LDAPUserInfo extends UserInfo
{
/**
* @param userName
* @param credential
*/
public LDAPUserInfo(String userName, Credential credential)
{
super(userName, credential);
}
@Override
public List<String> doFetchRoles() throws Exception
{
return getUserRoles(_rootContext, getUserName());
}
}
/**
* get the available information about the user
* <p>
@ -199,9 +221,7 @@ public class LdapLoginModule extends AbstractLoginModule
pwdCredential = convertCredentialLdapToJetty(pwdCredential);
Credential credential = Credential.getCredential(pwdCredential);
List<String> roles = getUserRoles(_rootContext, username);
return new UserInfo(username, credential, roles);
return new LDAPUserInfo(username, credential);
}
protected String doRFC2254Encoding(String inputString)
@ -411,12 +431,17 @@ public class LdapLoginModule extends AbstractLoginModule
setCurrentUser(new JAASUserInfo(userInfo));
boolean authed = false;
if (webCredential instanceof String)
{
return credentialLogin(Credential.getCredential((String) webCredential));
}
authed = credentialLogin(Credential.getCredential((String) webCredential));
else
authed = credentialLogin(webCredential);
return credentialLogin(webCredential);
//only fetch roles if authenticated
if (authed)
getCurrentUser().fetchRoles();
return authed;
}
catch (UnsupportedCallbackException e)
{
@ -496,16 +521,18 @@ public class LdapLoginModule extends AbstractLoginModule
String filter = "(&(objectClass={0})({1}={2}))";
LOG.info("Searching for users with filter: \'" + filter + "\'" + " from base dn: " + _userBaseDn);
if (LOG.isDebugEnabled())
LOG.debug("Searching for users with filter: \'" + filter + "\'" + " from base dn: " + _userBaseDn);
Object[] filterArguments = new Object[]{
_userObjectClass,
_userIdAttribute,
username
_userObjectClass,
_userIdAttribute,
username
};
NamingEnumeration<SearchResult> results = _rootContext.search(_userBaseDn, filter, filterArguments, ctls);
LOG.info("Found user?: " + results.hasMoreElements());
if (LOG.isDebugEnabled())
LOG.debug("Found user?: " + results.hasMoreElements());
if (!results.hasMoreElements())
{

View File

@ -101,7 +101,7 @@ public class PropertyFileLoginModule extends AbstractLoginModule
}
/**
* Don't implement this as we want to pre-fetch all of the users.
*
*
* @param userName the user name
* @throws Exception if unable to get the user information
@ -117,6 +117,8 @@ public class PropertyFileLoginModule extends AbstractLoginModule
if (userIdentity==null)
return null;
//TODO in future versions change the impl of PropertyUserStore so its not
//storing Subjects etc, just UserInfo
Set<Principal> principals = userIdentity.getSubject().getPrincipals();
List<String> roles = new ArrayList<String>();

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.jaas.spi;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.jetty.util.security.Credential;
@ -29,24 +30,70 @@ import org.eclipse.jetty.util.security.Credential;
* This is the information read from the external source
* about a user.
*
* Can be cached by a UserInfoCache implementation
* Can be cached.
*/
public class UserInfo
{
private String _userName;
private Credential _credential;
private List<String> _roleNames;
protected List<String> _roleNames = new ArrayList<>();
protected boolean _rolesLoaded = false;
/**
* @param userName
* @param credential
* @param roleNames
*/
public UserInfo (String userName, Credential credential, List<String> roleNames)
{
_userName = userName;
_credential = credential;
_roleNames = new ArrayList<String>();
if (roleNames != null)
{
_roleNames.addAll(roleNames);
synchronized (_roleNames)
{
_roleNames.addAll(roleNames);
_rolesLoaded = true;
}
}
}
/**
* @param userName
* @param credential
*/
public UserInfo (String userName, Credential credential)
{
this (userName, credential, null);
}
/**
* Should be overridden by subclasses to obtain
* role info
*
* @return
* @throws Exception
*/
public List<String> doFetchRoles ()
throws Exception
{
return Collections.emptyList();
}
public void fetchRoles () throws Exception
{
synchronized (_roleNames)
{
if (!_rolesLoaded)
{
_roleNames.addAll(doFetchRoles());
_rolesLoaded = true;
}
}
}
@ -57,7 +104,7 @@ public class UserInfo
public List<String> getRoleNames ()
{
return new ArrayList<String>(_roleNames);
return Collections.unmodifiableList(_roleNames);
}
public boolean checkCredential (Object suppliedCredential)

View File

@ -74,6 +74,33 @@ public class DataSourceLoginService extends MappedLoginService
private String _roleSql;
private boolean _createTables = false;
/**
* DBUser
*
*
*/
public class DBUser extends KnownUser
{
private int _key;
/**
* @param name
* @param credential
*/
public DBUser(String name, Credential credential, int key)
{
super(name, credential);
_key = key;
}
public int getKey ()
{
return _key;
}
}
/* ------------------------------------------------------------ */
public DataSourceLoginService()
{
@ -290,13 +317,13 @@ public class DataSourceLoginService extends MappedLoginService
*
* @param userName the user name
*/
@Override
@Deprecated
protected UserIdentity loadUser (String userName)
{
try
{
try (Connection connection = getConnection();
PreparedStatement statement1 = connection.prepareStatement(_userSql))
PreparedStatement statement1 = connection.prepareStatement(_userSql))
{
statement1.setObject(1, userName);
try (ResultSet rs1 = statement1.executeQuery())
@ -305,19 +332,20 @@ public class DataSourceLoginService extends MappedLoginService
{
int key = rs1.getInt(_userTableKey);
String credentials = rs1.getString(_userTablePasswordField);
List<String> roles = new ArrayList<String>();
try (PreparedStatement statement2 = connection.prepareStatement(_roleSql))
{
statement2.setInt(1, key);
try (ResultSet rs2 = statement2.executeQuery())
List<String> roles = new ArrayList<String>();
try (PreparedStatement statement2 = connection.prepareStatement(_roleSql))
{
while (rs2.next())
statement2.setInt(1, key);
try (ResultSet rs2 = statement2.executeQuery())
{
roles.add(rs2.getString(_roleTableRoleField));
while (rs2.next())
{
roles.add(rs2.getString(_roleTableRoleField));
}
}
}
}
return putUser(userName, Credential.getCredential(credentials), roles.toArray(new String[roles.size()]));
return putUser(userName, Credential.getCredential(credentials), roles.toArray(new String[roles.size()]));
}
}
}
@ -334,6 +362,83 @@ public class DataSourceLoginService extends MappedLoginService
}
/**
* @see org.eclipse.jetty.security.MappedLoginService#loadUserInfo(java.lang.String)
* @Override
*/
public KnownUser loadUserInfo (String username)
{
try
{
try (Connection connection = getConnection();
PreparedStatement statement1 = connection.prepareStatement(_userSql))
{
statement1.setObject(1, username);
try (ResultSet rs1 = statement1.executeQuery())
{
if (rs1.next())
{
int key = rs1.getInt(_userTableKey);
String credentials = rs1.getString(_userTablePasswordField);
return new DBUser(username, Credential.getCredential(credentials), key);
}
}
}
}
catch (NamingException e)
{
LOG.warn("No datasource for "+_jndiName, e);
}
catch (SQLException e)
{
LOG.warn("Problem loading user info for "+username, e);
}
return null;
}
/**
* @see org.eclipse.jetty.security.MappedLoginService#loadRoleInfo(org.eclipse.jetty.security.MappedLoginService.KnownUser)
* @Override
*/
public String[] loadRoleInfo (KnownUser user)
{
DBUser dbuser = (DBUser)user;
try
{
try (Connection connection = getConnection();
PreparedStatement statement2 = connection.prepareStatement(_roleSql))
{
List<String> roles = new ArrayList<String>();
statement2.setInt(1, dbuser.getKey());
try (ResultSet rs2 = statement2.executeQuery())
{
while (rs2.next())
{
roles.add(rs2.getString(_roleTableRoleField));
}
return roles.toArray(new String[roles.size()]);
}
}
}
catch (NamingException e)
{
LOG.warn("No datasource for "+_jndiName, e);
}
catch (SQLException e)
{
LOG.warn("Problem loading user info for "+user.getName(), e);
}
return null;
}
/* ------------------------------------------------------------ */
@Override

View File

@ -19,7 +19,11 @@
package org.eclipse.jetty.security;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.eclipse.jetty.security.MappedLoginService.KnownUser;
import org.eclipse.jetty.security.PropertyUserStore.UserListener;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.Scanner;
@ -55,6 +59,36 @@ public class HashLoginService extends MappedLoginService implements UserListener
private Scanner _scanner;
private boolean hotReload = false; // default is not to reload
public class HashKnownUser extends KnownUser
{
String[] _roles;
/**
* @param name
* @param credential
*/
public HashKnownUser(String name, Credential credential)
{
super(name, credential);
}
public void setRoles (String[] roles)
{
_roles = roles;
}
public String[] getRoles()
{
return _roles;
}
}
/* ------------------------------------------------------------ */
public HashLoginService()
{
@ -163,6 +197,41 @@ public class HashLoginService extends MappedLoginService implements UserListener
// TODO: Consider refactoring MappedLoginService to not have to override with unused methods
}
@Override
protected String[] loadRoleInfo(KnownUser user)
{
UserIdentity id = _propertyUserStore.getUserIdentity(user.getName());
if (id == null)
return null;
Set<RolePrincipal> roles = id.getSubject().getPrincipals(RolePrincipal.class);
if (roles == null)
return null;
List<String> list = new ArrayList<>();
for (RolePrincipal r:roles)
list.add(r.getName());
return list.toArray(new String[roles.size()]);
}
@Override
protected KnownUser loadUserInfo(String userName)
{
UserIdentity id = _propertyUserStore.getUserIdentity(userName);
if (id != null)
{
return (KnownUser)id.getUserPrincipal();
}
return null;
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
@ -204,9 +273,11 @@ public class HashLoginService extends MappedLoginService implements UserListener
{
if (LOG.isDebugEnabled())
LOG.debug("update: " + userName + " Roles: " + roleArray.length);
putUser(userName,credential,roleArray);
//TODO need to remove and replace the authenticated user?
}
/* ------------------------------------------------------------ */
@Override
public void remove(String userName)

View File

@ -78,6 +78,32 @@ public class JDBCLoginService extends MappedLoginService
protected String _roleSql;
/**
* JDBCKnownUser
*
*
*/
public class JDBCKnownUser extends KnownUser
{
int _userKey;
/**
* @param name
* @param credential
*/
public JDBCKnownUser(String name, Credential credential, int key)
{
super(name, credential);
_userKey = key;
}
public int getUserKey ()
{
return _userKey;
}
}
/* ------------------------------------------------------------ */
public JDBCLoginService()
throws IOException
@ -231,7 +257,7 @@ public class JDBCLoginService extends MappedLoginService
}
/* ------------------------------------------------------------ */
@Override
@Deprecated
protected UserIdentity loadUser(String username)
{
try
@ -251,6 +277,8 @@ public class JDBCLoginService extends MappedLoginService
{
int key = rs1.getInt(_userTableKey);
String credentials = rs1.getString(_userTablePasswordField);
List<String> roles = new ArrayList<String>();
try (PreparedStatement stat2 = _con.prepareStatement(_roleSql))
@ -262,7 +290,7 @@ public class JDBCLoginService extends MappedLoginService
roles.add(rs2.getString(_roleTableRoleField));
}
}
return putUser(username, credentials, roles.toArray(new String[roles.size()]));
return putUser(username, Credential.getCredential(credentials), roles.toArray(new String[roles.size()]));
}
}
}
@ -275,13 +303,89 @@ public class JDBCLoginService extends MappedLoginService
return null;
}
/* ------------------------------------------------------------ */
protected UserIdentity putUser (String username, String credentials, String[] roles)
/**
* @see org.eclipse.jetty.security.MappedLoginService#loadUserInfo(java.lang.String)
* @Override
*/
public KnownUser loadUserInfo (String username)
{
return putUser(username, Credential.getCredential(credentials),roles);
try
{
if (null == _con)
connectDatabase();
if (null == _con)
throw new SQLException("Can't connect to database");
try (PreparedStatement stat1 = _con.prepareStatement(_userSql))
{
stat1.setObject(1, username);
try (ResultSet rs1 = stat1.executeQuery())
{
if (rs1.next())
{
int key = rs1.getInt(_userTableKey);
String credentials = rs1.getString(_userTablePasswordField);
return new JDBCKnownUser (username, Credential.getCredential(credentials), key);
}
}
}
}
catch (SQLException e)
{
LOG.warn("UserRealm " + getName() + " could not load user information from database", e);
closeConnection();
}
return null;
}
/**
* @see org.eclipse.jetty.security.MappedLoginService#loadRoleInfo(org.eclipse.jetty.security.MappedLoginService.KnownUser)
* @Override
*/
public String[] loadRoleInfo (KnownUser user)
{
JDBCKnownUser jdbcUser = (JDBCKnownUser)user;
try
{
if (null == _con)
connectDatabase();
if (null == _con)
throw new SQLException("Can't connect to database");
List<String> roles = new ArrayList<String>();
try (PreparedStatement stat2 = _con.prepareStatement(_roleSql))
{
stat2.setInt(1, jdbcUser.getUserKey());
try (ResultSet rs2 = stat2.executeQuery())
{
while (rs2.next())
roles.add(rs2.getString(_roleTableRoleField));
return roles.toArray(new String[roles.size()]);
}
}
}
catch (SQLException e)
{
LOG.warn("UserRealm " + getName() + " could not load user information from database", e);
closeConnection();
}
return null;
}
/**
* Close an existing connection
*/

View File

@ -139,6 +139,8 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
public void logout(UserIdentity identity)
{
LOG.debug("logout {}",identity);
//TODO should remove the user?????
}
/* ------------------------------------------------------------ */
@ -201,6 +203,24 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
return identity;
}
public synchronized UserIdentity putUser (KnownUser userPrincipal, String[] roles)
{
Subject subject = new Subject();
subject.getPrincipals().add(userPrincipal);
subject.getPrivateCredentials().add(userPrincipal._credential);
if (roles!=null)
for (String role : roles)
subject.getPrincipals().add(new RolePrincipal(role));
subject.setReadOnly();
UserIdentity identity=_identityService.newUserIdentity(subject,userPrincipal,roles);
_users.put(userPrincipal._name,identity);
return identity;
}
/* ------------------------------------------------------------ */
public void removeUser(String username)
{
@ -219,9 +239,17 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
UserIdentity user = _users.get(username);
if (user==null)
user = loadUser(username);
if (user!=null)
{
KnownUser userPrincipal = loadUserInfo(username);
if (userPrincipal.authenticate(credentials))
{
//safe to load the roles
String[] roles = loadRoleInfo(userPrincipal);
user = putUser(userPrincipal, roles);
return user;
}
}
else
{
UserPrincipal principal = (UserPrincipal)user.getUserPrincipal();
if (principal.authenticate(credentials))
@ -241,7 +269,10 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
return false;
}
/* ------------------------------------------------------------ */
protected abstract String[] loadRoleInfo (KnownUser user);
/* ------------------------------------------------------------ */
protected abstract KnownUser loadUserInfo (String username);
/* ------------------------------------------------------------ */
protected abstract UserIdentity loadUser(String username);

View File

@ -50,6 +50,13 @@
</securityHandler>
</webAppConfig>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.19</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>

View File

@ -1,5 +1,13 @@
xyz {
org.eclipse.jetty.jaas.spi.PropertyFileLoginModule required
org.eclipse.jetty.jaas.spi.JDBCLoginModule required
debug="true"
file="${jetty.base}/etc/login.properties";
userTable="users"
userField="username"
credentialField="pwd"
userRoleTable="userroles"
userRoleUserField="username"
userRoleRoleField="rolename"
dbDriver="com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource"
dbUrl="jdbc:mysql://localhost:3306/jaas"
dbUserName="janb";
};