我的Jetty Servlet进行了以下初始化。 HashLoginService工作但是LdapLoginModule没有连接到JAASLoginService," ldaploginmodule"是指我想跳过的默认ldap-loginModule.conf并传递选项Map中的所有参数(或以某种方式指定为文件位置)。
Server jettyServer = new Server(8080);
ServletContextHandler context = new ServletContextHandler(jettyServer, "/", ServletContextHandler.SESSIONS | ServletContextHandler.SECURITY);
context.addServlet(new ServletHolder(new DefaultServlet() {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getSession().invalidate(); // do logout
response.getWriter().append("<html><form method='POST' action='/j_security_check'>"
+ "<input type='text' name='j_username'/>"
+ "<input type='password' name='j_password'/>"
+ "<input type='submit' value='Login'/></form></html>");
}
}), "/login");
context.addServlet(new ServletHolder(new MyServlet()),"/*");
Constraint constraint = new Constraint();
constraint.setName(Constraint.__FORM_AUTH);
constraint.setRoles(new String[]{"user"});
constraint.setAuthenticate(true);
ConstraintMapping constraintMapping = new ConstraintMapping();
constraintMapping.setConstraint(constraint);
constraintMapping.setPathSpec("/*");
ConstraintSecurityHandler securityHandler;
if (ldapEnabled) { // *** something is missing ****
LdapLoginModule lm = new LdapLoginModule();
Map options = new HashMap<>();
options.put( "hostname", "127.0.0.1" );
options.put( "port", "389" );
options.put( "contextFactory", "com.sun.jndi.ldap.LdapCtxFactory" );
options.put( "bindDn", "CN=admin,OU=example,OU=com" );
options.put( "bindPassword", "password" );
options.put( "userBaseDn", "dc=example,dc=com" );
lm.initialize(null,null,null,options);
securityHandler = new ConstraintSecurityHandler ();
securityHandler.addConstraintMapping(constraintMapping);
JAASLoginService loginService = new JAASLoginService("ldaploginmodule");
loginService.setIdentityService(new DefaultIdentityService());
securityHandler.setLoginService(loginService);
} else { // This works
securityHandler = new ConstraintSecurityHandler();
securityHandler.addConstraintMapping(constraintMapping);
HashLoginService loginService = new HashLoginService();
loginService.putUser("username", new Password("password"), new String[]{"user"});
securityHandler.setLoginService(loginService);
}
当用户尝试以ldapEnabled模式登录时
HTTP错误:500
访问/ j_security_check时出现问题。原因是:
java.io.IOException: ldap-loginModule.conf (No such file or directory)
如何在不使用配置文件的情况下实现此功能( jetty服务器嵌入式在另一个应用程序中作为动态加载的jar )
答案 0 :(得分:1)
我已经设法为嵌入式Jetty写了一个LoginService
,它看起来像在LDAP上,而不会烦人“您必须使用jvm参数,因为这是jvm的要求”(或者它们关闭时的含义是什么) https://github.com/eclipse/jetty.project/issues/1349)
请注意,由于缺乏对“命名”(主要是LDAP)的经验,我并不总是了解自己在做什么,所以请随时改进此代码。
这是一个匿名类,我正在将其放入ConstraintSecurityHandler
之前直接创建内联。组被视为角色。
LoginService loginService = new AbstractLoginService() {
private final InitialLdapContext _ldap = _getLdap(
"cn=" + CONFIG.getString("ldap.manager") + "," + CONFIG.getString("ldap.baseDn"),
CONFIG.getString("ldap.managerPassword"));
@Override
protected void finalize() throws Throwable {
_ldap.close();
}
private InitialLdapContext _getLdap(String userDn, String password) {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.PROVIDER_URL, CONFIG.getString("ldap.server"));
env.put(Context.SECURITY_PRINCIPAL, userDn);
env.put(Context.SECURITY_CREDENTIALS, password);//dn user password
try {
InitialLdapContext ldap = new InitialLdapContext(env, null);
return ldap;
} catch (AuthenticationException e) {
return null;
} catch (NamingException e) {
return null;
}
}
// Based on https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java
private String _escapeLDAPSearchFilter(String filter) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < filter.length(); i++) {
char curChar = filter.charAt(i);
switch (curChar) {
case '\\':
sb.append("\\5c");
break;
case '*':
sb.append("\\2a");
break;
case '(':
sb.append("\\28");
break;
case ')':
sb.append("\\29");
break;
case '\u0000':
sb.append("\\00");
break;
default:
sb.append(curChar);
}
}
return sb.toString();
}
@Override
protected String[] loadRoleInfo(AbstractLoginService.UserPrincipal user) {
String groupBaseDn = CONFIG.getString("ldap.groupBaseDn") + "," + CONFIG.getString("ldap.baseDn");
String search = CONFIG.getString("ldap.groupFilter");
String userDn;
if (CONFIG.getBoolean("ldap.usePosixGroups", true)) {
userDn = user.getName();
} else {
userDn = "uid=" + user.getName() + "," + CONFIG.getString("ldap.userBaseDn") + "," + CONFIG.getString("ldap.baseDn"); // TODO: not sure in this, never tested
}
search = search + "(" + CONFIG.getString("ldap.groupMemberAttribute") + "=" + _escapeLDAPSearchFilter(userDn) + ")";
search = "(&" + search + ")";
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setTimeLimit(30000);
NamingEnumeration<SearchResult> enumeration = null;
ArrayList<String> roles = new ArrayList<>();
try {
enumeration = _ldap.search(groupBaseDn, search, searchControls);
while(enumeration.hasMore()){
SearchResult result = enumeration.nextElement();
final Attributes attributes = result.getAttributes();
Attribute attribute = attributes.get(CONFIG.getString("ldap.groupIdAttribute"));
if (attribute != null) {
roles.add((String) attribute.get());
}
}
} catch (NamingException e) {
} finally {
if (enumeration != null) {
try {
enumeration.close();
} catch (NamingException ee) {
}
}
}
String[] ret = new String[roles.size()];
return roles.toArray(ret);
}
@Override
protected AbstractLoginService.UserPrincipal loadUserInfo(String username) {
final Credential credential = new Credential() {
@Override
public boolean check(Object credentials) {
InitialLdapContext myLdap = _getLdap(
"uid=" + username + "," + CONFIG.getString("ldap.userBaseDn") + "," + CONFIG.getString("ldap.baseDn"),
(String) credentials);
if (myLdap == null) {
return false;
} else {
try {
myLdap.close();
} catch (NamingException e) {
//okay...
}
return true;
}
}
};
final AbstractLoginService.UserPrincipal webUser = new UserPrincipal(username, credential);
return webUser;
}
};
CONFIG
是我的配置文件,如下所示:
ldap.server=ldap://192.168.100.200
ldap.manager=admin
ldap.managerPassword=ldapadmin
ldap.baseDn=dc=example,dc=com
ldap.userBaseDn=ou=People
ldap.groupBaseDn=ou=Groups
ldap.groupMemberAttribute=memberUid
ldap.usePosixGroups=true
ldap.userFilter=(objectClass=inetOrgPerson)
ldap.groupFilter=(objectClass=posixGroup)
ldap.groupIdAttribute=cn
Ldap在ldap://192.168.100.200上进行了以下设置(我认为是很久以前的事情)
dn: ou=People,dc=example,dc=com
objectClass: organizationalUnit
ou: People
dn: ou=Groups,dc=example,dc=com
objectClass: organizationalUnit
ou: Groups
dn: uid=testuser01,ou=People,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: testuser01
sn: User01
givenName: Test01
cn: testuser01
displayName: Test User 01
uidNumber: 10001
gidNumber: 10001
userPassword: 12345qw
homeDirectory: /home/testuser01
dn: uid=testuser02,ou=People,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: testuser02
sn: User02
givenName: Test02
cn: testuser02
displayName: Test User 02
uidNumber: 10002
gidNumber: 10002
userPassword: 12345qw
homeDirectory: /home/testuser02
dn: uid=testuser03,ou=People,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: testuser03
sn: User03
givenName: Test03
cn: testuser03
displayName: Test User 03
uidNumber: 10003
gidNumber: 10003
userPassword: 12345qw
homeDirectory: /home/testuser03
dn: cn=admins,ou=Groups,dc=example,dc=com
objectClass: posixGroup
cn: admins
gidNumber: 5000
memberUid: testuser01
dn: cn=staff,ou=Groups,dc=example,dc=com
objectClass: posixGroup
cn: staff
gidNumber: 5001
memberUid: testuser01
memberUid: testuser02
memberUid: testuser03
dn: cn=management,ou=Groups,dc=example,dc=com
objectClass: posixGroup
cn: management
gidNumber: 5003
memberUid: testuser02
如果在任何地方都可以使用相同的方法但在不同的环境中进行了测试的两三个方法包装器库,那就太好了,这样我就不必写这个了。
答案 1 :(得分:0)