Java tutorial
/******************************************************************************* * Panifex platform * Copyright (C) 2013 Mario Krizmanic * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ******************************************************************************/ package org.panifex.security.persistence; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.persistence.EntityManager; import org.apache.shiro.authc.AccountException; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.ExpiredCredentialsException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.codec.Base64; import org.apache.shiro.crypto.hash.Sha512Hash; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.panifex.module.api.accounts.Permission; import org.panifex.module.api.security.AuthenticationService; import org.panifex.module.api.security.AuthorizationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Realm that allows authentication and authorization via persisted data. * */ public class PersistenceRealm extends AuthorizingRealm implements AuthenticationService, AuthorizationService { private final Logger log = LoggerFactory.getLogger(getClass()); /** * Hash algorithm name to use when performing hashes for credentials matching. */ public static final String HASH_ALGORITHM = Sha512Hash.ALGORITHM_NAME; /** * The number of times a submitted {@code AuthenticationToken}'s credentials will be hashed before comparing * to the credentials stored in the system. */ public static final int HASH_ITERATIONS = 1024; private AccountRepositoryImpl accountRepository; /** * {@link org.apache.shiro.crypto.RandomNumberGenerator RandomNumberGenerator} is used * for creating random numbers for generating password salts. */ //private RandomNumberGenerator randomGenerator = new SecureRandomNumberGenerator(); private EntityManager entityManager; /** * Constructor adds EhCacheManager. */ public PersistenceRealm() { super(new EhCacheManager()); // set hashed credentials matcher HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(HASH_ALGORITHM); credentialsMatcher.setHashIterations(HASH_ITERATIONS); credentialsMatcher.setStoredCredentialsHexEncoded(false); setCredentialsMatcher(credentialsMatcher); } public void setAccountRepository(AccountRepositoryImpl accountRepository) { this.accountRepository = accountRepository; } public void setEntityManager(EntityManager entityManager) { this.entityManager = entityManager; } /** * {@inheritDoc} */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); log.debug("Get authentication info for username: {}", username); // Null username is invalid if (username == null) { throw new AccountException("Null usernames are not allowed."); } // get account from repository AccountEntity account = getAccountByUsername(username); // check if user's account is expired assertCredentialsNotExpired(account); // create authentication info SimpleAuthenticationInfo info = createAuthenticationInfo(account); log.debug("Authentication info resolved: username={}", username); return info; } /** * {@inheritDoc} */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // null usernames are invalid if (principals == null) { throw new AuthorizationException("PrincipalCollection method argument cannot be null."); } String username = (String) getAvailablePrincipal(principals); log.debug("Get authorization info for username: {}", username); // get account from repository AccountEntity account = accountRepository.getAccountByUsername(entityManager, username); // create empty sets Set<String> roleNames = new HashSet<>(); Set<String> permissionWildcardExpressions = new HashSet<>(); if (account != null) { List<? extends Permission> permissions = accountRepository.getPermissionsForAccount(entityManager, account); for (Permission permission : permissions) { String wildcardExpression = permission.getWildcardExpression(); permissionWildcardExpressions.add(wildcardExpression); } } SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames); info.setStringPermissions(permissionWildcardExpressions); return info; } /** * {@inheritDoc} */ /* @Override public void updateAccountExpiredPassword(String username, String oldPassword, String newPassword) throws AccountNotExpiredException, IncorrectCredentialsException, UnknownAccountException { // get account from repository AccountEntity account = getAccountByUsername(username); // create authentication info SimpleAuthenticationInfo info = createAuthenticationInfo(account); // create the authentication token UsernamePasswordToken token = new UsernamePasswordToken(username, oldPassword); // assert that the UsernamePasswordToken's credentials match the stored account AuthenticationInfo's credentials assertCredentialsMatch(token, info); // assert that the account's credentials is expired assertCredentialsExpired(account); // the account is expired, so change the current password updateAccountPassword(account, newPassword); }*/ /** * Updates the {@link AccountEntity} password. * * @param account the persisted {@link AccountEntity} * @param newPassword the new plain text password */ /* private void updateAccountPassword(AccountEntity account, String newPassword) { String passwordSalt = getRandomPasswordSalt(); String password = getHashedPasswordBase64(newPassword, passwordSalt); account.setPassword(password); account.setPasswordSalt(passwordSalt); account.setIsCredentialsExpired(false); accountRepository.updateAccount(entityManager, account); }*/ /** * Returns the {@link AccountEntity} with the same username. * * @param username a account's username * @return the {@link AccountEntity} with the same username * @throws UnknownAccountException if the account has not found, or if its password or its password's salt is null */ private AccountEntity getAccountByUsername(String username) throws UnknownAccountException { // get account from repository AccountEntity account = accountRepository.getAccountByUsername(entityManager, username); if (account != null) { // get password String password = account.getPassword(); // get password salt String passwordSalt = account.getPasswordSalt(); // check user's username and password if (password == null || passwordSalt == null) { StringBuilder sb = new StringBuilder(); sb.append("No account found for user "); sb.append(username); throw new UnknownAccountException(sb.toString()); } return account; } else { // the account has not found. Throw UnknownAccountException. throw new UnknownAccountException("No account found for user [" + username + "]"); } } /** * Constructs the {@link org.apache.shiro.authc.SimpleAuthenticationInfo SimpleAuthenticationInfo} * based on the persisted {@link AccountEntity}. * <p> * The {@link org.apache.shiro.authc.SimpleAuthenticationInfo SimpleAuthenticationInfo} is used in subsequent * authentication steps. * * @param account the persisted {@link AccountEntity} * @return the constructed {@link org.apache.shiro.authc.SimpleAuthenticationInfo SimpleAuthenticationInfo} based on the persisted {@link AccountEntity} */ private SimpleAuthenticationInfo createAuthenticationInfo(AccountEntity account) { // decode the password salt byte[] decodedPasswordSalt = Base64.decode(account.getPasswordSalt()); ByteSource passwordSaltSource = ByteSource.Util.bytes(decodedPasswordSalt); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(account.getUsername(), account.getPassword(), passwordSaltSource, getName()); return info; } /** * Asserts that the persisted account is not expired, and if not, throws an ExpiredCredentialsException. * * @param account the persisted {@link AccountEntity} * @throws ExpiredCredentialsException it the account is expired */ private void assertCredentialsNotExpired(AccountEntity account) throws ExpiredCredentialsException { if (account.getIsCredentialsExpired()) { // the account is expired. Throw ExpiredCredentialsException StringBuilder sb = new StringBuilder(); sb.append("Credentials is expired for user "); sb.append(account.getUsername()); throw new ExpiredCredentialsException(sb.toString()); } } /** * Asserts that the persisted account is expired, and if not, throws an AccountNotExpiredException. * * @param account the persisted {@link AccountEntity} * @throws AccountNotExpiredException it the account is not expired */ /* private void assertCredentialsExpired(AccountEntity account) throws AccountNotExpiredException { if (!account.getIsCredentialsExpired()) { throw new AccountNotExpiredException(); } }*/ /** * Returns a random generated <a href="http://en.wikipedia.org/wiki/Base64">Base 64</a>-formatted * byte array of fixed length filled. * <p> * It is used for salting passwords. * * @return a random generated <a href="http://en.wikipedia.org/wiki/Base64">Base 64</a>-formatted byte array */ /* private String getRandomPasswordSalt() { return randomGenerator.nextBytes().toBase64(); }*/ /** * Returns a hashed and salted plain text password. * * @param plainTextPassword the password to be hashes * @param passwordSalt the salt to use for the hash * @return a hashed and salted plain text password */ /* private String getHashedPasswordBase64(String plainTextPassword, String passwordSalt) { // decode password salt byte[] salt = Base64.decode(passwordSalt); // hash salted password String hashedPasswordBase64 = new SimpleHash( PersistenceRealm.HASH_ALGORITHM, plainTextPassword, salt, PersistenceRealm.HASH_ITERATIONS).toBase64(); return hashedPasswordBase64; }*/ }