Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-11.0.x

This commit is contained in:
Jan Bartel 2020-11-17 15:49:00 +01:00
commit 83e5b3fc04
22 changed files with 399 additions and 461 deletions

View File

@ -14,7 +14,6 @@ jdbc
jsp
annotations
ext
demo-realm
[files]
basehome:modules/demo.d/demo-jaas.xml|webapps/demo-jaas.xml
@ -22,6 +21,6 @@ basehome:modules/demo.d/demo-login.conf|etc/demo-login.conf
basehome:modules/demo.d/demo-login.properties|etc/demo-login.properties
maven://org.eclipse.jetty.demos/demo-jaas-webapp/${jetty.version}/war|webapps/demo-jaas.war
[ini-template]
[ini]
# Enable security via jaas, and configure it
jetty.jaas.login.conf=etc/demo-login.conf
jetty.jaas.login.conf?=etc/demo-login.conf

View File

@ -231,12 +231,14 @@ public class JAASLoginService extends ContainerLifeCycle implements LoginService
}
catch (Exception e)
{
LOG.trace("IGNORED", e);
if (LOG.isDebugEnabled())
LOG.debug("Login error", e);
}
finally
{
INSTANCE.remove();
}
return null;
}

View File

@ -26,7 +26,7 @@ import javax.security.auth.login.LoginContext;
* JAASUserPrincipal
* <p>
* Implements the JAAS version of the
* org.eclipse.jetty.http.UserPrincipal interface.
* org.eclipse.jetty.security.UserPrincipal interface.
*/
public class JAASUserPrincipal implements Principal
{

View File

@ -27,6 +27,7 @@ import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import org.eclipse.jetty.security.UserPrincipal;
import org.eclipse.jetty.util.security.Credential;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -57,11 +58,11 @@ public abstract class AbstractDatabaseLoginModule extends AbstractLoginModule
*/
public abstract Connection getConnection() throws Exception;
public class JDBCUserInfo extends UserInfo
public class JDBCUser extends JAASUser
{
public JDBCUserInfo(String userName, Credential credential)
public JDBCUser(UserPrincipal user)
{
super(userName, credential);
super(user);
}
@Override
@ -79,7 +80,7 @@ public abstract class AbstractDatabaseLoginModule extends AbstractLoginModule
* @throws Exception if unable to get the user info
*/
@Override
public UserInfo getUserInfo(String userName)
public JAASUser getUser(String userName)
throws Exception
{
try (Connection connection = getConnection())
@ -100,11 +101,9 @@ public abstract class AbstractDatabaseLoginModule extends AbstractLoginModule
}
if (dbCredential == null)
{
return null;
}
return new JDBCUserInfo(userName, Credential.getCredential(dbCredential));
return new JDBCUser(new UserPrincipal(userName, Credential.getCredential(dbCredential)));
}
}

View File

@ -19,11 +19,11 @@
package org.eclipse.jetty.jaas.spi;
import java.io.IOException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
@ -34,9 +34,10 @@ import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import org.eclipse.jetty.jaas.JAASPrincipal;
import org.eclipse.jetty.jaas.JAASRole;
import org.eclipse.jetty.jaas.callback.ObjectCallback;
import org.eclipse.jetty.security.UserPrincipal;
import org.eclipse.jetty.util.thread.AutoLock;
/**
* AbstractLoginModule
@ -50,35 +51,22 @@ public abstract class AbstractLoginModule implements LoginModule
private boolean authState = false;
private boolean commitState = false;
private JAASUserInfo currentUser;
private JAASUser currentUser;
private Subject subject;
/**
* JAASUserInfo
*
* This class unites the UserInfo data with jaas concepts
* such as Subject and Principals
*/
public class JAASUserInfo
public abstract static class JAASUser
{
private UserInfo user;
private Principal principal;
private List<JAASRole> roles;
public JAASUserInfo(UserInfo u)
private final UserPrincipal _user;
private List<JAASRole> _roles;
public JAASUser(UserPrincipal u)
{
this.user = u;
this.principal = new JAASPrincipal(u.getUserName());
_user = u;
}
public String getUserName()
{
return this.user.getUserName();
}
public Principal getPrincipal()
{
return this.principal;
return _user.getName();
}
/**
@ -86,12 +74,12 @@ public abstract class AbstractLoginModule implements LoginModule
*/
public void setJAASInfo(Subject subject)
{
subject.getPrincipals().add(this.principal);
if (this.user.getCredential() != null)
{
subject.getPrivateCredentials().add(this.user.getCredential());
}
subject.getPrincipals().addAll(roles);
if (_user == null)
return;
_user.configureSubject(subject);
if (_roles != null)
subject.getPrincipals().addAll(_roles);
}
/**
@ -99,35 +87,29 @@ public abstract class AbstractLoginModule implements LoginModule
*/
public void unsetJAASInfo(Subject subject)
{
subject.getPrincipals().remove(this.principal);
if (this.user.getCredential() != null)
{
subject.getPrivateCredentials().remove(this.user.getCredential());
}
subject.getPrincipals().removeAll(this.roles);
if (_user == null)
return;
_user.deconfigureSubject(subject);
if (_roles != null)
subject.getPrincipals().removeAll(_roles);
}
public boolean checkCredential(Object suppliedCredential)
{
return this.user.checkCredential(suppliedCredential);
return _user.authenticate(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()));
}
}
List<String> rolenames = doFetchRoles();
if (rolenames != null)
_roles = rolenames.stream().map(JAASRole::new).collect(Collectors.toList());
}
public abstract List<String> doFetchRoles() throws Exception;
}
public abstract UserInfo getUserInfo(String username) throws Exception;
public abstract JAASUser getUser(String username) throws Exception;
public Subject getSubject()
{
@ -139,12 +121,12 @@ public abstract class AbstractLoginModule implements LoginModule
this.subject = s;
}
public JAASUserInfo getCurrentUser()
public JAASUser getCurrentUser()
{
return this.currentUser;
}
public void setCurrentUser(JAASUserInfo u)
public void setCurrentUser(JAASUser u)
{
this.currentUser = u;
}
@ -252,15 +234,15 @@ public abstract class AbstractLoginModule implements LoginModule
throw new FailedLoginException();
}
UserInfo userInfo = getUserInfo(webUserName);
JAASUser user = getUser(webUserName);
if (userInfo == null)
if (user == null)
{
setAuthenticated(false);
throw new FailedLoginException();
}
currentUser = new JAASUserInfo(userInfo);
currentUser = user;
setAuthenticated(currentUser.checkCredential(webCredential));
if (isAuthenticated())

