#JsonProperty/#JsonIgnore not working as expected on write-only property - spring-boot

As suggested here I've tried to use #JsonIgnore and #JsonProperty (from import com.fasterxml.jackson.annotation.*;) on the password and passwordSalt fields of my AppUser.
#Entity
#Table(name = "app_user")
public class AppUser extends AbstractTimestampEntity {
// ..
#JsonIgnore
public String getPassword() {
return password;
}
#JsonProperty
public void setPassword(String password) {
this.password = password;
}
#JsonIgnore
public String getPasswordSalt() {
return passwordSalt;
}
#JsonProperty
public void setPasswordSalt(String passwordSalt) {
this.passwordSalt = passwordSalt;
}
}
However, for some reason the two fields are always null. As I try to "register" a user, the JSON I am sending is not getting de-serialized completely. password is set to null:
What I want to achieve is to be able to receive the user's password in order to store it, but at the same time make sure that the stored (encrypted) password is not being serialized and sent back to the client as well.
What am I doing wrong here?

With Jackson version 2.9.0 the following works for me:
#NoArgsConstructor
#AllArgsConstructor
public class Credentials {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#JsonProperty(access = Access.WRITE_ONLY)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
…and a running test example…
#Test
public void jsonIgnoreSerialization() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
String serialized = objectMapper.writeValueAsString(new Credentials("user", "pass"));
assertThat(serialized).isEqualTo("{\"username\":\"user\"}");
Credentials credentials = objectMapper.readValue("{\"username\":\"user\",\"password\":\"pass\"}", Credentials.class);
assertThat(credentials.getUsername()).isEqualTo("user");
assertThat(credentials.getPassword()).isEqualTo("pass");
}
For the sake of simplicity the constructors are the ones from Lombok and the assertions come from AssertJ.
Can you give this a try?

Related

How to retrieve all data of the current User in controller?

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;
}

Java-Hashed password value is null

Hello I have spent several hours trying to figure out why my hashed password is null. I am trying to use Spring BCryptPasswordEncoder. I have read several documentations, looked at many different examples, and watched videos on how to use this encryptor but I just can't get it to work. Looking at my MYSQL database I know the password field is populated, but the field for hashPassword remains null. Before anyone flips out I know that the plain password should not be stored in the database, but at the moment this is how I know that the password parameter is not null. As far as I know this is not a duplicate question and if so please direct me to right place, because everything I have seen for the hashing category has not been helpful. Below is a snippet of the code I am using if you need more detail please let me know.
#Entity
public class User {
#Id
#GeneratedValue
private int uid;
#NotNull
#Column(unique = true )
private String username;
#NotNull
private String password;
#Column
private String hashedPassword;
private static final BCryptPasswordEncoder encodedPassword = new BCryptPasswordEncoder();
public User(String username, String password) {
this.username = username;
this.password = password;
this.hashedPassword = hashIt(this.password);
}
public User() { }
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getHashedPassword() {
return hashedPassword;
}
public void setHashedPassword(String hashedPassword) {
this.hashedPassword = hashedPassword;
}
public String hashIt(String password) {
return encodedPassword.encode(password);
}
public boolean isCorrectPassword(String password){
return encodedPassword.matches(password,hashedPassword);
}
}

Spring JPA - Ignore a field only in persistence

I have a field called password which can be received by endpoint. But it cannot be sent back in response or persisted in Database
The class is as follows -
public class ShortURL {
#Pattern(regexp="^(https?|ftp|file)://[-a-zA-Z0-9+&##/%?=~_|!:,.;]*[-a-zA-Z0-9+&##/%=~_|]")
private String url;
#Size(min=8,max=16)
#Transient
private String password = null;
private boolean isPasswordProtected = false;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isPasswordProtected() {
return isPasswordProtected;
}
public void setPasswordProtected(boolean isPasswordProtected) {
this.isPasswordProtected = isPasswordProtected;
}
public ShortURL(
#Pattern(regexp = "^(https?|ftp|file)://[-a-zA-Z0-9+&##/%?=~_|!:,.;]*[-a-zA-Z0-9+&##/%=~_|]") String url,
#Size(min = 8, max = 16) String password, boolean isPasswordProtected) {
super();
this.url = url;
this.password = password;
this.isPasswordProtected = isPasswordProtected;
}
#Transient works properly. But adding the #JsonIgnore after #Transient causes problems -
Type definition error: [simple type, class java.lang.String];
nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
No fallback setter/field defined for creator property 'password'"
How do I achieve my intentions?
Depends on your Jackson version.
Before version 1.9, you could add #JsonIgnore to the getter of password and add #JsonProperty to the setter of the password field.
Recent versions of Jackson provide READ_ONLY and WRITE_ONLY annotation arguments for #JsonProperty, something like this:
#JsonProperty(access = Access.READ_ONLY)
private String password;
Yes you can use #JsonIgnore to let jackson ignore it during sending the user response but. There are certain best practices you should follow.
Never expose the Entities directly to the endpoint instead its better to have a wrapper i.e DTO that translates your entity to the required response.
For eg. in your case
public class ShortURL {
#Pattern(regexp="^(https?|ftp|file)://[-a-zA-Z0-9+&##/%?=~_|!:,.;]*[-a-zA-Z0-9+&##/%=~_|]")
private String url;
#Size(min=8,max=16)
private String password;
private boolean isPasswordProtected;
}
//here is the dto in which you can create a parameterised constructor and
accordingly invoke it based on the fields you want to set.
public class ShortURLDTO {
private String url;
public ShortURLDTO(String url){
this.url=url
}
}

Spring Boot Data Rest JPA - Entity custom create (User)

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.

why I can't use string as id

I am trying to create a user model with a CrudRepository:
#Entity
public class User {
#Id
#GeneratedValue
private String username;
private String password;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
public interface UserRepository extends CrudRepository<User, String> {
}
However I got an 500 error every time I call findOne():
#Controller
public class UserController {
#Autowired
private UserRepository users;
#Override
#RequestMapping(value="/register", method=RequestMethod.POST)
public #ResponseBody User register(#RequestBody User userToRegister) {
String username = userToRegister.getUsername();
User user = users.findOne(id);
if (user != null) {
return null;
}
User registeredUser = users.save(userToRegister);
return registeredUser;
}
}
However if I just switch to an long type id instead of username itself then everything works. I think it's common to use string as id. So how to make this work?
I use the embedded hsql database. I didn't wrote any sql code.
The problem is that String username; is annotated with both #Id and #GeneratedValue. #Id means that is should be a primary key, fine it can be a String. But #GeneratedValue means that you want the system to automatically generate a new key when you create a new record. That's easy when the primary key is integer, all databases have a notion of sequence (even if the syntax is not always the same). But if you want String automatically generated keys, you will have do define your own custom generator.
Or if you have no reason for the #GeneratedValue annotation, simply remove it as suggested by Bohuslav Burghardt
Use column annotation like below by putting nullable False.
#Id
#GeneratedValue
#Column(name = "username", nullable = false)
private String username;

Resources