I have an issue in Spring (or Hibernate) with checking for data existence in DB till updating. I want to update user’s profile and I can change Name, Password and Email fields. Name and Password hasn’t to be unique but Email has to. I show the form with fields filled by user’s old data, when I enter new data not changing Email it, of course, shows that it is already exists. If delete this check I’ll have two same emails in base. How can I set same data for certain user?
My method for update with checking email.
public void updateUser(User user) throws NotUniqueEmailException {
if (user == null) {
throw new NullPointerException();
}
if (user.getUserId() < 1) {
throw new IllegalArgumentException();
}
if (user.getEmail() == null || user.getEmail().intern().equals("")) {
throw new IllegalArgumentException();
}
if (getUserByEmail(user.getEmail()).getEmail() != null) {
throw new NotUniqueEmailException("The email of user not unique! " + user.getEmail());
}
currentSession().update(user);
// currentSession().saveOrUpdate(user);
}
And I’ve one more method for checking for existence.
public boolean isEmailExists(User user) {
Session session = HibernateUtil.openSession();
boolean result = true;
Query query = session.createQuery("SELECT u FROM User u WHERE u.email=?");
query.setString(0, user.getEmail());
User u = (User) query.uniqueResult();
if (u == null) {
result = false;
}
return result;
}
Update controller
#RequestMapping(value = "/edit/{userId}", method = RequestMethod.GET)
public String updateView(#PathVariable("userId")Integer userId,
UserForm userForm,
HttpSession session,
ModelMap model){
User user=userService.getUserById(userId);
userForm.setUser(user);
model.addAttribute("userForm",userForm);
return"profileupdate";
}
#RequestMapping(value = "/edit.do/{userId}", method = RequestMethod.POST)
public String updateUserProcess(#ModelAttribute(value = "userForm")
UserForm userForm,
#PathVariable("userId")Integer userId,
BindingResult result,Model model,
HttpSession session,
HttpServletRequest request){
User user=userService.getUserById(userId);
session.getAttribute("userForm");
model.addAttribute("userForm",userForm);
updateValidator.validate(userForm,result);
if(result.hasErrors()){
logger.error("Validation error");
return"profileupdate";
}
return updatingUser(userForm,user,model,request);
}
private void fillForm(UserForm userForm,User user){
userForm.setUserId(user.getUserId());
userForm.setLogin(user.getLogin());
userForm.setRegDate(user.getRegDate());
userForm.setComputers(userService.getAllUsersComputers(user.getLogin()));
userForm.setRole(roleService.findByName(user.getRole().getRoleName()));
}
private String updatingUser(UserForm userForm,User user,Model model,HttpServletRequest request){
fillForm(userForm,user);
user=userForm.getUser();
try{
userService.updateUser(user);
logger.info("User updated!");
request.getSession().setAttribute("user",user);
return"newprofile";
}catch(NotUniqueEmailException e){
logger.error("Can't update user - not unique email!!",e);
model.addAttribute("errorMsg","Email is already in use!");
return"profileupdate";
}
}
EDIT : add elements for Hibernate persistence methods
If you want Hibernate to automagically know that it should do an an update and not an insert, the primary key has to be set in user. As you say the fields have been modified, I suppose user fields come from a form input. You have two ways to keep the id :
store it in a hidden input field in your form - on submit, spring will populate it
have it in a path variable, get it in your controller and populate it yourself (#RequestMapping(/.../{id}) public String method(#PathVariable("id") user_id, ...))
But it may not be enough and you could have a merge vs update vs saveOrUpdate problem. This other post from SO could give indications Hibernate : Downside of merge over update
compare the args user.getId() with u.getId()
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 an application with #CustomFormAuthenticationMechanismDefinition, and I would like to log the username, session id, IP address, etc. both at login and at logout. The HttpAuthMechanism that gets applied with this annotation associate the given session with the principal, which I can access through the SecurityContext. With a direct logout, I have no problem logging, but I would also like to log when session times out. So I created a HttpSessionListener and in its sessionDestroyed() method I try to access the logged in user through SecurityContext, but it returns an empty set, maybe because the securityContext got invalidated already.
One solution I have in my mind is to store the user principal in a session parameter (which likely happens with the HttpAuthMechanism implementation) and access it from there from the HttpSessionEvent object, but that doesn't feel like the cleanest solution. Is there another Listener I can use or some other solution?
I went with the custom HttpAuthenticationMechanism, here is it if anyone would need it (though I would be more than glad to have some feedback on whether or not it has any security flaws, or improvements).
In an #ApplicationScoped class implementing HttpAuthenticationMechanism:
#Override
public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response, HttpMessageContext httpMessageContext) throws AuthenticationException {
if (!httpMessageContext.isProtected()) {
return httpMessageContext.doNothing();
}
HttpSession session = request.getSession(false);
Credential credential = httpMessageContext.getAuthParameters().getCredential();
// If we already have a session, we get the user from it, unless it's a new login
if (session != null && !(credential instanceof UsernamePasswordCredential)) {
User user = (User) session.getAttribute("user");
if (user != null) {
return httpMessageContext.notifyContainerAboutLogin(user, user.getRoles());
}
}
// If we either don't have a session or it has no user attribute, we redirect/forward to login page
if (!(credential instanceof UsernamePasswordCredential)) {
return redirect(request, response, httpMessageContext);
}
// Here we have a Credential, so we validate it with the registered IdentityStoreHandler (injected as idStoreHandler)
CredentialValidationResult validate = idStoreHandler.validate(credential);
Context context = new Context();
context.setIp(request.getRemoteAddr());
if (validate.getStatus() == CredentialValidationResult.Status.VALID) {
session = request.getSession(true);
CallerPrincipal callerPrincipal = validate.getCallerPrincipal();
session.setAttribute("user", callerPrincipal);
context.setUser(callerPrincipal);
context.setSessionId(session.getId());
Logger log = new Logger(logger, "validateRequest", context);
log.debug("Logged in user: " + callerPrincipal.getName());
String redirectPage = "whatYouWant.xhtml";
redirect(request, response, httpMessageContext, redirectPage);
return httpMessageContext.notifyContainerAboutLogin(validate);
} else if (validate.getStatus() == CredentialValidationResult.Status.NOT_VALIDATED) {
return redirect(request, response, httpMessageContext);
} else {
// Logging
return httpMessageContext.responseUnauthorized();
}
}
And in an implemented HttpSessionListener:
#Override
public void sessionDestroyed(HttpSessionEvent se) {
User user = (User) se.getSession().getAttribute("user");
if (user != null) {
// logging
}
}
I am creating a Spring MVC web application using spring security for authentication.
My application has a requirement that the user is logged in automatically when the user accesses a particular URL, say http://www.example.com/login?username=xyz
The user xyz is in the database, and I need to create a session for this user automatically.
Request you to let me know how this can be achieved.
Thanks!
You can do something like this, the idea would be to retrieve the user from the database, make the checks you want and then use the UsernamePasswordAuthenticationToken to create a new Principal into the current session.
The use of the returned Principal of the method is for subsequent uses on the same request, as the Principal won't be available to be retrieved by normal means until the next request.
public Principal doAutoLogin(String username, String password, HttpServletRequest hreq, HttpServletResponse hres, boolean rememberMe) {
User user = getUserFromDatabase(username);
if(user != null && passwordsMatch(password, user.getPassword())) {
try {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
token.setDetails(new WebAuthenticationDetails(hreq));
if(LOG.isDebugEnabled())
LOG.debug("Logging in with "+ token.getPrincipal());
SecurityContextHolder.getContext().setAuthentication(token);
if(rememberMe) {
rememberMeServices.onLoginSuccess(hreq, hres, token);
}
return token;
} catch (Exception e) {
SecurityContextHolder.getContext().setAuthentication(null);
LOG.error("Failure in autoLogin", e);
return null;
}
}
else {
throw new IllegalArgumentException("The specified credentials can't be found");
}
}
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.