View File

@ -45,6 +45,7 @@ import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import org.eclipse.jetty.jaas.callback.ObjectCallback;
import org.eclipse.jetty.security.UserPrincipal;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.security.Credential;
import org.slf4j.Logger;
@ -179,18 +180,13 @@ public class LdapLoginModule extends AbstractLoginModule
private DirContext _rootContext;
public class LDAPUserInfo extends UserInfo
public class LDAPUser extends JAASUser
{
Attributes attributes;
/**
* @param userName the user name
* @param credential the credential
* @param attributes the user {@link Attributes}
*/
public LDAPUserInfo(String userName, Credential credential, Attributes attributes)
public LDAPUser(UserPrincipal user, Attributes attributes)
{
super(userName, credential);
super(user);
this.attributes = attributes;
}
@ -201,6 +197,25 @@ public class LdapLoginModule extends AbstractLoginModule
}
}
public class LDAPBindingUser extends JAASUser
{
DirContext _context;
String _userDn;
public LDAPBindingUser(UserPrincipal user, DirContext context, String userDn)
{
super(user);
_context = context;
_userDn = userDn;
}
@Override
public List<String> doFetchRoles() throws Exception
{
return getUserRolesByDn(_context, _userDn);
}
}
/**
* get the available information about the user
* <p>
@ -214,19 +229,17 @@ public class LdapLoginModule extends AbstractLoginModule
* @throws Exception if unable to get the user info
*/
@Override
public UserInfo getUserInfo(String username) throws Exception
public JAASUser getUser(String username) throws Exception
{
Attributes attributes = getUserAttributes(username);
String pwdCredential = getUserCredentials(attributes);
if (pwdCredential == null)
{
return null;
}
pwdCredential = convertCredentialLdapToJetty(pwdCredential);
Credential credential = Credential.getCredential(pwdCredential);
return new LDAPUserInfo(username, credential, attributes);
return new LDAPUser(new UserPrincipal(username, credential), attributes);
}
protected String doRFC2254Encoding(String inputString)
@ -421,7 +434,7 @@ public class LdapLoginModule extends AbstractLoginModule
else
{
// This sets read and the credential
UserInfo userInfo = getUserInfo(webUserName);
JAASUser userInfo = getUser(webUserName);
if (userInfo == null)
{
@ -429,7 +442,7 @@ public class LdapLoginModule extends AbstractLoginModule
return false;
}
setCurrentUser(new JAASUserInfo(userInfo));
setCurrentUser(userInfo);
if (webCredential instanceof String)
authed = credentialLogin(Credential.getCredential((String)webCredential));
@ -520,12 +533,8 @@ public class LdapLoginModule extends AbstractLoginModule
try
{
DirContext dirContext = new InitialDirContext(environment);
List<String> roles = getUserRolesByDn(dirContext, userDn);
UserInfo userInfo = new UserInfo(username, null, roles);
setCurrentUser(new JAASUserInfo(userInfo));
setCurrentUser(new LDAPBindingUser(new UserPrincipal(username, null), dirContext, userDn));
setAuthenticated(true);
return true;
}
catch (javax.naming.AuthenticationException e)

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.jaas.spi;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -27,8 +28,9 @@ import javax.security.auth.callback.CallbackHandler;
import org.eclipse.jetty.jaas.JAASLoginService;
import org.eclipse.jetty.jaas.PropertyUserStoreManager;
import org.eclipse.jetty.security.AbstractLoginService;
import org.eclipse.jetty.security.PropertyUserStore;
import org.eclipse.jetty.security.RolePrincipal;
import org.eclipse.jetty.security.UserPrincipal;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.security.Credential;
import org.slf4j.Logger;
@ -122,22 +124,23 @@ public class PropertyFileLoginModule extends AbstractLoginModule
* @throws Exception if unable to get the user information
*/
@Override
public UserInfo getUserInfo(String userName) throws Exception
public JAASUser getUser(String userName) throws Exception
{
LOG.debug("Checking PropertyUserStore {} for {}", _store.getConfig(), userName);
UserIdentity userIdentity = _store.getUserIdentity(userName);
if (userIdentity == null)
if (LOG.isDebugEnabled())
LOG.debug("Checking PropertyUserStore {} for {}", _store.getConfig(), userName);
UserPrincipal up = _store.getUserPrincipal(userName);
if (up == null)
return null;
//TODO in future versions change the impl of PropertyUserStore so its not
//storing Subjects etc, just UserInfo
Set<AbstractLoginService.RolePrincipal> principals = userIdentity.getSubject().getPrincipals(AbstractLoginService.RolePrincipal.class);
List<String> roles = principals.stream()
.map(AbstractLoginService.RolePrincipal::getName)
.collect(Collectors.toList());
Credential credential = (Credential)userIdentity.getSubject().getPrivateCredentials().iterator().next();
return new UserInfo(userName, credential, roles);
List<RolePrincipal> rps = _store.getRolePrincipals(userName);
List<String> roles = rps == null ? Collections.emptyList() : rps.stream().map(RolePrincipal::getName).collect(Collectors.toList());
return new JAASUser(up)
{
@Override
public List<String> doFetchRoles()
{
return roles;
}
};
}
}

