Spring boot REST api ConstraintViolationException on password field - java

I'm having a weird issue on my Spring backend, where I am sending a post request with a domain User object from my angular2 frontend that is being recieved by a REST API endpoint and translating it into the Spring User model object. The issue is that the password field from the JSON request seems to not be translating to the password field on the backend. All the other fields (username, name, email, etc.) are coming through fine, but the password is null.
Request payload as seen from network tab in chrome
email : "asdf#asdf.com" firstName : "asdfasdjk" lastName : "asdfs"
login : "adfsjk" password : "fasdfasdfasdsd"
Response seen from network tab
error: "Internal Server Error" exception: "javax.validation.ConstraintViolationException" message: "Validation failed for classes [domain.User] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[ ConstraintViolationImpl{interpolatedMessage='may not be null', propertyPath=password, rootBeanClass=class domain.User, messageTemplate='{javax.validation.constraints.NotNull.message}'}]"
path:"/api/users" status: 500
Spring Rest method:
#RequestMapping(value = "/users",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public ResponseEntity<User> createUser(#RequestBody User user) throws URISyntaxException {
User result = userRepository.save(user);
return ResponseEntity.created(new URI("/api/users/" + result.getId()))
.headers(HeaderUtil.createEntityCreationAlert("user", result.getId().toString()))
.body(result);
}
Spring domain object
#Entity
#Table(name = "user")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class User extends AbstractAuditingEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#Size(max = 100)
#Column(length = 100, unique = true)
private String login;
#JsonIgnore
#NotNull
#Size(min = 5, max = 60)
#Column(length = 60)
private String password;
#Size(max = 50)
#Column(name = "first_name", length = 50)
private String firstName;
#Size(max = 50)
#Column(name = "last_name", length = 50)
private String lastName;
#Email
#Size(max = 100)
#Column(length = 100, unique = true)
private String email;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_tag",
joinColumns = #JoinColumn(name = "users_id", referencedColumnName = "ID"),
inverseJoinColumns = #JoinColumn(name = "tags_id", referencedColumnName = "ID"))
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private List<Tag> tags = new ArrayList<>();
}
Angular2 domain object
export class User {
id: number;
login: string;
password: string;
firstName: string;
lastName: string;
email: string;
}

The problem is with using #JsonIgnore on the password field, this makes the filed ignorable when reading from or writing to JSON message, one solution is to use #JsonProperty with access = Access.WRITE_ONLY as follows
public class User extends AbstractAuditingEntity implements Serializable {
// Other fileds
#NotNull
#JsonProperty(access = Access.WRITE_ONLY)
#Size(min = 5, max = 60)
#Column(length = 60)
private String password;
}
Another solution is to implement a getter and setter for the password field, and annotate the getter with #JsonIgnore and the setter with #JsonProperty, as follows
public class User extends AbstractAuditingEntity implements Serializable {
// Other fileds
#NotNull
#Size(min = 5, max = 60)
#Column(length = 60)
private String password;
#JsonIgnore
public String getPassword() {
return password;
}
#JsonProperty
public void setPassword(String password) {
this.password = password;
}
}

Related

Bad Request 400 when trying to get all users. Dto and form

