-
1. Re: Using hash password in Seam security 3.1.0 with JpaIdentityStore
lightguard Mar 26, 2012 2:20 PM (in response to khosro_question)I believe you are correct, the hashing check feature hasn't been completed.
-
2. Re: Using hash password in Seam security 3.1.0 with JpaIdentityStore
s1mm0t May 22, 2012 5:54 AM (in response to lightguard)Guys, not implementing password hashing by default is really poor in my opinion. This is security 101 and leaves me questioning what else is missing from seam security and seam 3 in general. Anyway, I have come up with a work around that I hope will help other people. Please note that this solution only deals with authentication, it doesn't deal with creating users at the moment. I have used the recommended data model and this solution is aimed at that data model - it is not a generic solution as JpaIdentityStore is.
public class JpaIdentityStoreWithHashing extends JpaIdentityStore { private static final long serialVersionUID = 6538258501489656692L; private static final String DefaultEncoding = "UTF-8"; private static final String HashAlgorithm = "SHA-256"; private static final String SaltAttribute = "SALT"; private Class<?> credentialClass; public JpaIdentityStoreWithHashing(String id) { super(id); } @Override public void bootstrap(org.picketlink.idm.spi.configuration.IdentityStoreConfigurationContext configurationContext) throws IdentityException { String clsName = configurationContext.getStoreConfigurationMetaData() .getOptionSingleValue(OPTION_CREDENTIAL_CLASS_NAME); if (clsName != null) { try { credentialClass = Class.forName(clsName); } catch (ClassNotFoundException e) { throw new IdentityException("Error bootstrapping JpaIdentityStoreWithHashing - invalid credential entity class: " + clsName); } } super.bootstrap(configurationContext); } @Override public boolean validateCredential(org.picketlink.idm.spi.store.IdentityStoreInvocationContext ctx, org.picketlink.idm.spi.model.IdentityObject identityObject, org.picketlink.idm.spi.model.IdentityObjectCredential credential) throws IdentityException { EntityManager em = getEntityManager(ctx); if ( credentialClass == com.greenlifewater.entity.IdentityObjectCredential.class) { CriteriaBuilder builder = em.getCriteriaBuilder(); CriteriaQuery<IdentityObjectCredential> criteria = builder.createQuery(IdentityObjectCredential.class); Root<IdentityObjectCredential> root = criteria.from(IdentityObjectCredential.class); List<Predicate> predicates = new ArrayList<Predicate>(); predicates.add(builder.equal(root.get(IdentityObjectCredential_.identityObject), lookupIdentity(identityObject, em))); predicates.add(builder.equal(root.get(IdentityObjectCredential_.type), lookupCredentialTypeEntity(credential.getType().getName(), em))); criteria.where(predicates.toArray(new Predicate[0])); List<IdentityObjectCredential> results = em.createQuery(criteria).getResultList(); if (results.isEmpty()) return false; final String salt = getSalt(ctx, identityObject); final byte[] bSalt = Base64.decodeBase64(salt); final byte[] proposedDigest = getHash(credential.getValue().toString(), bSalt); for (IdentityObjectCredential result : results) { final byte[] bDigest = Base64.decodeBase64(result.getValue()); if (Arrays.equals(proposedDigest, bDigest)) { return true; } } } return super.validateCredential(ctx, identityObject, credential); } /** * Gets the salt to use to create the hash of a password. * @param ctx IdentityStoreInvocationContext the current context. * @param identityObject IdentityObject the identity to get the salt for. * @return the salt. * @throws IdentityException if the default encoding doesn't exist. */ private String getSalt(org.picketlink.idm.spi.store.IdentityStoreInvocationContext ctx, org.picketlink.idm.spi.model.IdentityObject identityObject) throws IdentityException { EntityManager em = getEntityManager(ctx); CriteriaBuilder builder = em.getCriteriaBuilder(); CriteriaQuery<IdentityObjectAttribute> criteria = builder.createQuery(IdentityObjectAttribute.class); Root<IdentityObjectAttribute> root = criteria.from(IdentityObjectAttribute.class); final List<Predicate> predicates = new ArrayList<Predicate>(); predicates.add(builder.equal(root.get(IdentityObjectAttribute_.identityObject), lookupIdentity(identityObject, em))); predicates.add(builder.equal(root.get(IdentityObjectAttribute_.name), SaltAttribute)); criteria.where(predicates.toArray(new Predicate[0])); final List<IdentityObjectAttribute> results = em.createQuery(criteria).getResultList(); return results.size() > 0 ? results.get(0).getValue() : ""; } /** * From a password and a salt returns the corresponding digest * @param password String The password to encrypt * @param salt byte[] The salt * @return byte[] The digested password * @throws IdentityException If the hash algorithm doesn't exist or * the default encoding doesn't exist */ private byte[] getHash(String password, byte[] salt) throws IdentityException { try { final MessageDigest digest = MessageDigest.getInstance(HashAlgorithm); digest.reset(); digest.update(salt); return digest.digest(password.getBytes(DefaultEncoding)); } catch (NoSuchAlgorithmException ex) { throw new IdentityException(ex); } catch (UnsupportedEncodingException ex) { throw new IdentityException(ex); } } }
So that the new identity store is used in place of the default, you need to tell the identity store config to use it. I am doing that as follows:
public class Initializer { @Inject private JpaIdentityStoreConfiguration identityStoreConfig; public void initialize(@Observes @Initialized WebApplication webapp) { identityStoreConfig.setIdentityStoreClass(JpaIdentityStoreWithHashing.class); } }
Other than than that the configuration etc from the seam security documentation stays the same.
For completeness the following script will create a user with a username/password combination of sysadmin/password123.
insert into identity_object_role_type (id, name) values (1, 'sysadmin'); insert into identity_object_type (id, name) values (1, 'USER'); insert into identity_object_type (id, name) VALUES (2, 'GROUP'); insert into identity_object (id, name, identity_object_type_id) VALUES (1, 'sysadmin', 1); insert into identity_object_attribute (attributeId, name, value, identity_object_id) VALUES (1, 'SALT', 'VGhpcy1Jcy1NeS1TYWx0 ', 1); insert into identity_object (id, name, identity_object_type_id) VALUES (2, 'system', 2); insert into identity_object_credential_type (id, name) VALUES (1, 'PASSWORD'); insert into identity_object_credential (id, value, identity_object_id, credential_type_id) VALUES (1, 'IPuynTwsY0vMJui1kamAsdwI33i+//hB3n7T+F1NhfU= ', 1, 1); insert into identity_object_relationship_type (id, name) VALUES (1, 'JBOSS_IDENTITY_ROLE'); insert into identity_object_relationship (id, name, from_identity_id, relationship_type_id, to_identity_id) VALUES (1, 'sysadmin', 2, 1, 1);