View File

@ -1,113 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
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;
import org.eclipse.jetty.util.thread.AutoLock;
/**
* UserInfo
*
* This is the information read from the external source
* about a user.
*
* Can be cached.
*/
public class UserInfo
{
private final AutoLock _lock = new AutoLock();
private String _userName;
private Credential _credential;
protected List<String> _roleNames = new ArrayList<>();
protected boolean _rolesLoaded = false;
/**
* @param userName the user name
* @param credential the credential
* @param roleNames a {@link List} of role name
*/
public UserInfo(String userName, Credential credential, List<String> roleNames)
{
_userName = userName;
_credential = credential;
if (roleNames != null)
{
_roleNames.addAll(roleNames);
_rolesLoaded = true;
}
}
/**
* @param userName the user name
* @param credential the credential
*/
public UserInfo(String userName, Credential credential)
{
this(userName, credential, null);
}
/**
* Should be overridden by subclasses to obtain
* role info
*
* @return List of role associated to the user
* @throws Exception if the roles cannot be retrieved
*/
public List<String> doFetchRoles()
throws Exception
{
return Collections.emptyList();
}
public void fetchRoles() throws Exception
{
try (AutoLock l = _lock.lock())
{
if (!_rolesLoaded)
{
_roleNames.addAll(doFetchRoles());
_rolesLoaded = true;
}
}
}
public String getUserName()
{
return this._userName;
}
public List<String> getRoleNames()
{
return Collections.unmodifiableList(_roleNames);
}
public boolean checkCredential(Object suppliedCredential)
{
return _credential.check(suppliedCredential);
}
protected Credential getCredential()
{
return _credential;
}
}

View File

