[seam-commits] Seam SVN: r10417 - in trunk/src/main/org/jboss/seam: security/management and 1 other directory.

seam-commits at lists.jboss.org seam-commits at lists.jboss.org
Tue Apr 14 19:56:23 EDT 2009


Author: shane.bryzak at jboss.com
Date: 2009-04-14 19:56:22 -0400 (Tue, 14 Apr 2009)
New Revision: 10417

Added:
   trunk/src/main/org/jboss/seam/annotations/security/management/PasswordSalt.java
Modified:
   trunk/src/main/org/jboss/seam/annotations/security/management/UserPassword.java
   trunk/src/main/org/jboss/seam/security/management/JpaIdentityStore.java
   trunk/src/main/org/jboss/seam/security/management/PasswordHash.java
Log:
JBSEAM-3762

Added: trunk/src/main/org/jboss/seam/annotations/security/management/PasswordSalt.java
===================================================================
--- trunk/src/main/org/jboss/seam/annotations/security/management/PasswordSalt.java	                        (rev 0)
+++ trunk/src/main/org/jboss/seam/annotations/security/management/PasswordSalt.java	2009-04-14 23:56:22 UTC (rev 10417)
@@ -0,0 +1,24 @@
+package org.jboss.seam.annotations.security.management;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * The salt value used to produce the password hash 
+ * 
+ * @author Shane Bryzak
+ */
+ at Target({METHOD,FIELD})
+ at Documented
+ at Retention(RUNTIME)
+ at Inherited
+public @interface PasswordSalt
+{
+      
+}

Modified: trunk/src/main/org/jboss/seam/annotations/security/management/UserPassword.java
===================================================================
--- trunk/src/main/org/jboss/seam/annotations/security/management/UserPassword.java	2009-04-14 21:36:54 UTC (rev 10416)
+++ trunk/src/main/org/jboss/seam/annotations/security/management/UserPassword.java	2009-04-14 23:56:22 UTC (rev 10417)
@@ -20,5 +20,13 @@
 @Inherited
 public @interface UserPassword
 {   
+   /**
+    * The hash algorithm, only used if there is no @PasswordSalt property specified
+    */
    String hash() default "";
+   
+   /**
+    * Number of iterations for generating the password hash
+    */
+   int iterations() default 1000;
 }

Modified: trunk/src/main/org/jboss/seam/security/management/JpaIdentityStore.java
===================================================================
--- trunk/src/main/org/jboss/seam/security/management/JpaIdentityStore.java	2009-04-14 21:36:54 UTC (rev 10416)
+++ trunk/src/main/org/jboss/seam/security/management/JpaIdentityStore.java	2009-04-14 23:56:22 UTC (rev 10417)
@@ -6,7 +6,9 @@
 import java.io.Serializable;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
+import java.security.GeneralSecurityException;
 import java.security.Principal;
+import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
@@ -22,6 +24,7 @@
 import org.jboss.seam.annotations.Observer;
 import org.jboss.seam.annotations.Scope;
 import org.jboss.seam.annotations.intercept.BypassInterceptors;
+import org.jboss.seam.annotations.security.management.PasswordSalt;
 import org.jboss.seam.annotations.security.management.RoleConditional;
 import org.jboss.seam.annotations.security.management.RoleGroups;
 import org.jboss.seam.annotations.security.management.RoleName;
@@ -41,6 +44,7 @@
 import org.jboss.seam.security.Role;
 import org.jboss.seam.security.SimplePrincipal;
 import org.jboss.seam.util.AnnotatedBeanProperty;
