I am trying to learn Spring. I created a project with Spring Boot using the following tools:
Spring Data JPA
Spring Data REST
Spring HATEOAS
Spring Security
I am trying to create a User entity. I want the user to have an encrypted password (+ salt).
When i do POST to /api/users i successfully create a new user.
{
"firstname":"John",
"lastname":"Doe",
"email":"johndoe#example.com",
"password":"12345678"
}
But i have 2 problems:
the password is saved in clear-text
the salt is null
+----+---------------------+-----------+----------+----------+------+
| id | email | firstname | lastname | password | salt |
+----+---------------------+-----------+----------+----------+------+
| 1 | johndoe#example.com | John | Doe | 12345678 | NULL |
+----+---------------------+-----------+----------+----------+------+
The problem i think is that the default constructor is used and not the other one i have created. I am new to Spring and JPA so i must be missing something. Here is my code.
User.java
#Entity
#Table(name = "users")
public class User{
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
public String firstname;
#Column(nullable = false)
public String lastname;
#Column(nullable = false, unique = true)
public String email;
#JsonIgnore
#Column(nullable = false)
public String password;
#JsonIgnore
#Column
private String salt;
public User() {}
public User(String email, String firstname, String lastname, String password) {
this.email = email;
this.firstname = firstname;
this.lastname = lastname;
this.salt = UUID.randomUUID().toString();
this.password = new BCryptPasswordEncoder().encode(password + this.salt);
}
#JsonIgnore
public String getSalt() {
return salt;
}
#JsonProperty
public void setSalt(String salt) {
this.salt = salt;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
#JsonIgnore
public String getPassword() {
return password;
}
#JsonProperty
public void setPassword(String password) {
this.password = password;
}
}
UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
public User findByEmail(String email);
public User findByEmailAndPassword(String email, String password);
}
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application .class, args);
}
}
Also if someone finds what i did wrong, i would like to point me where/how i should put the user login code (decryption).
Thanks.
So, here is how i solved my problem: i created a Controller as my custom endpoint and then i created a service in which i placed the logic i wanted for the creation of the user. Here is the code:
UserController.java
#Controller
public class UserController {
#Autowired
private UserService userService;
#RequestMapping("/api/register")
#ResponseBody
public Long register(#RequestBody User user) {
return userService.registerUser(user);
}
...
}
UserService .java
#Service
public class UserService {
#Autowired
private UserRepository userRepository;
public Long registerUser(User user) {
user.setPassword(new BCryptPasswordEncoder().encode(password));
userRepository.save(user);
return user.getId();
}
...
}
so by doing a POST with
{
"firstname":"John",
"lastname":"Doe",
"email":"johndoe#example.com",
"password":"12345678"
}
in /api/register, i can now create a user with a hashed password.
If you want Spring to use your constructor, you need to
remove the no-argument constructor
annotate every parameter in the other constructor with #JsonProperty like this
public User(#JsonProperty("email") String email,
#JsonProperty("firstname") String firstname,
#JsonProperty("lastname") String lastname,
#JsonProperty("password") String password) {
this.email = email;
this.firstname = firstname;
this.lastname = lastname;
this.password = new BCryptPasswordEncoder().encode(password);
}
You don't need to provide a salt value to the BCryptPasswordEncoder because it already salts passwords by itself.
Related
Model Class for User:
#Component
public class UserModel {
private Integer userId;
private Integer roleId;
private String firstName;
private String email;
private String password;
public Integer getUserId() {
return userId;
}
public Integer getRoleId() {
return roleId;
}
public String getFirstName() {
return firstName;
}
public String getEmail() {
return email;
}
public String getPassword() {
return password;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
public void setName(String firstName) {
this.firstName = firstName;
}
public void setEmail(String email) {
this.email = email;
}
public void setPassword(String password) {
this.password = password;
}
}
Model Class for Role of the User:
#Component
public class RolesModel {
private Integer roleId;
private String roleName;
public Integer getRoleId() {
return roleId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
}
Entity Class for User:
#Entity
#Table(name = "userBloodBank")
public class User {
#Id
#GeneratedValue (strategy = GenerationType.AUTO)
private Integer userId;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="roleId")
private Roles roles;
#NotNull
#Column
#Pattern(regexp="[A-Z][a-zA-Z]*",message="Only enter words starting with captital letter")
private String firstName;
#NotNull
#Column
#Email
#Pattern(regexp = "^[a-zA-Z0-9+_.-]+#[a-zA-Z0-9.-]+$", message = "Enter a proper email ID")
private String email;
#NotNull
#Column
#Pattern(regexp= "^(?=.*[0-9])"
+ "(?=.*[a-z])(?=.*[A-Z])"
+ "(?=.*[##$%^&+=])"
+ "(?=\\S+$).{8,20}$", message="Enter minimum 8 characters and maximum 20 characters")
private String password;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Roles getRoles() {
return roles;
}
public void setRoles(Roles roles) {
this.roles = roles;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Entity Class for Role:
#Entity
#Table(name="Roles")
public class Roles {
#Id
#GeneratedValue (strategy = GenerationType.AUTO)
private Integer roleId;
#NotNull
#Column
#Pattern(regexp="(Recipient|Donor|BloodBankAdmin|Admin)", message = "Choose among Recipient|Donor|BloodBankAdmin|Admin")
private String roleName;
public Integer getRoleId() {
return roleId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
}
In the above case Role Id has oneToOne relationship and how will I map this to model object of role and user? Help me with the code to create the function "convertModelToEntity()" and "convertEntityToModel".
You can add below code in your User-entity class to map entity with Model.
public static UserModel convertEntityToModel(User user) {
UserModel userModel = new UserModel();
userModel.setUserId(user.getUserId());
userModel.setRoleId(user.getRoles().getRoleId());
userModel.setFirstName(user.getFirstName());
userModel.setEmail(user.getEmail());
userModel.setPassword(user.getPassword());
return userModel;
}
This will return User-model.
I think you should add the Roles-model(or list of Roles-model) in User Model.
A lot of the articles online for Spring Boot deals with Spring Security and it does not help me in the slightest. I am trying to implement a registration and login page and once the user successfully logins, it will take them to a welcome page where it should display their first name, something like "Welcome first name or Welcome username". I have tried passing the first name through a
model.addAttribute("firstName", accountInstance.getFirstName());
but that doesn't seem to work. Any hints to achieve this would be much appreciated
Login Controller
#Controller
public class LoginController {
#Autowired
private AccountRepository accountRepo;
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String showLoginPage(ModelMap model) {
model.addAttribute("login", new AccountEntity());
return "login";
}
#RequestMapping(value = "/login", method = RequestMethod.POST)
public Object submitLoginIn(#ModelAttribute("login") AccountEntity accountForm, Model model) {
AccountEntity accountInstance = accountRepo.findByEmail(accountForm.getEmail().toLowerCase());
// Password Verifier using Argon2
Argon2PasswordEncoder argon2PasswordEncoder = new Argon2PasswordEncoder();
boolean passwordMatch = argon2PasswordEncoder.matches(accountForm.getPassword(), accountInstance.getPassword());
// issue where if i use caps email, throws null pointer exception
if (accountInstance == null || !passwordMatch) {
System.out.println("Invalid Email or Password");
// return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
return "login";
} else if (accountInstance.isEnabled() == false) {
System.out.println("Cant login cause not verified");
return "login";
} else {
System.out.println("account exist");
model.addAttribute("firstName", accountInstance.getFirstName());
return "redirect:welcome"; // Change later
}
}
}
Account Repository
public interface AccountRepository extends CrudRepository<AccountEntity, Long> {
// Optional<AccountEntity> findById(Long Id);
AccountEntity findByUserName(String userName);
AccountEntity findByPassword(String password);
AccountEntity findByEmail(String email);
AccountEntity findByVerificationCode(String verificationCode);
}
Account Entity
#Entity(name = "user")
public class AccountEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
private String userName;
private String email;
private String password;
// private String gender;
private Integer age;
private Date createdDate;
private boolean enabled;
#Column(updatable = false)
private String verificationCode;
// Getters and Setters
public void setId(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
/*
* public String getGender() { return gender; }
*
* public void setGender(String gender) { this.gender = gender; }
*/
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getVerificationCode() {
return verificationCode;
}
public void setVerificationCode(String verificationCode) {
this.verificationCode = verificationCode;
}
public Date getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
Welcome.jsp
<p> Welcome, ${firstName.firstName} </p>
<!-- <p> Welcome, ${firstName} </p> -->
SO #Bollywood was correct with the redirecting:welcome. Doing so didn't pass the value I wanted to the jsp. Changing it to return "welcome" instead of return "redirect:welcome" worked!
I need take all data of the current user which is logged in and send it in JSON format into the route "/home". I was searching how to do it, but nothing.. I found that i can take only username and authorities there. Can someone help me to handle it? Thanks all.
There is my AuthController.java
// Getting all user data
#RequestMapping(value = "/home", method = RequestMethod.GET)
public String getUsersDataById(Principal principal) {
return principal.getName();
}
There is my UserRepository
import com.example.demo.Models.Users;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<Users, Integer> {
Optional<Users> findByUsername(String username);
}
There are my services:
MyUserDetails.java
public class MyUserDetails implements UserDetails {
private String username;
private String password;
private String firstname;
private String lastname;
private String email;
private String last_login_date;
private String registration_date;
private String last_login_ip;
private Integer balance;
private Integer status;
private String brith_date;
private List<GrantedAuthority> authorities;
private boolean active;
public MyUserDetails(Users user) {
this.username = user.getUsername();
this.password = user.getPassword();
this.authorities = Arrays.stream(user.getRoles().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
this.active = user.isActive();
}
public MyUserDetails(String username, String firstname, String lastname, String email, String last_login_date, String registration_date, String last_login_ip, Integer balance, Integer status, String brith_date) {
this.username = username;
this.firstname = firstname;
this.lastname = lastname;
this.email = email;
this.last_login_date = last_login_date;
this.registration_date = registration_date;
this.last_login_ip = last_login_ip;
this.balance = balance;
this.status = status;
this.brith_date = brith_date;
}
public MyUserDetails() {
}
// and Override methods by default..
MyUserDetailsService
#Service
public class MyUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<Users> user = userRepository.findByUsername(username);
user.orElseThrow(() -> new UsernameNotFoundException("Not found: " + username));
return user.map(MyUserDetails::new).get();
}
}
There is Users.java with columns from table and geters and setters without constructor
#Entity
#Table(name = "users")
public class Users {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String username;
...
/// etc..
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
...
// etc..
Annotate your method with #AuthenticationPrincipal and use UserDetails.
#RequestMapping(value = "/home", method = RequestMethod.GET)
public Userdetails getUsersDataById(#AuthenticationPrincipal UserDetails userDetails) {
return userDetails;
}
I am using spring boot 2 with JPA and Spring Security.
I have a handler method that, depending upon a dropdown, will find all links either similar to a specific title or posted by a specific user. I know my database is set up properly.
I am getting a null pointer exception at this line:
Optional<User> user = userRepository.findUserByAlias("searchTerm");
I have tried changing the method to findByAlias(...) with the same result.
This is the code for my UserRepository:
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
Optional<User> findUserByAlias(String alias);
Optional<User> findByAlias(String alias);
}
and this is my handler method wherein the error occurs at this line:
Optional<User> user = userRepository.findUserByAlias("searchTerm");
#GetMapping("/search")
public String showSearchResults(#RequestParam("searchTerm") String searchTerm, #RequestParam("searchBy") String searchBy, Model model) {
System.out.println("INSIDE showSearchResults + searchTerm =" + searchTerm);
List<Link> searchResults;
if(searchBy.equals("user")) {
System.out.println("INSIDE IF EQUALS 'user'");
// get the user by alias
Optional<User> user = userRepository.findUserByAlias("searchTerm");
// if the user is present the find all links by the user id
if (user.isPresent()) {
searchResults = linkRepository.findAllByUser_Id(user.get().getId());
} else {
searchResults = null;
}
}
if(searchBy.equals("title")){
searchResults = linkRepository.findAllByTitleLike("%" + searchTerm + "%");
} else {
searchResults = null;
}
model.addAttribute("searchTerm", new SearchTerm());
model.addAttribute("searchResults", searchResults);
return "search-results";
}
and this is my User class:
#Entity
public class User implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private Long id;
#Column
private String email;
#Column
private String password;
#Column
private boolean enabled;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "users_roles",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id")
)
private Set<Role> roles = new HashSet<>();
#Column
private String firstName;
#Column
private String lastName;
#Column
private String fullName;
#Column
private String alias;
#Transient
private String confirmPassword;
public User(){
}
public User(String email, String password, boolean enabled,
String firstName, String lastName,
String fullName, String alias) {
this.email = email;
this.password = password;
this.enabled = enabled;
this.firstName = firstName;
this.lastName = lastName;
this.fullName = fullName;
this.alias = alias;
}
public void addRole(Role role){
roles.add(role);
}
public void addRoles(Set<Role> roles) {
roles.forEach(this::addRole);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFullName() {
return firstName + " " + lastName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getConfirmPassword() {
return confirmPassword;
}
public void setConfirmPassword(String confirmPassword) {
this.confirmPassword = confirmPassword;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for(Role role : roles){
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
#Override
public String getUsername() {
return null;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
}
So I have two questions:
1.) First and most importantly - why am I getting null pointer exception? I'm at a loss as to how to go about debugging this.
2.) What is the difference between findByAlias and findUserByAlias?
Any advice would be much appreciated.
Thank you for your help,
Marc
How is your userRepository injected into your controller? It seems that is the most likely reason for the null pointer.
I've got problem with my entities. I've trade to make many-to-one connection between this two entities. I'm doing it like that:
User user = new User();
user.setName("a");
user.setLastName("b");
Set<Adress> a = new HashSet<Adress>();
Adress a1 = new Adress();
Adress a2 = new Adress();
a1.setCity("a1");
a2.setCity("a2");
a.add(a1);
a.add(a2);
user.setAdress(a);
userProxy.save(user);
My entites:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private Long id;
#NotNull
private String name;
#NotNull
private String lastName;
#OneToMany(mappedBy="user", cascade=CascadeType.ALL)
private Set<Adress> adress = new HashSet<Adress>();
public User(String name, String lastName) {
this.name = name;
this.lastName = lastName;
}
public User() {
}
public Set<Adress> getAdress() {
return adress;
}
public void setAdress(Set<Adress> adress) {
this.adress = adress;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Long getId() {
return id;
}
}
Second Entity
#Entity
public class Adress {
#Id
#GeneratedValue(strategy=GenerationType.TABLE)
private Long id;
private String city;
#ManyToOne
#JoinColumn(name="user_id")
private User user;
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public Long getId() {
return id;
}
}
Data in table User saves fine, but in table adress field user_id is "NULL" can anyone explain to me why is that? I've tried a lots of combinations with #ManyToOne but nothing worked for me.
For more details UserProxy:
#Service
public class UserProxyDao {
private UserDao userDao;
#Autowired
public UserProxyDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(User user) {
userDao.save(user);
}
}
however if i put #NotNull on field user in Adress entity validation fails... I really dont know why is that
Caused by: javax.validation.ConstraintViolationException: validation failed for classes [pl.rd.j2ee.api.domain.Adress] during persist time for groups [javax.validation.groups.Default, ]
You should be able to do this in one action as long as you do this first.
a1.setUser(user);
a2.setUser(user);
You could always add User to your Address constructor and do it there.
public Address (User user) {
this.user = user;
}