@ -18,12 +18,14 @@
package org.eclipse.jetty.jaas;
import java.util.Collections;
import java.util.List;
import javax.security.auth.callback.Callback;
import javax.security.auth.login.LoginException;
import org.eclipse.jetty.jaas.callback.ServletRequestCallback;
import org.eclipse.jetty.jaas.spi.AbstractLoginModule;
import org.eclipse.jetty.jaas.spi.UserInfo;
import org.eclipse.jetty.security.UserPrincipal;
import org.eclipse.jetty.util.ArrayUtil;
import org.eclipse.jetty.util.security.Password;
@ -34,9 +36,16 @@ public class TestLoginModule extends AbstractLoginModule
public ServletRequestCallback _callback = new ServletRequestCallback();
@Override
public UserInfo getUserInfo(String username) throws Exception
{
return new UserInfo(username, new Password("aaa"));
public JAASUser getUser(String username) throws Exception
{
return new JAASUser(new UserPrincipal(username, new Password("aaa")))
{
@Override
public List<String> doFetchRoles() throws Exception
{
return Collections.emptyList();
}
};
}
@Override

View File

@ -19,9 +19,12 @@
package org.eclipse.jetty.security.jaspi;
import java.io.IOException;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
@ -29,6 +32,8 @@ import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.security.AbstractLoginService;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.RolePrincipal;
import org.eclipse.jetty.security.UserPrincipal;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
@ -55,7 +60,7 @@ public class JaspiTest
public class TestLoginService extends AbstractLoginService
{
protected Map<String, UserPrincipal> _users = new HashMap<>();
protected Map<String, String[]> _roles = new HashMap();
protected Map<String, List<RolePrincipal>> _roles = new HashMap<>();
public TestLoginService(String name)
{
@ -66,11 +71,15 @@ public class JaspiTest
{
UserPrincipal userPrincipal = new UserPrincipal(username, credential);
_users.put(username, userPrincipal);
_roles.put(username, roles);
if (roles != null)
{
List<RolePrincipal> rps = Arrays.stream(roles).map(RolePrincipal::new).collect(Collectors.toList());
_roles.put(username, rps);
}
}
@Override
protected String[] loadRoleInfo(UserPrincipal user)
protected List<RolePrincipal> loadRoleInfo(UserPrincipal user)
{
return _roles.get(user.getName());
}

View File

@ -27,6 +27,7 @@ import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
@ -35,16 +36,17 @@ import javax.sql.DataSource;
import org.eclipse.jetty.plus.jndi.NamingEntryUtil;
import org.eclipse.jetty.security.AbstractLoginService;
import org.eclipse.jetty.security.IdentityService;
import org.eclipse.jetty.security.RolePrincipal;
import org.eclipse.jetty.security.UserPrincipal;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.security.Credential;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* DataSourceUserRealm
* DataSourceLoginService
* <p>
* Obtain user/password/role information from a database
* via jndi DataSource.
* Obtain user/password/role information from a database via jndi DataSource.
*/
public class DataSourceLoginService extends AbstractLoginService
{
@ -264,7 +266,7 @@ public class DataSourceLoginService extends AbstractLoginService
}
@Override
public String[] loadRoleInfo(UserPrincipal user)
public List<RolePrincipal> loadRoleInfo(UserPrincipal user)
{
DBUserPrincipal dbuser = (DBUserPrincipal)user;
@ -280,11 +282,9 @@ public class DataSourceLoginService extends AbstractLoginService
try (ResultSet rs2 = statement2.executeQuery())
{
while (rs2.next())
{
roles.add(rs2.getString(_roleTableRoleField));
}
return roles.toArray(new String[roles.size()]);
return roles.stream().map(RolePrincipal::new).collect(Collectors.toList());
}
}
}

View File

@ -18,19 +18,21 @@
package org.eclipse.jetty.security;
import java.io.Serializable;
import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
import javax.security.auth.Subject;
import jakarta.servlet.ServletRequest;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.security.Credential;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* AbstractLoginService
*
* Base class for LoginServices that allows subclasses to provide the user authentication and authorization information,
* but provides common behaviour such as handling authentication.
*/
public abstract class AbstractLoginService extends ContainerLifeCycle implements LoginService
{
@ -40,65 +42,7 @@ public abstract class AbstractLoginService extends ContainerLifeCycle implements
protected String _name;
protected boolean _fullValidate = false;
/**
* RolePrincipal
*/
public static class RolePrincipal implements Principal, Serializable
{
private static final long serialVersionUID = 2998397924051854402L;
private final String _roleName;
public RolePrincipal(String name)
{
_roleName = name;
}
@Override
public String getName()
{
return _roleName;
}
}
/**
* UserPrincipal
*/
public static class UserPrincipal implements Principal, Serializable
{
private static final long serialVersionUID = -6226920753748399662L;
private final String _name;
private final Credential _credential;
public UserPrincipal(String name, Credential credential)
{
_name = name;
_credential = credential;
}
public boolean authenticate(Object credentials)
{
return _credential != null && _credential.check(credentials);
}
public boolean authenticate(Credential c)
{
return (_credential != null && c != null && _credential.equals(c));
}
@Override
public String getName()
{
return _name;
}
@Override
public String toString()
{
return _name;
}
}
protected abstract String[] loadRoleInfo(UserPrincipal user);
protected abstract List<RolePrincipal> loadRoleInfo(UserPrincipal user);
protected abstract UserPrincipal loadUserInfo(String username);
@ -155,18 +99,22 @@ public abstract class AbstractLoginService extends ContainerLifeCycle implements
if (userPrincipal != null && userPrincipal.authenticate(credentials))
{
//safe to load the roles
String[] roles = loadRoleInfo(userPrincipal);
List<RolePrincipal> roles = loadRoleInfo(userPrincipal);
List<String> roleNames = new ArrayList<>();
Subject subject = new Subject();
subject.getPrincipals().add(userPrincipal);
subject.getPrivateCredentials().add(userPrincipal._credential);
userPrincipal.configureSubject(subject);
if (roles != null)
for (String role : roles)
{
roles.forEach(p ->
{
subject.getPrincipals().add(new RolePrincipal(role));
}
p.configureForSubject(subject);
roleNames.add(p.getName());
});
}
subject.setReadOnly();
return _identityService.newUserIdentity(subject, userPrincipal, roles);
return _identityService.newUserIdentity(subject, userPrincipal, roleNames.toArray(new String[0]));
}
return null;
@ -185,10 +133,10 @@ public abstract class AbstractLoginService extends ContainerLifeCycle implements
if (user.getUserPrincipal() instanceof UserPrincipal)
{
return fresh.authenticate(((UserPrincipal)user.getUserPrincipal())._credential);
return fresh.authenticate(((UserPrincipal)user.getUserPrincipal()));
}
throw new IllegalStateException("UserPrincipal not KnownUser"); //can't validate
throw new IllegalStateException("UserPrincipal not known"); //can't validate
}
@Override
@ -201,7 +149,6 @@ public abstract class AbstractLoginService extends ContainerLifeCycle implements
public void logout(UserIdentity user)
{
//Override in subclasses
}
public boolean isFullValidate()

View File

@ -19,20 +19,13 @@
package org.eclipse.jetty.security;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jetty.server.UserIdentity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Properties User Realm.
* <p>
* An implementation of UserRealm that stores users and roles in-memory in HashMaps.
* <p>
* Typically these maps are populated by calling the load() method or passing a properties resource to the constructor. The format of the properties file is:
*
* An implementation of a LoginService that stores users and roles in-memory in HashMaps.
* The source of the users and roles information is a properties file formatted like so:
* <pre>
* username: password [,rolename ...]
* </pre>
@ -72,7 +65,7 @@ public class HashLoginService extends AbstractLoginService
}
/**
* Load realm users from properties file.
* Load users from properties file.
* <p>
* The property file maps usernames to password specs followed by an optional comma separated list of role names.
* </p>
@ -121,41 +114,21 @@ public class HashLoginService extends AbstractLoginService
}
@Override
protected String[] loadRoleInfo(UserPrincipal user)
protected List<RolePrincipal> loadRoleInfo(UserPrincipal user)
{
UserIdentity id = _userStore.getUserIdentity(user.getName());
if (id == null)
return null;
Set<RolePrincipal> roles = id.getSubject().getPrincipals(RolePrincipal.class);
if (roles == null)
return null;
List<String> list = roles.stream()
.map(rolePrincipal -> rolePrincipal.getName())
.collect(Collectors.toList());
return list.toArray(new String[roles.size()]);
return _userStore.getRolePrincipals(user.getName());
}
@Override
protected UserPrincipal loadUserInfo(String userName)
{
UserIdentity id = _userStore.getUserIdentity(userName);
if (id != null)
{
return (UserPrincipal)id.getUserPrincipal();
}
return null;
return _userStore.getUserPrincipal(userName);
}
@Override
protected void doStart() throws Exception
{
super.doStart();
// can be null so we switch to previous behaviour using PropertyUserStore
if (_userStore == null)
{
if (LOG.isDebugEnabled())
@ -179,7 +152,6 @@ public class HashLoginService extends AbstractLoginService
}
/**
* To facilitate testing.
*
* @return true if a UserStore has been created from a config, false if a UserStore was provided.
*/

