CCM NG: The OneTimeAuthToken is now saved as hash in the database.
git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@3985 8810af33-2d31-482b-a856-94f89814c4dfpull/2/head
parent
f4e6b1da96
commit
b458a61aca
|
|
@ -18,6 +18,8 @@
|
|||
*/
|
||||
package org.libreccm.security;
|
||||
|
||||
import com.arsdigita.kernel.security.SecurityConfig;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Date;
|
||||
|
|
@ -32,6 +34,15 @@ import javax.persistence.TypedQuery;
|
|||
import javax.transaction.Transactional;
|
||||
|
||||
import org.apache.commons.lang.RandomStringUtils;
|
||||
import org.apache.shiro.authc.credential.PasswordMatcher;
|
||||
import org.apache.shiro.authc.credential.PasswordService;
|
||||
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
|
||||
import org.apache.shiro.crypto.hash.SimpleHash;
|
||||
import org.apache.shiro.crypto.hash.format.DefaultHashFormatFactory;
|
||||
import org.apache.shiro.crypto.hash.format.HashFormat;
|
||||
import org.apache.shiro.crypto.hash.format.HashFormatFactory;
|
||||
import org.apache.shiro.crypto.hash.format.Shiro1CryptFormat;
|
||||
import org.apache.shiro.util.ByteSource;
|
||||
|
||||
/**
|
||||
* This class manages the generation and delation of {@link OneTimeAuthToken}s.
|
||||
|
|
@ -45,19 +56,26 @@ public class OneTimeAuthManager {
|
|||
private EntityManager entityManager;
|
||||
|
||||
@Inject
|
||||
private ConfigurationManager configurationManager;
|
||||
private ConfigurationManager confManager;
|
||||
|
||||
/**
|
||||
* Creates a new one time auth token which for the provided user and the
|
||||
* provided purpose. The length of the token and how long the token is valid
|
||||
* are configured by the {@link OneTimeAuthConfig}.
|
||||
* Creates a new one time authentication token which for the provided user
|
||||
* and the provided purpose. The length of the token and how long the token
|
||||
* is valid are configured by the {@link OneTimeAuthConfig}.
|
||||
*
|
||||
* This method generates the token <em>and</em> saves it in the database.
|
||||
*
|
||||
* @param user The user for which the one time auth token is generated.
|
||||
* Please note: The returned token contains the not hashed
|
||||
* authentication key/token string. The string is saved as hash in the
|
||||
* database (using the same hash algorithm as for the passwords). This
|
||||
* allows the caller to put the token string into an email (or an other
|
||||
* message) and send it to the user.
|
||||
*
|
||||
* @param user The user for which the one time authentication token is
|
||||
* generated.
|
||||
* @param purpose The purpose for which the token is generated.
|
||||
*
|
||||
* @return The one time auth token.
|
||||
* @return The one time authentication token with the not hashed token.
|
||||
*/
|
||||
@Transactional(Transactional.TxType.REQUIRED)
|
||||
public OneTimeAuthToken createForUser(
|
||||
|
|
@ -68,26 +86,52 @@ public class OneTimeAuthManager {
|
|||
+ "time auth token.");
|
||||
}
|
||||
|
||||
final OneTimeAuthConfig config = configurationManager.findConfiguration(
|
||||
final OneTimeAuthConfig config = confManager.findConfiguration(
|
||||
OneTimeAuthConfig.class);
|
||||
|
||||
final OneTimeAuthToken token = new OneTimeAuthToken();
|
||||
token.setUser(user);
|
||||
token.setPurpose(purpose);
|
||||
// Token with cleartext token string
|
||||
final OneTimeAuthToken tmpToken = new OneTimeAuthToken();
|
||||
tmpToken.setUser(user);
|
||||
tmpToken.setPurpose(purpose);
|
||||
|
||||
final String tokenStr = RandomStringUtils.randomAlphanumeric(config.
|
||||
getTokenLength());
|
||||
token.setToken(tokenStr);
|
||||
tmpToken.setToken(tokenStr);
|
||||
|
||||
final LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
|
||||
final LocalDateTime valid = now.plusSeconds(config.getTokenValid());
|
||||
final Date validUntil = Date.from(valid.toInstant(ZoneOffset.UTC));
|
||||
|
||||
tmpToken.setValidUntil(validUntil);
|
||||
|
||||
final OneTimeAuthToken token = new OneTimeAuthToken();
|
||||
token.setUser(user);
|
||||
token.setPurpose(purpose);
|
||||
token.setValidUntil(validUntil);
|
||||
|
||||
//Get configuration values for hashing from SecurityConfig
|
||||
final SecurityConfig securityConfig = confManager.findConfiguration(
|
||||
SecurityConfig.class);
|
||||
final String hashAlgo = securityConfig.getHashAlgorithm();
|
||||
final int iterations = securityConfig.getHashIterations();
|
||||
|
||||
//Create the hash using Shiro's SimpleHash class
|
||||
final SimpleHash hash = new SimpleHash(hashAlgo,
|
||||
tokenStr.toCharArray(),
|
||||
generateSalt(),
|
||||
iterations);
|
||||
//We want to use the Shiro1 format for storing the password. This
|
||||
//format includes the algorithm used, the salt, the number of
|
||||
//iterations used and the hashed password in special formatted string.
|
||||
final HashFormatFactory hashFormatFactory
|
||||
= new DefaultHashFormatFactory();
|
||||
final HashFormat hashFormat = hashFormatFactory.getInstance(
|
||||
Shiro1CryptFormat.class.getName());
|
||||
token.setToken(hashFormat.format(hash));
|
||||
|
||||
entityManager.persist(token);
|
||||
|
||||
return token;
|
||||
return tmpToken;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -191,7 +235,17 @@ public class OneTimeAuthManager {
|
|||
return false;
|
||||
}
|
||||
|
||||
return token.getToken().equals(submittedToken);
|
||||
//The token is stored as hash (like the passwords. We
|
||||
//the facilities provided by Shiro to verify the submitted token
|
||||
//Create a new Shiro PasswordMatcher instance
|
||||
final PasswordMatcher matcher = new PasswordMatcher();
|
||||
//Get the PasswordService instance from the matcher (the PasswordService
|
||||
//class provides the methods we need here).
|
||||
final PasswordService service = matcher.getPasswordService();
|
||||
|
||||
return service.passwordsMatch(submittedToken, token.getToken());
|
||||
|
||||
// return token.getToken().equals(submittedToken);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -212,4 +266,26 @@ public class OneTimeAuthManager {
|
|||
entityManager.remove(delete);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for generating a random salt. The length of the generated
|
||||
* salt is configured in the {@link LegacySecurityConfig}.
|
||||
*
|
||||
* @return A new random salt.
|
||||
*/
|
||||
private ByteSource generateSalt() {
|
||||
final SecurityConfig securityConfig = confManager.findConfiguration(
|
||||
SecurityConfig.class);
|
||||
final int generatedSaltSize = securityConfig.getSaltLength();
|
||||
|
||||
if (generatedSaltSize % 8 != 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Salt length is not a multipe of 8");
|
||||
}
|
||||
|
||||
final SecureRandomNumberGenerator generator
|
||||
= new SecureRandomNumberGenerator();
|
||||
final int byteSize = generatedSaltSize / 8; //generatedSaltSize is in *bits* - convert to byte size:
|
||||
return generator.nextBytes(byteSize);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,9 +134,10 @@ public class UserManager {
|
|||
* @return The hashed password.b
|
||||
*/
|
||||
private String hashPassword(final String password) {
|
||||
//Get the values from the LegacySecurityConfig
|
||||
final String hashAlgo = SecurityConfig.getConfig().getHashAlgorithm();
|
||||
final int iterations = SecurityConfig.getConfig().getHashIterations();
|
||||
//Get the values from the SecurityConfig
|
||||
final SecurityConfig securityConfig = SecurityConfig.getConfig();
|
||||
final String hashAlgo = securityConfig.getHashAlgorithm();
|
||||
final int iterations = securityConfig.getHashIterations();
|
||||
|
||||
//Create the hash using Shiro's SimpleHash class
|
||||
final SimpleHash hash = new SimpleHash(hashAlgo,
|
||||
|
|
@ -145,7 +146,7 @@ public class UserManager {
|
|||
iterations);
|
||||
|
||||
//We want to use the Shiro1 format for storing the password. This
|
||||
//format includes the algorithm used, the salt and the number of
|
||||
//format includes the algorithm used, the salt, the number of
|
||||
//iterations used and the hashed password in special formatted string.
|
||||
final HashFormatFactory hashFormatFactory
|
||||
= new DefaultHashFormatFactory();
|
||||
|
|
|
|||
|
|
@ -23,10 +23,8 @@ import java.time.LocalDateTime;
|
|||
import java.time.ZoneOffset;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import org.jboss.arquillian.container.test.api.Deployment;
|
||||
import org.jboss.arquillian.container.test.api.ShouldThrowException;
|
||||
|
|
@ -126,6 +124,7 @@ public class OneTimeAuthManagerTest {
|
|||
getPackage())
|
||||
.addPackage(org.libreccm.jpa.utils.MimeTypeConverter.class.
|
||||
getPackage())
|
||||
.addClass(com.arsdigita.kernel.security.SecurityConfig.class)
|
||||
.addPackage(org.libreccm.testutils.EqualsVerifier.class.
|
||||
getPackage())
|
||||
.addPackage(org.libreccm.tests.categories.IntegrationTest.class.
|
||||
|
|
|
|||
Loading…
Reference in New Issue