+import org.jboss.seam.util.Base64;
 import org.jboss.seam.util.TypedBeanProperty;
 
 /**
@@ -76,6 +80,7 @@
    
    private AnnotatedBeanProperty<UserPrincipal> userPrincipalProperty;
    private AnnotatedBeanProperty<UserPassword> userPasswordProperty;
+   private AnnotatedBeanProperty<PasswordSalt> passwordSaltProperty;
    private AnnotatedBeanProperty<UserRoles> userRolesProperty;
    private AnnotatedBeanProperty<UserEnabled> userEnabledProperty;
    private AnnotatedBeanProperty<UserFirstName> userFirstNameProperty;
@@ -126,6 +131,7 @@
    {
       userPrincipalProperty = new AnnotatedBeanProperty(userClass, UserPrincipal.class);
       userPasswordProperty = new AnnotatedBeanProperty(userClass, UserPassword.class);
+      passwordSaltProperty = new AnnotatedBeanProperty(userClass, PasswordSalt.class);
       userRolesProperty = new AnnotatedBeanProperty(userClass, UserRoles.class);
       userEnabledProperty = new AnnotatedBeanProperty(userClass, UserEnabled.class);
       userFirstNameProperty = new AnnotatedBeanProperty(userClass, UserFirstName.class);
@@ -217,8 +223,8 @@
             if (userEnabledProperty.isSet()) userEnabledProperty.setValue(user, false);
          }
          else
-         {            
-            userPasswordProperty.setValue(user, generatePasswordHash(password, getUserAccountSalt(user)));
+         {  
+            setUserPassword(user, password);
             if (userEnabledProperty.isSet()) userEnabledProperty.setValue(user, true);
          }
          
@@ -243,12 +249,38 @@
       }      
    }
    
+   protected void setUserPassword(Object user, String password)
+   {
+      if (passwordSaltProperty.isSet())
+      {
+         byte[] salt = generateUserSalt(user);               
+         passwordSaltProperty.setValue(user, Base64.encodeBytes(salt));
+         userPasswordProperty.setValue(user, generatePasswordHash(password, salt));
+      }
+      else
+      {
+         userPasswordProperty.setValue(user, generatePasswordHash(password, getUserAccountSalt(user)));
+      }
+   }
+   
+   /**
+    * @deprecated Use JpaIdentityStore.generateRandomSalt(Object) instead
+    */
+   @Deprecated
    protected String getUserAccountSalt(Object user)
-   {
+   {      
       // By default, we'll use the user's username as the password salt
       return userPrincipalProperty.getValue(user).toString();
    }
    
+   /**
+    * Generates a 64 bit random salt value
+    */
+   public byte[] generateUserSalt(Object user)
+   {
+      return PasswordHash.instance().generateRandomSalt();
+   }
+   
    public boolean createUser(String username, String password)
    {
       return createUser(username, password, null, null);
@@ -361,8 +393,6 @@
          }
       }
       
-      mergeEntity(user);
-      
       return true;
    }   
    
@@ -399,8 +429,7 @@
             }
          }
       }
-      
-      if (success) mergeEntity(user);
+
       return success;
    }
    
@@ -420,7 +449,6 @@
          throw new NoSuchRoleException("Could not grant role, group '" + group + "' does not exist");
       }
       
-
       Collection roleGroups = (Collection) roleGroupsProperty.getValue(targetRole); 
       if (roleGroups == null)
       {
@@ -452,7 +480,6 @@
       }
 
       ((Collection) roleGroupsProperty.getValue(targetRole)).add(targetGroup);
-      mergeEntity(targetRole);
       
       return true;
    }
@@ -474,8 +501,6 @@
       }      
        
       boolean success = ((Collection) roleGroupsProperty.getValue(roleToRemove)).remove(targetGroup);
-      
-      if (success) mergeEntity(roleToRemove);
       return success;
    }      
    
@@ -565,8 +590,7 @@
          return false;
       }
       
-      userEnabledProperty.setValue(user, true);
-      mergeEntity(user);      
+      userEnabledProperty.setValue(user, true);   
       return true;
    }
    
@@ -590,9 +614,7 @@
          return false;
       }          
       
-      userEnabledProperty.setValue(user, false);
-      mergeEntity(user);
-      
+      userEnabledProperty.setValue(user, false);     
       return true;
    }
    
@@ -604,8 +626,8 @@
          throw new NoSuchUserException("Could not change password, user '" + username + "' does not exist");
       }
       
-      userPasswordProperty.setValue(user, generatePasswordHash(password, getUserAccountSalt(user)));
-      mergeEntity(user);
+      setUserPassword(user, password);
+      
       return true;
    }
    
@@ -723,8 +745,33 @@
       }
    }
    
+   public String generatePasswordHash(String password, byte[] salt)
+   {
+      if (passwordSaltProperty.isSet())
+      {
+         try
+         {
+            return PasswordHash.instance().createPasswordKey(password.toCharArray(), salt, 
+                  userPasswordProperty.getAnnotation().iterations());
+         }
+         catch (GeneralSecurityException ex)
+         {
+            throw new IdentityManagementException("Exception generating password hash", ex);
+         }
+      }
+      else
+      {
+         return generatePasswordHash(password, new String(salt));
+      }
+   }
+   
+   /**
+    * 
+    * @deprecated Use JpaIdentityStore.generatePasswordHash(String, byte[]) instead
+    */
+   @Deprecated
    protected String generatePasswordHash(String password, String salt)