View File

@ -18,7 +18,6 @@
package org.eclipse.jetty.security;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
@ -28,6 +27,7 @@ import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;
import jakarta.servlet.ServletRequest;
import org.eclipse.jetty.util.Loader;
@ -37,17 +37,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* HashMapped User Realm with JDBC as data source.
* The {@link #login(String, Object, ServletRequest)} method checks the inherited Map for the user. If the user is not
* found, it will fetch details from the database and populate the inherited
* Map. It then calls the superclass {@link #login(String, Object, ServletRequest)} method to perform the actual
* authentication. Periodically (controlled by configuration parameter),
* internal hashes are cleared. Caching can be disabled by setting cache refresh
* interval to zero. Uses one database connection that is initialized at
* startup. Reconnect on failures.
* <p>
* An example properties file for configuration is in
* <code>${jetty.home}/etc/jdbcRealm.properties</code>
* JDBC as a source of user authentication and authorization information.
* Uses one database connection that is lazily initialized. Reconnect on failures.
*/
public class JDBCLoginService extends AbstractLoginService
{
@ -61,16 +52,18 @@ public class JDBCLoginService extends AbstractLoginService
protected String _userTableKey;
protected String _userTablePasswordField;
protected String _roleTableRoleField;
protected Connection _con;
protected String _userSql;
protected String _roleSql;
protected Connection _con;
/**
* JDBCKnownUser
* JDBCUserPrincipal
*
* A UserPrincipal with extra jdbc key info.
*/
public class JDBCUserPrincipal extends UserPrincipal
{
int _userKey;
final int _userKey;
public JDBCUserPrincipal(String name, Credential credential, int key)
{
@ -85,25 +78,21 @@ public class JDBCLoginService extends AbstractLoginService
}
public JDBCLoginService()
throws IOException
{
}
public JDBCLoginService(String name)
throws IOException
{
setName(name);
}
public JDBCLoginService(String name, String config)
throws IOException
{
setName(name);
setConfig(config);
}
public JDBCLoginService(String name, IdentityService identityService, String config)
throws IOException
{
setName(name);
setIdentityService(identityService);
@ -171,19 +160,12 @@ public class JDBCLoginService extends AbstractLoginService
}
/**
* (re)Connect to database with parameters setup by loadConfig()
* Connect to database with parameters setup by loadConfig()
*/
public void connectDatabase()
public Connection connectDatabase()
throws SQLException
{
try
{
Class.forName(_jdbcDriver);
_con = DriverManager.getConnection(_url, _userName, _password);
}
catch (Exception e)
{
LOG.warn("UserRealm {} could not connect to database; will try later", getName(), e);
}
return DriverManager.getConnection(_url, _userName, _password);
}
@Override
@ -192,10 +174,7 @@ public class JDBCLoginService extends AbstractLoginService
try
{
if (null == _con)
connectDatabase();
if (null == _con)
throw new SQLException("Can't connect to database");
_con = connectDatabase();
try (PreparedStatement stat1 = _con.prepareStatement(_userSql))
{
@ -214,7 +193,7 @@ public class JDBCLoginService extends AbstractLoginService
}
catch (SQLException e)
{
LOG.warn("UserRealm {} could not load user information from database", getName(), e);
LOG.warn("LoginService {} could not load user {}", getName(), username, e);
closeConnection();
}
@ -222,17 +201,17 @@ public class JDBCLoginService extends AbstractLoginService
}
@Override
public String[] loadRoleInfo(UserPrincipal user)
public List<RolePrincipal> loadRoleInfo(UserPrincipal user)
{
if (user == null)
return null;
JDBCUserPrincipal jdbcUser = (JDBCUserPrincipal)user;
try
{
if (null == _con)
connectDatabase();
if (null == _con)
throw new SQLException("Can't connect to database");
_con = connectDatabase();
List<String> roles = new ArrayList<String>();
@ -242,16 +221,15 @@ public class JDBCLoginService extends AbstractLoginService
try (ResultSet rs2 = stat2.executeQuery())
{
while (rs2.next())
{
roles.add(rs2.getString(_roleTableRoleField));
}
return roles.toArray(new String[roles.size()]);
return roles.stream().map(RolePrincipal::new).collect(Collectors.toList());
}
}
}
catch (SQLException e)
{
LOG.warn("UserRealm {} could not load user information from database", getName(), e);
LOG.warn("LoginService {} could not load roles for user {}", getName(), user.getName(), e);
closeConnection();
}
@ -273,7 +251,7 @@ public class JDBCLoginService extends AbstractLoginService
if (_con != null)
{
if (LOG.isDebugEnabled())
LOG.debug("Closing db connection for JDBCUserRealm");
LOG.debug("Closing db connection for JDBCLoginService");
try
{
_con.close();

View File

@ -206,7 +206,7 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener
@Override
public String toString()
{
return String.format("%s@%x[users.count=%d,identityService=%s]", getClass().getSimpleName(), hashCode(), getKnownUserIdentities().size(), getIdentityService());
return String.format("%s[cfg=%s]", super.toString(), _configPath);
}
protected void loadUsers() throws IOException
@ -251,7 +251,7 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener
}
}
List<String> currentlyKnownUsers = new ArrayList<>(getKnownUserIdentities().keySet());
List<String> currentlyKnownUsers = new ArrayList<>(_users.keySet());
// if its not the initial load then we want to process removed users
if (!_firstLoad)
{

View File

@ -0,0 +1,52 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.security;
import java.io.Serializable;
import java.security.Principal;
import javax.security.auth.Subject;
/**
* RolePrincipal
*
* Represents a role. This class can be added to a Subject to represent a role that the
* Subject has.
*
*/
public class RolePrincipal implements Principal, Serializable
{
private static final long serialVersionUID = 2998397924051854402L;
private final String _roleName;
public RolePrincipal(String name)
{
_roleName = name;
}
@Override
public String getName()
{
return _roleName;
}
public void configureForSubject(Subject subject)
{
subject.getPrincipals().add(this);
}
}

View File

@ -0,0 +1,92 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.security;
import java.io.Serializable;
import java.security.Principal;
import javax.security.auth.Subject;
import org.eclipse.jetty.util.security.Credential;
/**
* UserPrincipal
*
* Represents a user with a credential.
* Instances of this class can be added to a Subject to
* present the user, while the credentials can be added
* directly to the Subject.
*/
public class UserPrincipal implements Principal, Serializable
{
private static final long serialVersionUID = -6226920753748399662L;
private final String _name;
protected final Credential _credential;
public UserPrincipal(String name, Credential credential)
{
_name = name;
_credential = credential;
}
public boolean authenticate(Object credentials)
{
return _credential != null && _credential.check(credentials);
}
public boolean authenticate(Credential c)
{
return (_credential != null && c != null && _credential.equals(c));
}
public boolean authenticate(UserPrincipal u)
{
return (u != null && authenticate(u._credential));
}
public void configureSubject(Subject subject)
{
if (subject == null)
return;
subject.getPrincipals().add(this);
if (_credential != null)
subject.getPrivateCredentials().add(_credential);
}
public void deconfigureSubject(Subject subject)
{
if (subject == null)
return;
subject.getPrincipals().remove(this);
if (_credential != null)
subject.getPrivateCredentials().remove(_credential);
}
@Override
public String getName()
{
return _name;
}
@Override
public String toString()
{
return _name;
}
}

View File

@ -18,59 +18,75 @@
package org.eclipse.jetty.security;
import java.security.Principal;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.security.auth.Subject;
import java.util.stream.Collectors;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.security.Credential;
/**
* Base class to store User
* Store of user authentication and authorization information.
*
*/
public class UserStore extends AbstractLifeCycle
{
private IdentityService _identityService = new DefaultIdentityService();
private final Map<String, UserIdentity> _knownUserIdentities = new ConcurrentHashMap<>();
protected final Map<String, User> _users = new ConcurrentHashMap<>();
protected class User
{
protected UserPrincipal _userPrincipal;
protected List<RolePrincipal> _rolePrincipals = Collections.emptyList();
protected User(String username, Credential credential, String[] roles)
{
_userPrincipal = new UserPrincipal(username, credential);
_rolePrincipals = Collections.emptyList();
if (roles != null)
_rolePrincipals = Arrays.stream(roles).map(RolePrincipal::new).collect(Collectors.toList());
}
protected UserPrincipal getUserPrincipal()
{
return _userPrincipal;
}
protected List<RolePrincipal> getRolePrincipals()
{
return _rolePrincipals;
}
}
public void addUser(String username, Credential credential, String[] roles)
{
Principal userPrincipal = new AbstractLoginService.UserPrincipal(username, credential);
Subject subject = new Subject();
subject.getPrincipals().add(userPrincipal);
subject.getPrivateCredentials().add(credential);
if (roles != null)
{
for (String role : roles)
{
subject.getPrincipals().add(new AbstractLoginService.RolePrincipal(role));
}
}
subject.setReadOnly();
_knownUserIdentities.put(username, _identityService.newUserIdentity(subject, userPrincipal, roles));
_users.put(username, new User(username, credential, roles));
}
public void removeUser(String username)
{
_knownUserIdentities.remove(username);
_users.remove(username);
}
public UserPrincipal getUserPrincipal(String username)
{
User user = _users.get(username);
return (user == null ? null : user.getUserPrincipal());
}
public List<RolePrincipal> getRolePrincipals(String username)
{
User user = _users.get(username);
return (user == null ? null : user.getRolePrincipals());
}
public UserIdentity getUserIdentity(String userName)
@Override
public String toString()
{
return _knownUserIdentities.get(userName);
}
public IdentityService getIdentityService()
{
return _identityService;
}
public Map<String, UserIdentity> getKnownUserIdentities()
{
return _knownUserIdentities;
return String.format("%s@%x[users.count=%d]", getClass().getSimpleName(), hashCode(), _users.size());
}
}

View File

@ -191,9 +191,9 @@ public class PropertyUserStoreTest
store.start();
assertThat("Failed to retrieve UserIdentity directly from PropertyUserStore", store.getUserIdentity("tom"), notNullValue());
assertThat("Failed to retrieve UserIdentity directly from PropertyUserStore", store.getUserIdentity("dick"), notNullValue());
assertThat("Failed to retrieve UserIdentity directly from PropertyUserStore", store.getUserIdentity("harry"), notNullValue());
assertThat("Failed to retrieve user directly from PropertyUserStore", store.getUserPrincipal("tom"), notNullValue());
assertThat("Failed to retrieve user directly from PropertyUserStore", store.getUserPrincipal("dick"), notNullValue());
assertThat("Failed to retrieve user directly from PropertyUserStore", store.getUserPrincipal("harry"), notNullValue());
userCount.assertThatCount(is(3));
userCount.awaitCount(3);
}
@ -224,12 +224,12 @@ public class PropertyUserStoreTest
store.start();
assertThat("Failed to retrieve UserIdentity directly from PropertyUserStore", //
store.getUserIdentity("tom"), notNullValue());
assertThat("Failed to retrieve UserIdentity directly from PropertyUserStore", //
store.getUserIdentity("dick"), notNullValue());
assertThat("Failed to retrieve UserIdentity directly from PropertyUserStore", //
store.getUserIdentity("harry"), notNullValue());
assertThat("Failed to retrieve user directly from PropertyUserStore", //
store.getUserPrincipal("tom"), notNullValue());
assertThat("Failed to retrieve user directly from PropertyUserStore", //
store.getUserPrincipal("dick"), notNullValue());
assertThat("Failed to retrieve user directly from PropertyUserStore", //
store.getUserPrincipal("harry"), notNullValue());
userCount.assertThatCount(is(3));
userCount.awaitCount(3);
}
@ -264,7 +264,7 @@ public class PropertyUserStoreTest
addAdditionalUser(usersFile, "skip: skip, roleA\n");
userCount.awaitCount(4);
assertThat(loadCount.get(), is(2));
assertThat(store.getUserIdentity("skip"), notNullValue());
assertThat(store.getUserPrincipal("skip"), notNullValue());
userCount.assertThatCount(is(4));
userCount.assertThatUsers(hasItem("skip"));

View File

@ -44,26 +44,14 @@ public class TestLoginService extends AbstractLoginService
}
@Override
protected String[] loadRoleInfo(UserPrincipal user)
protected List<RolePrincipal> loadRoleInfo(UserPrincipal user)
{
UserIdentity userIdentity = userStore.getUserIdentity(user.getName());
Set<RolePrincipal> roles = userIdentity.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()]);
return userStore.getRolePrincipals(user.getName());
}
@Override
protected UserPrincipal loadUserInfo(String username)
{
UserIdentity userIdentity = userStore.getUserIdentity(username);
return userIdentity == null ? null : (UserPrincipal)userIdentity.getUserPrincipal();
return userStore.getUserPrincipal(username);
}
}