I have REST api with User model - DTO and Create / update form. My userService checks if user is administrator, then allow to getAllUsers in List. When I want to get all users, I get Bad request 400, but it should return Forbidden. It used to work but when I added some changes to my code I got bad request. I don't know what I'm missing...
My User.java
///Lombok annotations
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Setter(AccessLevel.NONE)
#Column(unique = true)
private Long id;
#Setter(AccessLevel.NONE)
#EqualsAndHashCode.Include
#Column(nullable = false, unique = true)
private UUID uuid = UUID.randomUUID();
#Column(unique = true, nullable = false, length = 254)
private String login;
#Column(nullable = false, length = 254)
private String firstName;
#Column(nullable = false, length = 254)
private String lastName;
#Enumerated(EnumType.STRING)
private RoleType roleType;
#Column(nullable = false, length = 254)
private String password;
#Email
#Column(nullable = false, length = 254)
private String email;
#Positive
private Double cost;
public User(String login, String firstName, String lastName, RoleType roleType, String password,
String email, Double cost) {
this.login = login;
this.firstName = firstName;
this.lastName = lastName;
this.roleType = roleType;
this.password = password;
this.email = email;
this.cost = cost;
}
UserController
#GetMapping("users")
public ResponseEntity<List<UserDto>> getAllUsers(#RequestParam UUID uuid) {
return userService.getListResponseEntity(uuid);
}
UserService
public ResponseEntity<List<UserDto>> getListResponseEntity(UUID adminUuid) {
if (authService.adminAuth(adminUuid)) {
List<User> users = userRepo.findAll();
List<UserDto> userDto = users
.stream()
.map(user -> userMapper.mapToUserDto(user))
.collect(Collectors.toList());
return new ResponseEntity<>(userDto, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
}
}
UserDto
#Builder
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class UserDto {
private String login;
private String firstName;
private String lastName;
private RoleType roleType;
private String email;
private Double cost;
I think you missed uuid parameter in request header.
It will be like this. http://localhost:8080/users?uuid="enter_your_uuid_here"
There could be changes that your JSON request data is not matching with you DTO data fields.
Validate following points in your JSON request
there could be issue of wrong field Name
may be not sending proper data as per the datatype.

Cannot deserialize instance of `java.lang.Long` out of START_OBJECT token; on Spring boot post

I want to post a new Game to my spring boot backend from my angular application.
I do this through an post from angular. This game has a relationship with my user entity in spring boot JPA. But when I do my post I get an 400 error code in the console which says:
"MethodArgumentNotValidException","fieldErrors":[{"field":"user","errorCode":"NotNull"}]}
This was the data from my post:
{category: "sport", sessieId: 20004, userID: 10004, score: null}
My game controller:
#CrossOrigin(origins = "*", allowedHeaders = "*")
#RestController
#RequestMapping(value = "/api/games", produces = MediaType.APPLICATION_JSON_VALUE)
public class GameController {
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public Long createGame(#RequestBody #Valid final GameDTO gameDTO) {
return gameService.create(gameDTO);
}
// more methods for http
}
my Game domain:
#Entity
public class Game {
#Id
#Column(nullable = false, updatable = false)
#SequenceGenerator(name = "primary_sequence", sequenceName = "primary_sequence",
allocationSize = 1, initialValue = 10000)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "primary_sequence")
private Long id;
#Column(nullable = false)
private Integer sessieId;
#Column(nullable = false)
private String category;
#Column
private String score;
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User user;
my Game DTO:
public class GameDTO {
private Long id;
#NotNull
private Integer sessieId;
#NotNull
#Size(max = 255)
private String category;
#Size(max = 255)
private String score;
#NotNull
private Long user;
The create method from my Game service:
public Long create(final GameDTO gameDTO) {
final Game game = new Game();
mapToEntity(gameDTO, game);
return gameRepository.save(game).getId();
}
So I saw the Game entity expects a long from user, so I tried to post the object user in the post for game.
the post body:
{category: "sport", sessieId: 20004,…}
category: "sport"
score: null
sessieId: 20004
user: {id: 10004, firstName: "test5", lastName: "test5", email: "test5#test.nl", password: "test"}
But then I got the following error:
JSON parse error: Cannot deserialize instance of java.lang.Long out of START_OBJECT token;
Do I need to pass in a user object on the post? and how can I then fix the error?
The tpye of user in class GameDTO is incorrect. It's "Long" type, but the "user" in post json is a object.
public class GameDTO {
private Long id;
#NotNull
private Integer sessieId;
#NotNull
#Size(max = 255)
private String category;
#Size(max = 255)
private String score;
#NotNull
private User user;
}

Spring REST limit user access by user role

I have several user roles, let's say: user and admin.
I want to grant access to certain routes only to users with the role of admin. For example for this route:
#GetMapping("/all")
public ResponseEntity getAll(){
List<User> users = this.userRepository.findAll();
return new ResponseEntity<>(users, HttpStatus.OK);
}
How is it possible to create different middlewares in Spring so that I can use them to limit access to a certain user role?
Here is my User model:
#Entity
#Table(name = "users")
public class User implements Serializable {
private static final long serialVersionUID = -7643506746013338699L;
public User() { }
public User(String username, String email, String password, String firstName, String lastName) {
this.username = username;
this.password = password;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
#Id
#NotEmpty
#Size(min = 5, max = 15)
#Column(name = "username")
private String username;
#Email
#NotEmpty
#Column(name = "email")
private String email;
#NotEmpty
#Size(min = 5)
#Column(name = "password")
private String password;
//some more user properties
...
#ManyToMany(fetch=FetchType.EAGER)
#JoinTable(name = "user_roles", joinColumns = #JoinColumn(name = "username"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
// getters and setters
...
}
I have the JWT authentication implemented. Please let me know if I should upload it as well.
Thank you!
You can use hasRole("ADMIN") on that route "/all" in spring security config
https://github.com/spring-projects/spring-security/blob/master/samples/boot/oauth2resourceserver/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java
otherwise you can extract role from Jwt like so and test for the correct role:
https://github.com/spring-projects/spring-security/blob/master/samples/boot/oauth2resourceserver/src/main/java/sample/OAuth2ResourceServerController.java

Foreign Key is Null

I trying to make relation between phonebook and user through jpa, when the current logged in user creates a contact the foreign key of user in table phonebook remains null. I checked couple of question here but it did'not work for me.
Phonebook
#Entity
#Table(name = "Phonebook")
public class Phonebook {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "phonebook_id")
private Long id;
#Column(name = "phone", length = 15, nullable = false)
private String phoneNumber;
#Column(name = "firstname", length = 50, nullable = false)
private String firstName;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
//getters and setters
User
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Long id;
#Column(name = "email")
private String email;
#Column(name = "password")
#Length(min = 5, message = "*Your password must have at least 5 characters")
#org.springframework.data.annotation.Transient
private String password;
#OneToMany(mappedBy = "user")
private List<Phonebook> phonebooks;
//getters and setters
PhonebookController
#RequestMapping(value = {"/home/phonebook"}, method = RequestMethod.GET)
public String showPage(Model model, #RequestParam(defaultValue = "0") int page){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User user = userService.findUserByEmail(auth.getName());
model.addAttribute("data",phonebookRepository.findAllByUserId(user.getId(),PageRequest.of(page,10)));
model.addAttribute("currentPage",page);
return "/home/phonebook";
}
#PostMapping("/home/phonebook/save")
public String save (Phonebook p){
phonebookRepository.save(p);
return "redirect:/home/phonebook";
}
PhonebookRepository
#Repository("phonebookRepository")
public interface PhonebookRepository extends JpaRepository<Phonebook,Integer> {
List<Phonebook> findAllByUserId(Long id, Pageable pageable);
}
what You have to do the first create a user object and set the id and then persist the phone book.
You must persist PhoneBook together with your User.
User u = new User();
// Set properties for your User...
PhoneBook p = new PhoneBook();
// Set properties for your phonebook...
// Store phone book to user:
u.setPhoneBook(Collections.singletonList(p));
userRepository.save(p);

Customize form bean creation process in spring

I have the following bean:
public class TerminalAdmin {
#Id
#Column(name = "admin_id", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id")
#SequenceGenerator(name = "user_id", sequenceName = "user_id")
private Long adminId;
#Column(name = "email", nullable = false)
private String email;
#Column(name = "phone")
#Size(max = 255)
private String phone;
#Size(max = 255)
#Column(name = "name")
private String name;
#Column(name = "registration_date")
#Temporal(TemporalType.TIMESTAMP)
private Calendar createDate;
#Column(name = "password", nullable = false)
#Size(min=1, max = 255, message = "введите пароль длиной от 1 до 255 символов")
private String password;
#ManyToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL)
#JoinTable(name = "admin_role", joinColumns = {
#JoinColumn(name = "admin_id", nullable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id",
nullable = false) })
private Set<AdminRole> adminRoles;
#Column(name = "blocked")
private boolean blocked;
...
}
and this:
public class AdminRole {
#Id
#Column(name = "role_id", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id")
#SequenceGenerator(name = "user_id", sequenceName = "user_id")
private Long id;
#Column(name = "role")
private String role;
....
}
Inside controller:
#RequestMapping(value = "/admin/addNewAdmin")
public String adminUsers(#Valid TerminalAdmin terminalAdmin,
BindingResult bindingResult, ModelMap model, Principal principal, HttpSession session) {
from client side I send following request:
terminalAdmin comes to the method looks like this
Why spring writes values into role field?
How to force spring write 250/251 into id field?
P.S.
I tried to write
InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(AdminRole.class, new PropertyEditorSupport() {
public void setAsText(String name) {
....
}
});
}
but setAsText method doesn't invoke.
This is not a good practice to populate model objects into to forms since Spring can bind fields to object even if they are not populated into the view if your init binder is not properly configured.
Easiest way is to create DTO objects, eg. you could create AdminTerminalDTO or AdminTerminalForm wich you populate to the view.
The Form could contain same fields as AdminTerminal excluding ID field or any other sensitive fields. You cant insert new ID's from the view since it can cause DB integrity errors.
After successful validation you just persist your model object filling it with DTO/Form Object.
Moreover your JSR-303 Annotations seem to be not used in a proper way.
The #Size Annotation is not proper a validation to check String length. You have to use #Length instead. You use #Size to check length of an arrays. #Size also works on Strings but #Length is more accurate.
You can't just send an Integer and just try to bind to your Set(spring does some weird binding as you can see now) . Instead you already done addNewAdmin method in your controller wich already informs that it adds an Admin User.
You have to assign admin role on the server side right in this method. First you can use DTO wich will contain eg. username,password and other fields. You annote them with proper JSR-303 Annotations. Using bindingResult you check if there were any validation errors. If form is validated fine, you just convert your DTO/Form object to Model object. Then you can add admin role and persist your model object.
I can write some example code if this tips are not enough.
EDIT:
public class TerminalAdminDTO {
private String username;
#Length(max = 255)
public String getUsername(){
return username;
}
public void setUsername(String username){
this.username = username;
}
public TerminalAdmin convertToTerminalAdmin(){
TerminalAdmin terminalAdmin = new TerminalAdmin();
terminalAdmin.setUsername(this.username);
return terminAdmin;
}
}
#Entity
#Table
public class TerminalAdmin {
#Id
#Column(name = "admin_id", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id")
#SequenceGenerator(name = "user_id", sequenceName = "user_id")
private Long adminId;
#Column(name = "email", nullable = false)
private String email;
#Column(name = "phone")
#Size(max = 255)
private String phone;
#Size(max = 255)
#Column(name = "name")
private String name;
#Column(name = "registration_date")
#Temporal(TemporalType.TIMESTAMP)
private Calendar createDate;
#Column(name = "password", nullable = false)
#Size(min=1, max = 255, message = "введите пароль длиной от 1 до 255 символов")
private String password;
#ManyToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL)
#JoinTable(name = "admin_role", joinColumns = {
#JoinColumn(name = "admin_id", nullable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id",
nullable = false) })
private Set<AdminRole> adminRoles;
#Column(name = "blocked")
private boolean blocked;
...
}
#RequestMapping(value = "/admin/addNewAdmin")
public String adminUsers(#Valid TerminalAdminDTO terminalAdminDTO,
BindingResult bindingResult, ModelMap model, Principal principal, HttpSession session) {
if(result.hasErrors()){
return "errorPage";
}else{
userService.createAdminUser(terminalAdminDTO);
return "successPage";
}
}
#Service
#Transactional
public class UserServiceImpl implements UserService {
private final int ADMIN_ROLE_ID = 0;
#Autowired
EntityManager entityManager;
public void createAdminUser(TerminalAdminDTO terminalAdminDTO){
TerminalAdmin terminalAdmin = terminalAdminDTO.convertToTerminalAdmin();
AdminRole adminRole = entityManager.find(AdminRole.class,ADMIN_ROLE_ID);
terminalAdmin.getAdminRoles().add(adminRole);
entityManager.create(terminalAdmin);
}
}
I wrote it as an example of way doing it, this is not a ready-made code

Categories