All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.yadaframework.security.components.YadaUserDetailsService Maven / Gradle / Ivy

There is a newer version: 0.7.7.R4
Show newest version
package net.yadaframework.security.components;

import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import net.yadaframework.core.YadaConfiguration;
import net.yadaframework.security.TooManyFailedAttemptsException;
import net.yadaframework.security.exceptions.InternalAuthenticationException;
import net.yadaframework.security.persistence.entity.YadaUserCredentials;
import net.yadaframework.security.persistence.repository.YadaUserCredentialsDao;

@Component
@DependsOn("passwordEncoder")
public class YadaUserDetailsService implements UserDetailsService {
	private transient final Logger log = LoggerFactory.getLogger(getClass());
	private transient final Logger logSec = LoggerFactory.getLogger("security");
	@Autowired PasswordEncoder encoder;
	@Autowired YadaUserCredentialsDao yadaUserCredentialsDao;
	@Autowired YadaConfiguration yadaConfiguration;
	
	private final SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();
	private final SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository(); 

	/**
	 * Change the roles of the currently authenticated user, but not on the database
	 * @param authentication the current Authentication object
	 * @param roleIds the database ids of the needed roles
	 */
	public void changeCurrentRoles(Authentication authentication, int[] roleIds) {
	    Set authorities = new HashSet<>();
	    for (int roleId : roleIds) {
	    	authorities.add(new SimpleGrantedAuthority(yadaConfiguration.getRoleSpringName(roleId)));
		}
	    Authentication newAuth = new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), authentication.getCredentials(), authorities);
	    SecurityContextHolder.getContext().setAuthentication(newAuth);
	}

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, InternalAuthenticationException, TooManyFailedAttemptsException {
		username = username.trim().toLowerCase();
		UserDetails result = null;
		YadaUserCredentials yadaUserCredentials = null;
		boolean lockout=false;
		try {
			yadaUserCredentials = yadaUserCredentialsDao.findFirstByUsername(username);
			if (yadaUserCredentials!=null) {
				// BFA prevention: dopo N tentativi sbagliati consecutivi, blocco l'accesso per tot minuti
				// Il modo in cui è fatto è ridicolo perchè resetta il conto solo se sbaglio dopo che è passato il timeout...!
				int maxFailed = yadaConfiguration.getMaxPasswordFailedAttempts();
				int lockMillis = yadaConfiguration.getPasswordFailedAttemptsLockoutMinutes()*60000;
				Date lastFailedTimestamp = yadaUserCredentials.getLastFailedAttempt();
				if (yadaUserCredentials.getFailedAttempts()>=maxFailed && lastFailedTimestamp!=null) {
					if (System.currentTimeMillis()-lastFailedTimestamp.getTime()1) {
//			log.error("Internal Error: more than one UserCredentials with username='{}' - rejecting login", username);
//			throw new InternalAuthenticationException("Too many users with same username");
//		}
		if (yadaUserCredentials==null) {
			log.debug("Username '{}' not found", username);
			throw new UsernameNotFoundException("Username " + username + " not found");
		}
		if (lockout) {
			logSec.debug("Username '{}' too many failed attempts: locked out", username);
			throw new TooManyFailedAttemptsException();
		}
		return result;
	}

	private UserDetails createUserDetails(YadaUserCredentials userCredentials) {
		Set authorities = new HashSet();
		for (Integer roleId : userCredentials.getRoles()) {
			authorities.add(new SimpleGrantedAuthority(yadaConfiguration.getRoleSpringName(roleId)));
		}
		UserDetails userDetails = new org.springframework.security.core.userdetails.User(userCredentials.getUsername().toLowerCase(), userCredentials.getPassword(), userCredentials.isEnabled(), true, !userCredentials.isChangePassword(), true, authorities);
		return userDetails;
	}

	/**
	 * Manual authentication for Spring Security 6 without setting the login timestamp.
	 * @param userCredentials
	 * @param request
	 * @param response
	 */
	public Authentication authenticateAs(YadaUserCredentials userCredentials, HttpServletRequest request, HttpServletResponse response) {
		if (request==null || response==null) {
			log.warn("Using deprecated authentication method");
			return authenticateAs(userCredentials, false);
		}
		return authenticateAs(userCredentials, false, request, response);
	}
	
	/**
	 * Manual authentication for Spring Security 6. Also sets the login timestamp and clears the failed attempts counter.
	 * @param userCredentials
	 * @param request
	 * @param response
	 * @param setTimestamp true to set the lastSuccessfulLogin timestamp
	 */
	public Authentication authenticateAs(YadaUserCredentials userCredentials, boolean setTimestamp, HttpServletRequest request, HttpServletResponse response) {
		if (request==null || response==null) {
			log.warn("Using deprecated authentication method");
			return authenticateAs(userCredentials, setTimestamp);
		}
		UserDetails userDetails = createUserDetails(userCredentials);
		Authentication auth = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
		// Docs: https://docs.spring.io/spring-security/reference/servlet/authentication/session-management.html#store-authentication-manually
		SecurityContext context = securityContextHolderStrategy.createEmptyContext();
		context.setAuthentication(auth); 
		securityContextHolderStrategy.setContext(context);
		securityContextRepository.saveContext(context, request, response); 		
        //
		if (setTimestamp) {
			yadaUserCredentialsDao.updateLoginTimestamp(userCredentials.getUsername());
			yadaUserCredentialsDao.resetFailedAttempts(userCredentials.getUsername());
		}
		return auth;
	}

	/**
	 * Authenticate the user without setting the lastSuccessfulLogin timestamp
	 * @param userCredentials
	 * @deprecated because for Spring 5
	 */
	@Deprecated
	public Authentication authenticateAs(YadaUserCredentials userCredentials) {
		return authenticateAs(userCredentials, true);
	}

	/**
	 * Authenticate the user
	 * @param userCredentials
	 * @param setTimestamp true to set the lastSuccessfulLogin timestamp
	 * @deprecated because for Spring 5
	 */
	@Deprecated
	public Authentication authenticateAs(YadaUserCredentials userCredentials, boolean setTimestamp) {
		UserDetails userDetails = createUserDetails(userCredentials);
		Authentication auth = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
		SecurityContext context = SecurityContextHolder.getContext();
		context.setAuthentication(auth);
		// Fix for authentication being ignored in Spring Security 6.2.0 because of requireExplicitAuthenticationStrategy(true) by default
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes instanceof ServletRequestAttributes) {
            HttpServletRequest req = ((ServletRequestAttributes) requestAttributes).getRequest();
            HttpSession session = req.getSession(true);
            session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context);
        }
        //
		if (setTimestamp) {
			yadaUserCredentialsDao.updateLoginTimestamp(userCredentials.getUsername());
			yadaUserCredentialsDao.resetFailedAttempts(userCredentials.getUsername());
		}
		return auth;
	}

	/**
	 * Change the old password with the new password, but only if the old password is valid.
	 * If the change is successful, no exception is thrown.
	 * @param username user that needs to change password
	 * @param passwordTyped current password
	 * @param newPassword new password
	 * @throws UsernameNotFoundException if the username does not exist
	 * @throws BadCredentialsException if the supplied password is not valid for the user
	 * @throws InternalAuthenticationException in any other error occurs
	 */
	public void changePasswordIfAuthenticated(String username, String passwordTyped, String newPassword) throws UsernameNotFoundException, InternalAuthenticationException, BadCredentialsException {
		// Prima controllo che username e password siano validi, poi setto la nuova password
		try {
			username = username.toLowerCase();
			YadaUserCredentials userCredentials = yadaUserCredentialsDao.findFirstByUsername(username);
			if (userCredentials==null) {
				throw new UsernameNotFoundException("Username " + username + " not found");
			}
			boolean pwdMatch=passwordMatch(passwordTyped, userCredentials);
			if (pwdMatch) {
				userCredentials = yadaUserCredentialsDao.changePassword(userCredentials, newPassword);
			} else {
				log.debug("Invalid password: {}", passwordTyped);
				throw new BadCredentialsException("Password invalid");
			}
		} catch (UsernameNotFoundException e) {
			throw e;
		} catch (BadCredentialsException e) {
			throw e;
		} catch (Exception e) {
			throw new InternalAuthenticationException("Internal Error", e);
		}
	}

	/**
	 * Ritorna true se la password passata è valida per l'utente
	 * @param passwordTyped
	 * @param yadaUserCredentials
	 * @return
	 */
	public boolean passwordMatch(String passwordTyped, YadaUserCredentials yadaUserCredentials) {
		boolean pwdMatch=false;
		if (encoder!=null && yadaUserCredentials!=null) {
			pwdMatch = encoder.matches(passwordTyped, yadaUserCredentials.getPassword());
		} else if (yadaUserCredentials!=null) {
			pwdMatch = yadaUserCredentials.getPassword().equals(passwordTyped);
		}
		return pwdMatch;
	}

	public boolean validatePasswordSyntax(String password, int minLen, int maxLen) {
		return !StringUtils.isEmpty(password) && password.length()>=minLen && password.length()<=maxLen;
	}

	public boolean validatePasswordSyntax(String password) {
		return validatePasswordSyntax(password, yadaConfiguration.getMinPasswordLength(), yadaConfiguration.getMaxPasswordLength());
	}



}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy