I'm kind of newbie on Spring Boot and have a Jndi code that authenticates to an LDAP server just fine. But now i want to migrate my code to Spring LDAP, but get [LDAP: error code 49 - 80090308: LdapErr: DSID-0C09044E, comment: AcceptSecurityContext error, data 52e, v2580] every time.
So my JNDI code looks something like this:
public void connect(String userName, String pwd) throws NamingException, IllegalStateException {
Hashtable<String, String> env = new Hashtable<String, String>();
try {
env.put(LdapContext.CONTROL_FACTORIES, "com.sun.jndi.ldap.ControlFactory");
env.put(Context.SECURITY_PRINCIPAL, userName+"#domain.net);
env.put(Context.SECURITY_CREDENTIALS, pwd);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://server:389");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
context = new InitialDirContext(env);
} finally {
if (context !=null) {
log.info("Authentication successfull");
try {
context.close();
context = null;
} catch(NamingException ne) {
log.error(ne.getMessage());
ne.printStackTrace();
}
} else {
throw new IllegalStateException("Can't obtain LDAP context");
}
}
}
Very straightforward.
So far i have configured in my Spring boot application.properties file, the following:
spring.ldap.urls=Ldap://server:389
spring.ldap.embedded.credential.username=cn=ceadministrator
spring.ldap.embedded.credential.password=******
spring.ldap.embedded.base-dn=dc=domain,dc=net
Letting Spring Ldap manage the connection and initialization
Implemented this in order to search for some user:
public List<User> getUser(String userName) throws NamingException, LDAPException {
LdapQuery query = LdapQueryBuilder.query()
.searchScope(SearchScope.SUBTREE)
.timeLimit(3000)
.countLimit(10)
.attributes("cn")
.base(ldapConfig.getBase())
.where("objectClass").is("user")
.and("sAMAccountName").is(userName);
log.info("ldapTemplate: "+ldapTemplate);
return ldapTemplate.search(query, new UserAttributesMapper());
}
private class UserAttributesMapper implements AttributesMapper<User> {
#Override
public User mapFromAttributes(Attributes attributes) throws NamingException {
User user = new User();
if (attributes == null) {
log.warn("atttrs null");
return user;
}
user.setFirstName((String) attributes.get("cn").get());
Attribute sn = attributes.get("sAMAccountName");
if (sn != null) {
user.setUserName((String) sn.get());
}
return user;
}
}
But it throws an AutheticationException:
javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C09044E, comment: AcceptSecurityContext error, data 52e, v2580
What am i missing/doing wrong?... Please help
Well finally i got it.
I discovered that SpringBoot offers an embedded LDAP server, inserting the
"unboundid-ldapsdk" dependency into your pom.xml file. This is no my desired scenario, because i already had a production environment LDAP server to connect to.
So i simply needed to configure the following properties in my application.properties file like this:
spring.ldap.urls=Ldap://192.17.1.4:389
spring.ldap.base=ou=Organization,OU=Some users unit,dc=depr,dc=net
spring.ldap.username=administrator
spring.ldap.password=SomePassword
Understanding that the "Spring.ldap.base" is the base where my searches are going to start and has nothing to do with my administrator credentials.
Related
I have a Java JDBC application, after 100 queries of Select Jetty server crashed and return below error:
ERROR com.zaxxer.hikari.pool.HikariPool - dev-server - Exception during pool initialization.
java.sql.SQLNonTransientConnectionException: Too many connections
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:526)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:513)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:115)
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:1606)
at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:633)
at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:347)
at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:219)
at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:95)
at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:358)
at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:201)
at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:443)
at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:514)
at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:111)
at com.zaxxer.hikari.HikariDataSource.<init>(HikariDataSource.java:72)
at com.jrg.platform.commons.hikari.HikariDataSourceSupport.getDataSource(HikariDataSourceSupport.java:70)
at com.jrg.platform.commons.hikari.HikariDataSourceSupport.getConnection(HikariDataSourceSupport.java:82)
at com.jrg.platform.services.internal.pcms.data.campaign.CampaignAlertDaoImpl.getCampaignAlerts(CampaignAlertDaoImpl.java:40)
at com.jrg.platform.services.internal.pcms.service.CampaignAlertServiceImpl.campaignAlerts(CampaignAlertServiceImpl.java:43)
at com.jrg.platform.services.internal.pcms.resource.CampaignAlertResource.getCampaignAlerts(CampaignAlertResource.java:52)
at com.jrg.platform.services.internal.pcms.resource.CampaignAlertResource_$$_jvstf5a_6._d5getCampaignAlerts(CampaignAlertResource_$$_jvstf5a_6.java)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Unable to find the solution. I am just getting the connection from HikariDataSourceSupport and performing the query.
the sample code of getting connection is given below:
default HikariDataSource getDataSource(E e, boolean readOnly) throws IOException {
String key = this.getKey(e, readOnly);
Map<String, HikariDataSource> sources = this.getDataSources();
if(!sources.containsKey(key)) {
synchronized(this.getMonitor()) {
if(!sources.containsKey(key)) {
if(logger.isDebugEnabled()) {
logger.debug("creating new DataSource for {}", key);
}
Config config = this.getConfig(e, readOnly);
if(!config.hasPathOrNull("jdbcUrl")) {
throw new EnvironmentNotConfigured(e, readOnly);
}
sources.put(key, new HikariDataSource(new HikariConfig(ConfigUtils.asProperties(config))));
}
}
}
return (HikariDataSource)sources.get(key);
}
default Connection getConnection(E env, boolean readOnly) throws SQLException, IOException {
return this.getDataSource(env, readOnly).getConnection();
}
There was problem in configuration HikariCP. When request generated and try to get connection, the create connection method was creating the new datasource because the last state was destroy due to view scope of the bean.
now i have converted that class into to singalton and created the implementation of that interface to inject in the code. it is saving the state of previous data source connection. Now it is working perfect with minimum idle connections.
the code can be seen here:
APP.java
bind(HikariLucktasticDataSourceSupportImpl.class)
.to(HikariDataSourceSupport.class)
.named("hdsSupport")
.in(Singleton.class);
in DAO Layer:
#Inject
#Named("hdsSupport")
private HikariDataSourceSupport hdsSupport;
I was trying setup LDAP connection pooling using spring security and xml based configuration.
Below is my configuration,
<authentication-manager id="authenticationManager">
<ldap-authentication-provider server-ref="ldapServer"
user-dn-pattern="uid={0},ou=users"
group-search-filter="(&(objectClass=groupOfUniqueNames)(uniqueMember={0}))"
group-search-base="ou=groups"
group-role-attribute="cn"
role-prefix="ROLE_"
user-context-mapper-ref="ldapContextMapperImpl">
</ldap-authentication-provider>
</authentication-manager>
How do i provide all the connection pooling configuration?
I am intending to use PoolingContextSource class as it provides properties to configure pool size etc.
They explicitly removed pooling for ldap binds (or in Spring's case an authenticate):
https://github.com/spring-projects/spring-ldap/issues/216
ldapTemplate.authenticate searches for the user and calls contextSource.getContext to perform the ldap bind.
private AuthenticationStatus authenticate(Name base,
String filter,
String password,
SearchControls searchControls,
final AuthenticatedLdapEntryContextCallback callback,
final AuthenticationErrorCallback errorCallback) {
List<LdapEntryIdentification> result = search(base, filter, searchControls, new LdapEntryIdentificationContextMapper());
if (result.size() == 0) {
String msg = "No results found for search, base: '" + base + "'; filter: '" + filter + "'.";
LOG.info(msg);
return AuthenticationStatus.EMPTYRESULT;
} else if (result.size() > 1) {
String msg = "base: '" + base + "'; filter: '" + filter + "'.";
throw new IncorrectResultSizeDataAccessException(msg, 1, result.size());
}
final LdapEntryIdentification entryIdentification = result.get(0);
try {
DirContext ctx = contextSource.getContext(entryIdentification.getAbsoluteName().toString(), password);
executeWithContext(new ContextExecutor<Object>() {
public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException {
callback.executeWithContext(ctx, entryIdentification);
return null;
}
}, ctx);
return AuthenticationStatus.SUCCESS;
}
catch (Exception e) {
LOG.debug("Authentication failed for entry with DN '" + entryIdentification.getAbsoluteName() + "'", e);
errorCallback.execute(e);
return AuthenticationStatus.UNDEFINED_FAILURE;
}
}
By default, context sources disable pooling. From AbstractContextSource.java (which is what LdapContextSource inherits from):
public abstract class AbstractContextSource implements BaseLdapPathContextSource, InitializingBean {
...
public DirContext getContext(String principal, String credentials) {
// This method is typically called for authentication purposes, which means that we
// should explicitly disable pooling in case passwords are changed (LDAP-183).
return doGetContext(principal, credentials, EXPLICITLY_DISABLE_POOLING);
}
private DirContext doGetContext(String principal, String credentials, boolean explicitlyDisablePooling) {
Hashtable<String, Object> env = getAuthenticatedEnv(principal, credentials);
if(explicitlyDisablePooling) {
env.remove(SUN_LDAP_POOLING_FLAG);
}
DirContext ctx = createContext(env);
try {
authenticationStrategy.processContextAfterCreation(ctx, principal, credentials);
return ctx;
}
catch (NamingException e) {
closeContext(ctx);
throw LdapUtils.convertLdapException(e);
}
}
...
}
And if you try to use the PoolingContextSource, then you will get an UnsupportedOperationException when authenticate tries to call getContext:
public class PoolingContextSource
extends DelegatingBaseLdapPathContextSourceSupport
implements ContextSource, DisposableBean {
...
#Override
public DirContext getContext(String principal, String credentials) {
throw new UnsupportedOperationException("Not supported for this implementation");
}
}
This code is from the spring-ldap-core 2.3.1.RELEASE maven artifact.
You can still do connection pooling for ldap searches using the PoolingContextSource, but connection pooling for authenticates won't work.
Pooled connections doesn't work with authentication, because the way LDAP authentication works is that the connection is authenticated on creation.
This is my LdapTemplate Class
public LdapTemplate getLdapTemplete(String ldapID)
{
if (ldapID.equalsIgnoreCase(Constants.LDAP1))
{
if (ldapTemplate1 == null)
{
try
{
PasswordCredential passwordCredential = j2cAliasUtility.getAliasDetails(ldapID);
String managerDN = passwordCredential.getUserName();
String managerPwd = new String(passwordCredential.getPassword());
log.info("managerDN :"+managerDN+":: password : "+managerPwd);
LdapContextSource lcs = new LdapContextSource();
lcs.setUrl(ldapUrl1);
lcs.setUserDn(managerDN);
lcs.setPassword(managerPwd);
lcs.setDirObjectFactory(DefaultDirObjectFactory.class);
lcs.afterPropertiesSet();
ldapTemplate1 = new LdapTemplate(lcs);
log.info("ldap1 configured");
return ldapTemplate1;
}
catch (Exception e)
{
log.error("ldapContextCreater / getLdapTemplete / ldap2");
log.error("Error in getting ldap context", e);
}
}
return ldapTemplate1;
}
This is my J2CAliasUtility Class--I dont know what is this method doing and what does it return ?
public PasswordCredential getAliasDetails(String aliasName) throws Exception
{
PasswordCredential result = null;
try
{
// ----------WAS 6 change -------------
Map map = new HashMap();
map.put(com.ibm.wsspi.security.auth.callback.Constants.MAPPING_ALIAS, aliasName); //{com.ibm.mapping.authDataAlias=ldap1}
CallbackHandler cbh = (WSMappingCallbackHandlerFactory.getInstance()).getCallbackHandler(map, null);
LoginContext lc = new LoginContext("DefaultPrincipalMapping", cbh);
lc.login();
javax.security.auth.Subject subject = lc.getSubject();
java.util.Set creds = subject.getPrivateCredentials();
result = (PasswordCredential) creds.toArray()[0];
}
catch (Exception e)
{
log.info("APPLICATION ERROR: cannot load credentials for j2calias = " + aliasName);
log.error(" "+e);
throw new RuntimeException("Unable to get credentials");
}
return result;
}
J2C alias is a feature that encrypts the password used by the adapter to access the database. The adapter can use it to connect to the database instead of using a user ID and password stored in an adapter property.
J2C alias eliminates the need to store the password in clear text in an adapter configuration property, where it might be visible to others.
It would seem that your class "J2CAliasUtility" retrieves a user name and password from an JAAS (Java Authentication and Authorization Service) authentication alias, in this case apparently looked-up from LDAP. An auth alias may be configured in WebSphere Application Server as described here and here. Your code uses WebSphere security APIs to retrieve the user id and password from the given alias. More details on the programmatic logins and JAAS made be found in this IBM KnowledgeCenter topic and it's related topics.
Following are the modules in my project,
1. EJB module (version 3): We prepare ejb jar of this module and deploy on Weblogic11g server. It deals with database operation. It has #local, #Remote interface and #stateless classes implementing #local,#Remote interfaces.
2. Web Application : This web application takes inputs (user uploads file) from users, validates file and inserts data into database. It uses RMI.
Problem: On production (weblogic 11g server ) sometimes we observe exception saying $Proxy99 cannot be cast to "Remote interface name" (for different different classes) e.g com.xyz.fileProcessSetting.FileProcessSttgFacadeRemote.
But after some time when we again upload file, it gets uploaded successfully without any error.
Now, I do not understand how come these remote objects becomes temporarily unavailable? Never faced this issue on development/UAT environment. Also no idea how to reproduce and fix it.
Please help. Thanks in advance.
#Remote
public interface FileProcessSttgFacadeRemote {
//methods
}
#Local
public interface FileProcessSttgFacadeLocal {
//methods
}
#Stateless
public class FileProcessSttgFacade implements FileProcessSttgFacadeLocal, FileProcessSttgFacadeRemote {
//methods
}
in weblogic-ejb-jar.xml
<weblogic-enterprise-bean>
<ejb-name>FileProcessSttgFacade</ejb-name>
<stateless-session-descriptor>
<business-interface-jndi-name-map>
<business-remote>com.xyz.fileProcessSetting.FileProcessSttgFacadeRemote</business-remote>
<jndi-name>FileProcessSttgFacade</jndi-name>
</business-interface-jndi-name-map>
</stateless-session-descriptor>
</weblogic-enterprise-bean>
In web application also in ejb module whenever we want to call methods we use following lookup method to get remote object:
public class someclass extends EjbLocator {
public void someMethod(){
FileProcessSttgFacadeRemote fpfr = (FileProcessSttgFacadeRemote) getService("FileProcessSttgFacade");
//other code
}
}
Following is the class used for JNDI lookup:
public class EjbLocator {
public Object getService(final String jndiName) throws Exception {
try {
obj = getDefaultContext().lookup(jndiName);
} catch (final Exception exp) {
exp.printStackTrace();
}
return obj;
}
protected Context getDefaultContext() {
try {
final Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
env.put(Context.SECURITY_PRINCIPAL,"weblogic");
env.put(Context.SECURITY_CREDENTIALS, "password");
env.put(Context.PROVIDER_URL, "t3://<ip>:<port>");
defaultContext = new InitialContext(env);
return defaultContext;
} catch (final NamingException nExp) {
nExp.printStackTrace();
}
return null;
}
}
We currently have an application which uses multiple databases with the same schema. At the moment we're using a custom solution for switching between them based on the user's session. This works via
public final class DataSourceProxy extends BasicDataSource {
...
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getDetails() instanceof Map) {
Map<String, String> details = (Map<String, String>) auth.getDetails();
String targetUrl = details.get("database");
Connection c = super.getConnection();
Statement s = c.createStatement();
s.execute("USE " + targetUrl + ";");
s.close();
return c;
} else {
return super.getConnection();
}
}
Now we want to build a solution using AbstractRoutingDataSource. The problem is:
#Component
public class CustomRoutingDataSource extends AbstractRoutingDataSource {
#Autowired
Environment env;
#Autowired
DbDetailsRepositoy repo;
public CustomRoutingDataSource() {
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
for(DBDetails dbd : repo.findAll() {
// create DataSource and put it into the map
}
setTargetDataSources(new HashMap<Object, Object>());
}
#Override
protected Object determineCurrentLookupKey() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getDetails() instanceof Map) {
Map<String, String> details = (Map<String, String>) auth.getDetails();
return details.get("database");
}
return null;
}
}
Inside the constructor (or even via #PostConstruct) we have to fill the targetDataSources Map. But(!) for this we need the connection details which are stored in another database, which has its own DataSource and Entity Manager.
It seems like Spring can't determine the order of Bean construction, or maybe I'm just missing something. It always gives a NullPointerException when accessing the repository (which btw is a JpaRepository).
We're using Spring 3.2.3, Spring Data, Hibernate 4.2. Complete Annotation and Java-Code configuration of Spring and Spring Security.
Please help us!
Spring of course has to call the constructor before it can populate the properties. But that's not a Spring thing, that's basic Java 101 and one of the plenty downsides of using field injection.
To avoid this, simply add your dependencies to the constructor:
#Component
class CustomRoutingDataSource extends AbstractRoutingDataSource {
#Autowired
public CustomRoutingDataSource(DbDetailsRepository repo, Environment environment) {
…
}
…
}