What is the use of J2C alias on WAS server? - spring

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.

Related

How to authenticate to LDAP using Spring LDAP

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.

AWS Elasticache Jedis using credentials

I need to connect to a redis instance in my Elasticache. As I understand from Amazon Elasticache Redis cluster - Can't get Endpoint, I can get the endpoint from this.
Now suppose I get the endpoint and I use this endpoint to create a JedisClient(Since I use java) then How do I provide the AWS IAM credentials?
I am going to secure ElastiCache using IAM policies. How do I ensure no other application connects to this redis?
static AWSCredentials credentials = null;
static {
try {
//credentials = new ProfileCredentialsProvider("default").getCredentials();
credentials = new SystemPropertiesCredentialsProvider().getCredentials();
} catch (Exception e) {
System.out.println("Got exception..........");
throw new AmazonClientException("Cannot load the credentials from the credential profiles file. "
+ "Please make sure that your credentials file is at the correct "
+ "location (/Users/USERNAME/.aws/credentials), and is in valid format.", e);
}
}
#Bean
public LettuceConnectionFactory redisConnectionFactory() {
AmazonElastiCache elasticacheClient = AmazonElastiCacheClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(credentials)).withRegion(Regions.US_EAST_1).build();
DescribeCacheClustersRequest dccRequest = new DescribeCacheClustersRequest();
dccRequest.setShowCacheNodeInfo(true);
DescribeCacheClustersResult clusterResult = elasticacheClient.describeCacheClusters(dccRequest);
List<CacheCluster> cacheClusters = clusterResult.getCacheClusters();
List<String> clusterNodes = new ArrayList <String> ();
try {
for (CacheCluster cacheCluster : cacheClusters) {
for (CacheNode cacheNode : cacheCluster.getCacheNodes()) {
String addr = cacheNode.getEndpoint().getAddress();
int port = cacheNode.getEndpoint().getPort();
String url = addr + ":" + port;
if(<ReplicationGroup Name>.equalsIgnoreCase(cacheCluster.getReplicationGroupId()))
clusterNodes.add(url);
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
LettuceConnectionFactory redisConnectionFactory = new LettuceConnectionFactory(new RedisClusterConfiguration(clusterNodes));
redisConnectionFactory.setUseSsl(true);
redisConnectionFactory.afterPropertiesSet();
return redisConnectionFactory;
}

LDAP Connection Pooling with spring security

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.

How to create JMX client which can interect with multiple jmx server using different serviceUrl

I am using Spring jmx to create jmx client which can interact with Cassandra cluster to get a mbean object attribute Livedicsspaceused.
So this Cassandra cluster had 3 node hence different serviceUrl (each having different ip address).
Now I realize that while creating MBeanServerConnectionFactoryBean bean I can specify only one service URl like below:
#Bean
MBeanServerConnectionFactoryBean getConnector() {
MBeanServerConnectionFactoryBean mBeanfactory = new MBeanServerConnectionFactoryBean();
try {
mBeanfactory.setServiceUrl("serviceUrl1");
} catch (MalformedURLException e) {
e.printStackTrace();
}
mBeanfactory.setConnectOnStartup(false);
return mBeanfactory;
}
Then in main I am accessing this as below:
objectName = newObjectName(QueueServicesConstant.MBEAN_OBJ_NAME_LIVE_DISC_USED);
long count = (Long)mBeanFactory.getObject().getAttribute(objectName, QueueServicesConstant.MBEAN_ATTR_NAME_COUNT);
How can i get this value in all three nodes?
You need 3 distinct connectors.
Or you can use something like a Jolokia Proxy to access multiple servers (using REST instead of JSR 160).
This is how I solved the problem ..Instead of using Spring-JMX, I am directly using javax.management apis..So my code below will get any one of the connector which will be sufficient to provide me correct attribute value however it will try to connect to ohther node if it fails to get connector from one server node.
#SuppressWarnings("restriction")
private Object getMbeanAttributeValue(String MbeanObectName,
String attributeName) throws IOException,
AttributeNotFoundException, InstanceNotFoundException,
MBeanException, ReflectionException, MalformedObjectNameException {
Object attributeValue = null;
JMXConnector jmxc = null;
try {
State state = metaTemplate.getSession().getState();
List<String> serviceUrlList = getJmxServiceUrlList(state
.getConnectedHosts());
jmxc = getJmxConnector(serviceUrlList);
ObjectName objectName = new ObjectName(MbeanObectName);
MBeanServerConnection mbsConnection = jmxc
.getMBeanServerConnection();
attributeValue = mbsConnection.getAttribute(objectName,
attributeName);
} finally {
if (jmxc != null)
jmxc.close();
}
return attributeValue;
}
// This will provide any one of the JMX Connector of cassandra cluster
#SuppressWarnings("restriction")
private JMXConnector getJmxConnector(List<String> serviceUrlList)
throws IOException {
JMXConnector jmxc = null;
for (String serviceUrl : serviceUrlList) {
JMXServiceURL url;
try {
url = new JMXServiceURL(serviceUrl);
jmxc = JMXConnectorFactory.connect(url, null);
return jmxc;
} catch (IOException e) {
log.error(
"getJmxConnector: Error while connecting to JMX sereice {} ",
serviceUrl, e.getMessage());
}
}
throw new IOException(
"Not able to connect to any of Cassandra JMX connector.");
}

Multi tenancy in Shiro

We are evaluating Shiro for a custom Saas app that we are building. Seems like a great framework does does 90% of what we want, out of the box. My understanding of Shiro is basic, and here is what I am trying to accomplish.
We have multiple clients, each with an identical database
All authorization (Roles/Permissions) will be configured by the clients
within their own dedicated database
Each client will have a unique
Virtual host eg. client1.mycompany.com, client2.mycompany.com etc
Scenario 1
Authentication done via LDAP (MS Active Directory)
Create unique users in LDAP, make app aware of LDAP users, and have client admins provision them into whatever roles..
Scenario 2
Authentication also done via JDBC Relam in their database
Questions:
Common to Sc 1 & 2 How can I tell Shiro which database to use? I
realize it has to be done via some sort of custom authentication
filter, but can someone guide me to the most logical way ? Plan to use
the virtual host url to tell shiro and mybatis which DB to use.
Do I create one realm per client?
Sc 1 (User names are unique across clients due to LDAP) If user jdoe
is shared by client1 and client2, and he is authenticated via client1
and tries to access a resource of client2, will Shiro permit or have
him login again?
Sc 2 (User names unique within database only) If both client 1 and
client 2 create a user called jdoe, then will Shiro be able to
distinguish between jdoe in Client 1 and jdoe in Client 2 ?
My Solution based on Les's input..
public class MultiTenantAuthenticator extends ModularRealmAuthenticator {
#Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
TenantAuthenticationToken tat = null;
Realm tenantRealm = null;
if (!(authenticationToken instanceof TenantAuthenticationToken)) {
throw new AuthenticationException("Unrecognized token , not a typeof TenantAuthenticationToken ");
} else {
tat = (TenantAuthenticationToken) authenticationToken;
tenantRealm = lookupRealm(tat.getTenantId());
}
return doSingleRealmAuthentication(tenantRealm, tat);
}
protected Realm lookupRealm(String clientId) throws AuthenticationException {
Collection<Realm> realms = getRealms();
for (Realm realm : realms) {
if (realm.getName().equalsIgnoreCase(clientId)) {
return realm;
}
}
throw new AuthenticationException("No realm configured for Client " + clientId);
}
}
New Type of token..
public final class TenantAuthenticationToken extends UsernamePasswordToken {
public enum TENANT_LIST {
CLIENT1, CLIENT2, CLIENT3
}
private String tenantId = null;
public TenantAuthenticationToken(final String username, final char[] password, String tenantId) {
setUsername(username);
setPassword(password);
setTenantId(tenantId);
}
public TenantAuthenticationToken(final String username, final String password, String tenantId) {
setUsername(username);
setPassword(password != null ? password.toCharArray() : null);
setTenantId(tenantId);
}
public String getTenantId() {
return tenantId;
}
public void setTenantId(String tenantId) {
try {
TENANT_LIST.valueOf(tenantId);
} catch (IllegalArgumentException ae) {
throw new UnknownTenantException("Tenant " + tenantId + " is not configured " + ae.getMessage());
}
this.tenantId = tenantId;
}
}
Modify my inherited JDBC Realm
public class TenantSaltedJdbcRealm extends JdbcRealm {
public TenantSaltedJdbcRealm() {
// Cant seem to set this via beanutils/shiro.ini
this.saltStyle = SaltStyle.COLUMN;
}
#Override
public boolean supports(AuthenticationToken token) {
return super.supports(token) && (token instanceof TenantAuthenticationToken);
}
And finally use the new token when logging in
// This value is set via an Intercepting Servlet Filter
String client = (String)request.getAttribute("TENANT_ID");
if (!currentUser.isAuthenticated()) {
TenantAuthenticationToken token = new TenantAuthenticationToken(user,pwd,client);
token.setRememberMe(true);
try {
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal() + " is locked. "
+ "Please contact your administrator to unlock it.");
} // ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) {
//unexpected condition? error?
ae.printStackTrace();
}
}
}
You will probably need a ServletFilter that sits in front of all requests and resolves a tenantId pertaining to the request. You can store that resolved tenantId as a request attribute or a threadlocal so it is available anywhere for the duration of the request.
The next step is to probably create a sub-interface of AuthenticationToken, e.g. TenantAuthenticationToken that has a method: getTenantId(), which is populated by your request attribute or threadlocal. (e.g. getTenantId() == 'client1' or 'client2', etc).
Then, your Realm implementations can inspect the Token and in their supports(AuthenticationToken) implementation, and return true only if the token is a TenantAuthenticationToken instance and the Realm is communicating with the datastore for that particular tenant.
This implies one realm per client database. Beware though - if you do this in a cluster, and any cluster node can perform an authentication request, every client node will need to be able to connect to every client database. The same would be true for authorization if authorization data (roles, groups, permissions, etc) is also partitioned across databases.
Depending on your environment, this might not scale well depending on the number of clients - you'll need to judge accordingly.
As for JNDI resources, yes, you can reference them in Shiro INI via Shiro's JndiObjectFactory:
[main]
datasource = org.apache.shiro.jndi.JndiObjectFactory
datasource.resourceName = jdbc/mydatasource
# if the JNDI name is prefixed with java:comp/env (like a Java EE environment),
# uncomment this line:
#datasource.resourceRef = true
jdbcRealm = com.foo.my.JdbcRealm
jdbcRealm.datasource = $datasource
The factory will look up the datasource and make it available to other beans as if it were declared in the INI directly.

Resources