-   {
+   {    
       String algorithm = userPasswordProperty.getAnnotation().hash();
       
       if (algorithm == null || "".equals(algorithm))
@@ -752,7 +799,7 @@
          {
             return PasswordHash.instance().generateSaltedHash(password, salt, algorithm);
          }
-      }      
+      }
    }
    
    public boolean authenticate(String username, String password)
@@ -763,7 +810,25 @@
          return false;
       }
       
-      String passwordHash = generatePasswordHash(password, getUserAccountSalt(user)); 
+      String passwordHash = null;
+      
+      if (passwordSaltProperty.isSet())
+      {
+         String encodedSalt = (String) passwordSaltProperty.getValue(user);
+         if (encodedSalt == null)
+         {
+            throw new IdentityManagementException("A @PasswordSalt property was found on entity " + user + 
+                  ", but it contains no value");
+         }
+         
+         passwordHash = generatePasswordHash(password, Base64.decode(encodedSalt));
+      }
+      else
+      {
+         passwordHash = generatePasswordHash(password, getUserAccountSalt(user));   
+      }
+      
+       
       boolean success = passwordHash.equals(userPasswordProperty.getValue(user));
             
       if (success && Events.exists())

Modified: trunk/src/main/org/jboss/seam/security/management/PasswordHash.java
===================================================================
--- trunk/src/main/org/jboss/seam/security/management/PasswordHash.java	2009-04-14 21:36:54 UTC (rev 10416)
+++ trunk/src/main/org/jboss/seam/security/management/PasswordHash.java	2009-04-14 23:56:22 UTC (rev 10417)
@@ -3,8 +3,15 @@
 import static org.jboss.seam.ScopeType.STATELESS;
 import static org.jboss.seam.annotations.Install.BUILT_IN;
 
+import java.security.GeneralSecurityException;
 import java.security.MessageDigest;
+import java.security.SecureRandom;
 
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
 import org.jboss.seam.Component;
 import org.jboss.seam.ScopeType;
 import org.jboss.seam.annotations.Install;
@@ -26,24 +33,33 @@
 {
    public static final String ALGORITHM_MD5 = "MD5";
    public static final String ALGORITHM_SHA = "SHA";
-      
+        
    private static final String DEFAULT_ALGORITHM = ALGORITHM_MD5;
    
+   private int saltLength = 8; // default password salt length, in bytes
+      
+   @Deprecated
    public String generateHash(String password)
    {
       return generateHash(password, DEFAULT_ALGORITHM);
    }
    
+   @Deprecated
    public String generateHash(String password, String algorithm)
    {
       return generateSaltedHash(password, null, algorithm);
    }
    
+   @Deprecated
    public String generateSaltedHash(String password, String saltPhrase)
    {
       return generateSaltedHash(password, saltPhrase, DEFAULT_ALGORITHM);
    }
    
+   /**
+    * @deprecated Use PasswordHash.createPasswordKey() instead
+    */
+   @Deprecated
    public String generateSaltedHash(String password, String saltPhrase, String algorithm)
    {
       try {        
@@ -71,8 +87,39 @@
      } 
    }
    
+   public byte[] generateRandomSalt()
+   {      
+      byte[] salt = new byte[saltLength];
+      new SecureRandom().nextBytes(salt);
+      return salt;      
+   }
+   
+   /**
+    * 
+    */
+   public String createPasswordKey(char[] password, byte[] salt, int iterations) 
+      throws GeneralSecurityException 
+   {
+      PBEKeySpec passwordKeySpec = new PBEKeySpec(password, salt, iterations, 256);
+      SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+      SecretKey passwordKey = secretKeyFactory.generateSecret(passwordKeySpec);
+      passwordKeySpec.clearPassword();
+      byte[] encoded = passwordKey.getEncoded();
+      return Base64.encodeBytes(new SecretKeySpec(encoded, "AES").getEncoded());
+   }
+   
    public static PasswordHash instance()
    {
       return (PasswordHash) Component.getInstance(PasswordHash.class, ScopeType.STATELESS);
    }
+   
+   public int getSaltLength()
+   {
+      return saltLength;
+   }
+   
+   public void setSaltLength(int saltLength)
+   {
+      this.saltLength = saltLength;
+   }
 }




More information about the seam-commits mailing list