View File

@ -19,10 +19,7 @@
package org.eclipse.jetty.security;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.security.Credential;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -44,30 +41,21 @@ public class UserStoreTest
@Test
public void addUser()
{
this.userStore.addUser("foo", Credential.getCredential("beer"), new String[]{"pub"});
assertEquals(1, this.userStore.getKnownUserIdentities().size());
UserIdentity userIdentity = this.userStore.getUserIdentity("foo");
assertNotNull(userIdentity);
assertEquals("foo", userIdentity.getUserPrincipal().getName());
Set<AbstractLoginService.RolePrincipal>
roles = userIdentity.getSubject().getPrincipals(AbstractLoginService.RolePrincipal.class);
List<String> list = roles.stream()
.map(rolePrincipal -> rolePrincipal.getName())
.collect(Collectors.toList());
assertEquals(1, list.size());
assertEquals("pub", list.get(0));
userStore.addUser("foo", Credential.getCredential("beer"), new String[]{"pub"});
assertNotNull(userStore.getUserPrincipal("foo"));
List<RolePrincipal> rps = userStore.getRolePrincipals("foo");
assertNotNull(rps);
assertNotNull(rps.get(0));
assertEquals("pub", rps.get(0).getName());
}
@Test
public void removeUser()
{
this.userStore.addUser("foo", Credential.getCredential("beer"), new String[]{"pub"});
assertEquals(1, this.userStore.getKnownUserIdentities().size());
UserIdentity userIdentity = this.userStore.getUserIdentity("foo");
assertNotNull(userIdentity);
assertEquals("foo", userIdentity.getUserPrincipal().getName());
assertNotNull(userStore.getUserPrincipal("foo"));
userStore.removeUser("foo");
userIdentity = this.userStore.getUserIdentity("foo");
assertNull(userIdentity);
assertNull(userStore.getUserPrincipal("foo"));
}
}

View File

@ -24,10 +24,13 @@ import java.net.Socket;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
@ -43,6 +46,8 @@ import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.security.AbstractLoginService;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.RolePrincipal;
import org.eclipse.jetty.security.UserPrincipal;
import org.eclipse.jetty.security.authentication.DigestAuthenticator;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.NetworkConnector;
@ -85,7 +90,7 @@ public class DigestPostTest
public static class TestLoginService extends AbstractLoginService
{
protected Map<String, UserPrincipal> users = new HashMap<>();
protected Map<String, String[]> roles = new HashMap<>();
protected Map<String, List<RolePrincipal>> roles = new HashMap<>();
public TestLoginService(String name)
{
@ -96,11 +101,12 @@ public class DigestPostTest
{
UserPrincipal userPrincipal = new UserPrincipal(username, credential);
users.put(username, userPrincipal);
roles.put(username, rolenames);
if (rolenames != null)
roles.put(username, Arrays.stream(rolenames).map(RolePrincipal::new).collect(Collectors.toList()));
}
@Override
protected String[] loadRoleInfo(UserPrincipal user)
protected List<RolePrincipal> loadRoleInfo(UserPrincipal user)
{
return roles.get(user.getName());
}