I have this CustomAuthenticator for user with Errai Security:
public CustomAuthenticator extends BaseAuthenticator {
#Override
public void authenticate() {
String userId = loginCredentials.getUserId();
String password = loginCredentials.getPassword();
User user = userDAO.fetchUserByName(userId);
if (!BCrypt.checkpw(password, user.getPasswordHash())) {
setStatus(AuthenticationStatus.FAILURE);
} else {
// Add to IDM
IdentityQuery<UserImpl> query
= partitionManager.createIdentityManager().createIdentityQuery(UserImpl.class);
query.setParameter(UserImpl.LOGIN_NAME, user.getUsername());
List<UserImpl> result = query.getResultList();
org.picketlink.idm.model.basic.Role trial = new org.picketlink.idm.model.basic.Role("TRIAL");
if (result.isEmpty()){
UserImpl account = new UserImpl(user);
partitionManager.createIdentityManager().add(account);
partitionManager.createIdentityManager().updateCredential(account, new Password(password));
partitionManager.createIdentityManager().add(trial);
BasicModel.grantRole(partitionManager.createRelationshipManager(), account, trial);
IdentityQuery<UserImpl> q
= partitionManager.createIdentityManager().createIdentityQuery(UserImpl.class);
q.setParameter(UserImpl.LOGIN_NAME, user.getUsername());
UserImpl u = q.getResultList().iterator().next();
setStatus(AuthenticationStatus.SUCCESS);
setAccount(u);
} else {
setStatus(AuthenticationStatus.SUCCESS);
setAccount(result.iterator().next());
}
userEvent.fire(user);
}
}
Even I check the seAccount Account to be set is ok, I am not sure if the Roles is persisted at list at the Picketlink side; because the response of the call:
Caller<AuthenticationService> authServiceCaller;
The Errai Security User returned although not null, the names is "ANONYMOUS" and role is "NOBODY" I'm not sure what's happening here.
Update:
The login(username, password) method returns the correct User and Role, but getUser() does not. This is the issue.
Related
I want to obtain a HttpSession object by URL Path variable id to get some attributes from it.
Context:
I'm trying to implement a web server that has a register and login sub-systems as a learning exercise.
I'm using JAVA, Springboot and various other spring dependencies like hibernate, jdbc, etc.
I got the behavior I wanted, but as I tested my logic with an Android client application I encountered that the register confirmation link I send, does not work if I access it from another device, because the device-sender has a different session and thus my logic fails.
The flow of my registration is as follows:
User POSTs at /register -> { name, email, password }
Server saves this information in their session and sends confirmation email with /register/confirm/{token}
As the user GETs at /register/confirm/{token} that was send to their email,
the server checks if this token is contained in their session and commits the information from the session to the database.
Of course if I register from the device and try to confirm through another device they'd have different sessions and hence the temp information would not be available to the other device, but this is the same user trying to register and I'm looking for a work around. The way I decided to change my code is to send the user /register/confirm/{sessionId}+{token} to their email, but I can't find my way around obtaining the other HttpSession.
(#ServletComponentScan)
I tried to create a HttpSessionListener and tried to maintain a HashMap of HttpSession's but for some reason the Framework would instantiate the Listener object, but never send createSession events to it thus it's HashMap is always empty, thus {sessionId} is never found.
To provide some extra code for context.
My Listener:
#WebListener
public class SessionLookUpTable implements HttpSessionListener {
static final HashMap<String, HttpSession> sessionHashMap = new HashMap<>();
public SessionLookUpTable() {
super();
System.out.println("-------------- Session Listener Created"); // DEBUG
}
// Always empty for some reason, despite constructor being called
static public Optional<HttpSession> findSessionById(String sessionId) {
if (!sessionHashMap.containsKey(sessionId))
return Optional.empty();
return Optional.of( sessionHashMap.get( sessionId ) );
}
#Override
public void sessionCreated(HttpSessionEvent se) {
HttpSessionListener.super.sessionCreated(se);
HttpSession session = se.getSession();
sessionHashMap.put( session.getId(), session );
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSessionListener.super.sessionDestroyed(se);
sessionHashMap.remove(se.getSession().getId() );
}
};
The controller entry points
#PostMapping("/register")
public String register(HttpSession session,
#RequestParam("email") String username,
#RequestParam("password") String password,
#RequestParam("password2") String pw2)
{
User user = new User();
user.setUsername(username);
user.setPassword(password);
user.setPrivilegeLevel( Role.USER_PRIVILEGE_NORMAL );
if(session.getAttribute(ATTRIBUTE_USER_ID) != null) {
return "Already registered";
}
if(!userService.isUserDataValid(user)) {
return "Invalid input for registry";
}
if(userService.usernameExists(user.getUsername())) {
return "User already exists";
}
session.setAttribute(ATTRIBUTE_REGISTER_DATA, user);
String token = userService.sendConfirmationEmail( session );
if(token != null) {
session.setAttribute(ATTRIBUTE_USER_ID, 0L );
session.setAttribute(ATTRIBUTE_REGISTER_TOKEN, token);
}
return "A link was sent to your email.";
}
#RequestMapping("/register/confirm/{sessionId}+{token}")
void confirmRegister(HttpSession sessionIn,
#PathVariable("sessionId") String sessionId,
#PathVariable("token") String token) {
Optional<HttpSession> optSession = SessionLookUpTable.findSessionById( sessionId );
if(optSession.isEmpty())
return;
HttpSession session = optSession.get();
// Multiple confirmations guard
Long userId = (Long)session.getAttribute(ATTRIBUTE_USER_ID);
if( userId != null && userId != 0L ){
return;
}
String sessionToken = (String)session.getAttribute(ATTRIBUTE_REGISTER_TOKEN);
if(!sessionToken.equals(token)) {
return;
}
User user = (User)session.getAttribute(ATTRIBUTE_REGISTER_DATA);
user.setDateRegistered( LocalDate.now() );
Long id = userService.register( user );
session.setAttribute(ATTRIBUTE_USER_ID, id);
}
I'm stuck at this stage for quite a while, so any help is appreciated. Thank you.
When I edit a user's roles through a page in my application it somehow modifies the user's password. I cant for the life of me figure out where I am changing the user's password in my code. I even went so far as to print out the hashed password after I call Save in the user service and they still match! But when I log out and try to log back in with the user's same credentials Spring says the credentials are invalid. Oddly enough, this only happens when I change the roles, if I change just the name or email address the issue doesn't present.
This is my save method in userServiceImpl
#Override
public void save(User user) {
System.out.println("User trying to save");
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
userRepository.save(user);
}
And this is the controller for editing (I know it's a mess and needs to be cleaned up A LOT!)
#PostMapping("/users/edit")
public String editUser(#ModelAttribute("userForm") User user, BindingResult bindingResult, Model model) {
model.addAttribute("highestRole", getHighestRole(user.getUserName(), userService));
model.addAttribute("pageTitle", "AIM - Edit User Id:" + user.getId());
model.addAllAttributes(getUserAttributes());
model.addAttribute("roles", roleRepository.findAll());
userValidator.validate(user, bindingResult);
if (bindingResult.hasErrors()) {
return "user_edit";
}
User userFromDB = userService.findById(user.getId());
if (!user.getFirstName().equals(userFromDB.getFirstName())) {
userFromDB.setFirstName(user.getFirstName());
}
if (!user.getLastName().equals(userFromDB.getLastName())) {
userFromDB.setLastName(user.getLastName());
}
if (!user.getEmail().equals(userFromDB.getEmail())) {
userFromDB.setEmail(user.getEmail());
}
System.out.println("form password length: " + user.getPassword().length());
if (user.getPassword().length() != 0 && !bCryptPasswordEncoder.matches(user.getPassword(), userFromDB.getPassword())) {
userFromDB.setPassword(user.getPassword());
System.out.println("Changed password");
}
String currentHighestRole = getHighestRole(userFromDB.getUserName(), userService);
String newHighestRole = getHighestRoleFromUser(user);
System.out.println("Role set for current user = " + userFromDB.getRoles().toString());
System.out.println("Role set for new user = " + user.getRoles().toString());
if (!currentHighestRole.equals(newHighestRole)) {
// Ensure that at least one Super User remains
if (currentHighestRole == "SUPER_USER") {
List<User> userList = userService.getAllUsers();
int countOfSuperUsers = 0;
for (User u : userList) {
if (getHighestRoleFromUser(u).equals("SUPER_USER")) countOfSuperUsers++;
}
if (countOfSuperUsers <= 1)
return "redirect:/users/viewAll?alert=error&message=Cannot reduce Super User's role. At a minimum, there must be at least one Super User within the application.";
}
// Create new set of roles for user
Set<Role> newRoleSet = new HashSet<>();
for (Role role : roleRepository.findAll()) {
if (newHighestRole.equals(role.getName())) {
newRoleSet.add(role);
System.out.println("Setting highest role to: " + role.getName());
break;
}
}
System.out.println("New role set looks like this: " + newRoleSet.toString());
userFromDB.setRoles(newRoleSet);
}
userService.save(userFromDB);
System.out.println("Highest role attribute on page = " + getHighestRole(user.getUserName(), userService));
User newUser = userService.findByUsername(userFromDB.getUserName());
System.out.println("Password After Save:" + newUser.getPassword());
return "redirect:/users/view?id=" + user.getId();
}
I should also add that my intent is for a SUPER_USER to be able edit anything about a user, including the password, but if the password fields are left blank then the password would remain unaltered.
I have to remove a login field from my User class and use email as a username in SecurityUtils
I've already changed j_username parameter in a frontend, but now the issue remains on a backend
public static String getCurrentUserLogin() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
String userName = null;
if (authentication != null) {
if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
userName = springSecurityUser.getUsername();
} else if (authentication.getPrincipal() instanceof String) {
userName = (String) authentication.getPrincipal();
}
}
return userName;
}
and as a result userName is null, becauseUserDetails and Authentication don't have email. How could I set the field email as a 'j_username'? I've tried this
How to login by email instead of username in spring security
solution but it's not enough since I use an anonymousUser
Also, I have an implementation of UserDetailsService but when debugging it's not called when being the anonymousUser
public class DomainUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public DomainUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(final String login) {
String lowercaseLogin = login.toLowerCase(Locale.ENGLISH);
Optional<User> userFromDatabase = userRepository.findOneByLogin(lowercaseLogin);
return userFromDatabase.map(user -> {
if (!user.getActivated()) {
throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
}
List<GrantedAuthority> grantedAuthorities = user.getAuthorities().stream()
.map(authority -> new SimpleGrantedAuthority(authority.getName()))
.collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(lowercaseLogin,
user.getPassword(),
grantedAuthorities);
}).orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the " +
"database"));
}
}
In order to achieve your goal you will have to control anonymous user behavior. I had that issue before and when the user is logged in the queries are working fine. As M. Denim suggested you should search by email here -> Optional<User> userFromDatabase = userRepository.findOneByEmail(lowercaseLogin);
But in case of anonymous user in getCurrentUserLogin() you have to write an if statement to return anonymous#localhost in case the userName = anonymousUser
here i share some code from my Spring Security Configuration class
.formLogin().loginPage("/login")
.usernameParameter("logInId").passwordParameter("password")
here i use 'logInId' parameter for login instead of default parameter....
i think you searching some thing like this .......
Currently when a user logs in to my web server using a web POST form, a custom authenticator and a custom user. I have the CustomUser put into the Session provided by the RoutingContext because, when using RoutingContext#setUser it only changes the user for that request and as soon as the user is redirected from the login processing page to their destination the CustomUser has been lost.
However, it also seems as though the Session in RoutingContext for the new page doesn't have any user stored in the entry where the auth placed the CustomUser, could this be sending a completely different Session?
Routing:
//ROUTE DEFINITIONS
// SESSION AND COOKIE
router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx)).setNagHttps(false)); //TODO SSL
router.route().handler(CookieHandler.create());
// STATIC
router.route("/").handler(new StaticHandler()); //BASE
router.route("/admin").handler(new StaticHandler()); //ADMIN PAGE
// FORM REQUESTS
router.route("/login").handler(new AuthAndRegHandler(new CustomAuth(), dbController)); //LOGIN REQUEST
router.route("/logout").handler(new AuthAndRegHandler(new CustomAuth(), dbController)); //LOGOUT REQUEST
// AJAX
router.route("/ajax/updateInvoice").handler(new AjaxHandler());
// ERRORS
router.route().failureHandler(new ErrorHandler());
router.route().handler(handle -> {
handle.fail(404);
});
//END DEFINITIONS
AuthAndRegHandler:
public class AuthAndRegHandler extends AuthHandlerImpl {
private DatabaseController db;
private CustomAuth authProvider;
public AuthAndRegHandler(CustomAuth authProvider, DatabaseController db) {
super(authProvider);
this.db = db;
this.authProvider = authProvider;
}
#Override
public void handle(RoutingContext event) {
Logger log = LoggerFactory.getLogger(this.getClass());
HttpServerResponse response = event.response();
HttpServerRequest request = event.request();
Session session = event.session();
String requestedPath = request.path();
authProvider.setJdbc(db.getJdbc()); //returns a JDBCClient
if(requestedPath.equalsIgnoreCase("/login")) {
if(request.method() != HttpMethod.POST)
event.fail(500);
else {
request.setExpectMultipart(true);
request.endHandler(handle -> {
MultiMap formAtts = request.formAttributes();
String email = formAtts.get("email");
String pw = formAtts.get("password");
log.info(email + ":" + pw + " login attempt");
authProvider.authenticate(new JsonObject()
.put("username", email)
.put("password", pw), res -> {
if(res.succeeded()) {
CustomUser userToSet = (CustomUser) res.result();
session.put("user", userToSet);
log.info("Login successful for " + email);
response.putHeader("Location", "/").setStatusCode(302).end();
} else {
event.fail(500);
log.error("Auth error for " + request.host());
}
});
});
}
}
}
}
CustomAuth returns true every time for testing purposes.
StaticHandler
CustomUser user = session.get("user");
event.setUser(user);
response.putHeader("Content-Type", "text/html");
if(user != null) {
log.info(user.principal().getString("email") + " user detected");
event.setUser(user);
} else
log.info("Null user request detected"); //Constantly outputs, even after a login form has been submitted
I'm not entirely sure what's going wrong here. Vertx has sub-optimal documentation for a rookie like myself on session and handling things without their out-of-the-box implementations. Any help on how to log someone in and maintain their session like a normal website would be appreciated.
For those who stumble upon the same problem, but usually skip the comments:
Vert.x SessionHandler depends on CookieHandler, and the order is important here.
From the Vert.x examples:
router.route().handler(CookieHandler.create());
router.route().handler(sessionHandler);
I'm using the Acegi Security plugin for Grails, and authentication via LDAP.
The application logs show that on login, we can authenticate the user and get their roles via LDAP, but the login fails because the User Details cannot be found in the application's database.
Is there a way to auto create and save a basic User Details domain object if one doesn't already exist?
-- Update
Here are the relevant debug entries I am currently seeing
DEBUG populator.DefaultLdapAuthoritiesPopulator - Roles from search: [Role1, Role2, etc]
ERROR springsecurity.GrailsDaoImpl - User not found: MyUserName
DEBUG rememberme.TokenBasedRememberMeServices - Interactive login attempt was unsuccessful.
Sure.
You need to implement custom AuthProvider
SecurityConfig.groovy:
security {
providerNames = ['ldapAuthProvider']
}
Ldap Auth Provider:
import domain.user.AppUser
import org.apache.commons.codec.digest.DigestUtils
import org.apache.log4j.Logger
import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUserImpl
import org.springframework.security.BadCredentialsException
import org.springframework.security.GrantedAuthority
import org.springframework.security.GrantedAuthorityImpl
import org.springframework.security.providers.UsernamePasswordAuthenticationToken
import org.springframework.security.providers.dao.AbstractUserDetailsAuthenticationProvider
import org.springframework.security.userdetails.UserDetails
/**
* Authentication provider that checks user credentials against LDAP
*/
class LdapAuthProvider extends AbstractUserDetailsAuthenticationProvider {
private static final Logger log = Logger.getLogger(LdapAuthProvider.class)
def appUserService
/**
* Checks password hash stored in the session with password in authentication token.
*/
protected void additionalAuthenticationChecks(UserDetails details,
UsernamePasswordAuthenticationToken authentication) {
if (details.password != DigestUtils.md5Hex(authentication.credentials)) {
throw new BadCredentialsException(details.username)
}
}
/**
* Retrieves user from LDAP,
* checks credentials,
* updates local copy of user data,
* returns user details.
*/
protected UserDetails retrieveUser(String login, UsernamePasswordAuthenticationToken authentication) {
AppUser.withTransaction {
log.debug("Trying to retrieve user \"$login\"...")
def password = authentication.credentials?.toString()
def ldapUser = appUserService.findLdapUser(login)
if (!(password && ldapUser?.authenticate(password))) {
log.debug("Can't authenticate \"$login\"")
throw new BadCredentialsException(login)
}
AppUser localUser = AppUser.findByLogin(login, [cache: true])
if (!localUser) {
log.debug("Can't authenticate \"$login\"")
localUser = appUserService.updateLocalUser(ldapUser)
}
log.debug("User \"$login\" is authenticated.")
def authorities = localUser.collectAuthorities().collect {String authority ->
log.debug("\thas right \"$authority\"")
new GrantedAuthorityImpl(authority)
}
def userDetails = new AppUser();
userDetails.setAssignedTemplate(localUser.assignedTemplate)
userDetails.setFullName(localUser.getFullName())
userDetails.setLogin(localUser.getLogin())
userDetails.setEmail(localUser.getEmail())
userDetails.setDisabled(localUser.getDisabled())
userDetails.setManager(localUser.getManager())
userDetails.setRoles(new HashSet(localUser.getRoles()))
log.debug("Retrieving user \"$login\" is completed.")
return new GrailsUserImpl(userDetails.login, DigestUtils.md5Hex(password), true, true, true, true,
authorities.toArray(new GrantedAuthority[authorities.size()]), userDetails)
}
}
}
And in appUserService.updateLocalUser(ldapUser) you need create/modify your Domain object and persist in database.
AppUser updateLocalUser(LdapUser ldapUser) {
def login = ldapUser.login
log.debug("Start updating local user ${login}...")
def localUser = AppUser.findByLogin(login, [cache: true]) ?: new AppUser()
if (localUser.id) {
log.debug("user $login was found in local DB")
if (localUser.disabled ^ ldapUser.isDisabled()) {
log.debug("...user ${login} has been ${localUser.disabled ? 'activated' : 'disabled'}...")
}
} else {
log.debug("user $login is new")
}
localUser.login = login
localUser.email = ldapUser.email
localUser.fullName = ldapUser.fullName ?: login
localUser.disabled = ldapUser.isDisabled();
localUser.roles?.clear()
ldapUser.memberOf.collect { Role.findByLdapName(it, [cache: true]) }.each {role ->
if (role) {
localUser.addToRoles(role)
}
};
localUser.save(flush: true)
log.debug("Update local user $login is complete.")
}
UPDATE #1
You can implement custom UserDetailsService:
package com.foo.bar;
import org.springframework.security.userdetails.UserDetails; import org.springframework.security.userdetails.UserDetailsService;
public class MyUserDetailsService implements UserDetailsService {
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException, DataAccessException {
// lookup user and data
return new MyUserDetails(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities, id, fullName); } }