I'm learning Spring Boot security, I am trying to figure out how to properly implement authorization but each resource I find uses InMemory data to show it. However, I want to use User data that is entered from a registration form that is stored in the DB and give each user a default role of ROLE_USER.
User Model
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id;
private String firstname;
private String lastname;
private String username;
private String email;
private String password;
#ManyToMany(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinTable(
name = "user_role",
joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "role_id")}
)
#Enumerated(EnumType.STRING)
Set<Role> roles = new HashSet<>();
}
Role Model
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "roles")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Enumerated(EnumType.STRING)
#Column(length = 20)
private RoleName name;
}
Role Enum
public enum RoleName {
ROLE_ADMIN,
ROLE_USER,
ROLE_EMPLOYEE;
}
PostMapping Method that saves user to DB
#PostMapping(value="users/addNew")
public RedirectView addNew(User user, RedirectAttributes redirect, Model model) throws QrGenerationException {//Controller used to add new user to database(Register new user)
Role role = new Role();
role.setName(RoleName.ROLE_USER);
roleRepository.save(role);
user.setRoles(new HashSet<>(Collections.singletonList(role)));
userService.save(user);
RedirectView redirectView= new RedirectView("/login",true);//After successful registration user is redirected to the login page
redirect.addFlashAttribute("message", "You successfully registered! You can now login");
return redirectView;
}
And this is the Registration form
<form class="login-form" th:action="#{/users/addNew}" method="post">
<div class="login-wrap">
<p class="login-img"><i class="icon_lock_alt"></i></p>
<div class="form-group">
<span class="input-group-addon"><i class="icon_profile"></i></span>
<input type="text" class="form-control" placeholder="Firstname" autofocus name="firstname">
</div>
<div class="form-group">
<span class="input-group-addon"><i class="icon_profile"></i></span>
<input type="text" class="form-control" placeholder="Lastname" autofocus name="lastname">
</div>
<div class="form-group">
<span class="input-group-addon"><i class="icon_profile"></i></span>
<input type="text" class="form-control" placeholder="Username" autofocus name="username">
</div>
<div class="form-group">
<span class="input-group-addon"><i class="icon_profile"></i></span>
<input type="text" class="form-control" placeholder="Email" name="email" autofocus>
</div>
<div class="form-group">
<span class="input-group-addon"><i class="icon_key_alt"></i></span>
<input type="password" class="form-control" placeholder="Password" id="password" name="password">
</div>
<div class="form-group">
<span class="input-group-addon"><i class="icon_key_alt"></i></span>
<input type="password" class="form-control" placeholder="Confirm Password" id="confirmPassword">
</div>
<label class="checkbox">
<span><a th:href="#{/login}"> Login Page</a></span>
<span class="pull-right"> <a th:href="#{/forgot-password}"> Forgot Password?</a></span>
</label>
<button class="btn btn-primary btn-lg btn-block" type="submit">Register</button>
</div>
</form>
So far I have tried giving each new user upon registration a default Role of User or ROLE_USER which is an enum with the following code
Role role = new Role();
role.setName(RoleName.ROLE_USER);
roleRepository.save(role);
user.setRoles(new HashSet<>(Collections.singletonList(role)));
userService.save(user);
When I use this method, a Role Id and RoleName is stored in the Role table (Which I don't think should be):
I want the User Id and the Role name to be stored in the user_role table.
I'm trying to ask the question properly but I'm new to Authorization in Spring Boot and don't yet full understand how it works.
Related
I dont know how fix this error, i saw some articles about that but nothing works.
Error
Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errorsField error in object 'pupil' on field 'curse': rejected value [ModelCurse(id=2, name=PHP, division=2da, pupilList=[], teacherList=[])];
[Failed to convert property value of type 'java.lang.String' to required type 'com.example.administrator.administrator.model.ModelCurse' for property 'curse';
HTML
<form th:action="#{/pupilController/add}" th:object="${pupil}" method="post">
<div class="form-row">
<div class="form-group col-md-6">
<label>Name</label>
<input type="text" class="form-control" th:field="*{name}" placeholder="Name">
</div>
<div class="form-group col-md-6">
<label>Last Name</label>
<input type="text" class="form-control" th:field="*{lastName}" placeholder="Last Name">
</div>
<div class="form-group col-md-6">
<label>Age</label>
<input type="number" class="form-control" th:field="*{age}" placeholder="Age">
</div>
<div class="form-group col-md-6">
<label>Phone Number</label>
<input type="text" class="form-control" th:field="*{phoneNumber}" placeholder="Phone Number">
</div>
<div class="form-group col-md-6">
<select th:field="*{curse}" class="form-control">
<option th:each="curso : ${curses}"
th:value="${curso}"
th:text="${curso.name}"></option>
</select>
</div>
</div>
<input type="submit" name="btnInsert" class="btn btn-primary" value=Añadir>
</form>
Controller
#GetMapping("/addPupil")
public ModelAndView login(Model model){
ModelAndView mav = new ModelAndView("addpupil");
List<ModelCurse> modelCurses = curseService.getAllCurses();
mav.addObject("pupil",new ModelPupil());
mav.addObject("curses",modelCurses);
return mav;
}
#PostMapping("/add")
public RedirectView addPupil(#ModelAttribute("pupil")ModelPupil modelPupil){
pupilService.addPupil(modelPupil);
return new RedirectView("/pupilController/pupilList");
}
DTO
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class ModelCurse {
private long id;
private String name;
private String division;
private List<ModelPupil> pupilList;
private List<ModelTeacher> teacherList;
}
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class ModelPupil {
private long id;
private String name;
private String lastName;
private int age;
private int phoneNumber;
private ModelCurse curse;
}
When we submit the form, the value of selected option is sent as Text or String in HTTP POST. So, if you are expecting that Spring would automatically convert that String value of curse to object of type ModelCurse, then your assumption is wrong. But Spring does provide us a way to do it using Java beans PropertyEditorSupport. Here you can extend this class, and provide your own implementation on how to convert that String to the required object.
I have a booking entity and another entity is guest. the relation between them onetomany.
booking entity:
#Entity
public class Booking {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String nameInit;
private String firstName;
private String lastName;
private String email;
private String numberInit;
private String number;
private boolean status;
#JsonIgnore
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Guest>guests;
Guest entity:
#Entity
public class Guest{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String nameInit;
private String name;
private String age;
private String gender;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "booking_id")
private Booking booking;
thymeleaf form:
<div class="guest-inputs-wrapper" th:each="item, stat : *{guests}">
<div class="row">
<div class="col-md-1">
<div class="input-field col s12">
<div class="guest-number">
Guest <span class="number">1</span>
</div>
</div>
</div>
<div class="col-md-2">
<div class="input-field col s12">
<select name="nameInit" th:field="*{guests[__${stat.index}__].nameInit}" id="g-title-1">
<option value="" disabled selected>Title</option>
<option value="1">Mr.</option>
<option value="2">Mrs.</option>
<option value="3">Ms.</option>
</select>
</div>
</div>
<div class="col-md-4">
<div class="input-field col s12">
<input type="text" class="validate" name="name" th:field="*{guests[__${stat.index}__].name}" id="g-fullname-1" required>
<label>Full Name</label>
</div>
</div>
<div class="col-md-1">
<div class="input-field col s12">
<input type="text" class="validate" name="age" th:field="*{guests[__${stat.index}__].age}" id="g-age-1" required>
<label>Age</label>
</div>
</div>
<div class="col-md-3">
<div class="input-field col s12">
<div class="custom-radio">
<input type="radio" id="m-gender-1" name="gender" th:field="*{guests[__${stat.index}__].gender}" value="Male" checked>
<label for="m-gender-1">Male</label>
</div>
<div class="custom-radio">
<input type="radio" id="f-gender-1" name="gender" th:field="*{guests[__${stat.index}__].gender}" Value="Female">
<label for="f-gender-1">Female</label>
</div>
</div>
</div>
Controller:
#GetMapping("/booking/room/{roomId}")
public String bookingPage(Model model, #PathVariable Long roomId, HttpSession session){
Booking booking = new Booking();
booking.getGuests().add(new Guest());
model.addAttribute("booking", booking);
return "web/cart";
}
#PostMapping("/room/save-booking")
public String createBooking(Model model,#Valid Booking booking, BindingResult result, HttpSession session){
if (result.hasErrors()){
model.addAttribute("error", true);
return null;
}
hotelBookingService.save(booking);
guestService.saveAll(booking.getGuests());
model.addAttribute("success", true);
return "redirect:/main";
}
after submitting only one guest information is saved in db with booking information. but i want a list of guest. where is my worng i can't find. why form do not pass list of guest ? please help
For starters, within your th:each loop you have element ids with are not unique and don't use the stat index, for example g-title-1.
Secondly, the same goes for the name on each input. The th:each loop is an abstraction for document generation. As far as the browser is concerned, there is no "list" associated with the loop. What does that mean? When it comes time to submit, you will have a bunch of duplicate names as well, which will cause problems.
You could refactor your code/script to save one guest to a booking at a time, or you could use javascript to build a FormData object that grabs data from the inputs and keeps the lists like you want. I'm sure there are other solutions as well.
I am working on MVC Java project and am using Spring Boot with ThymeLeaf. The issue I have is, that when creating a Task, I want to select and save a User, that will be assign to the task. I managed to do the selection correctly, but am unable to save the selected user. It always saves without the User assigned.
Here is my Task entity class:
#Entity
public class Task extends BaseEntity {
private String name;
private String description;
private Date dateOfCreation;
#DateTimeFormat(pattern = "yyyy-MM-dd")
private Date dueDate;
#ManyToOne
private User assignee;
public Task() {
this.dateOfCreation = new Date(System.currentTimeMillis());
}
}
My User entity is as follows:
#Entity
public class User extends BaseEntity {
private String username;
private String password;
#OneToMany(mappedBy = "assignee")
private List<Task> assignedTasks;
}
Where BaseEntity contains
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Version
private Integer version;
My controller function, that is responsible for my taskform.html is:
#RequestMapping("/new")
public String newTask(Model model){
model.addAttribute("task", new Task());
model.addAttribute("users", userService.getAllUsers());
return "task/taskform";
}
And my taskform.html looks like this:
<form class="form-horizontal" th:object="${task}" th:action="#{/task}" method="post">
<input type="hidden" th:field="*{id}"/>
<input type="hidden" th:field="*{version}"/>
<div class="form-group">
<label class="col-sm-2 control-label">Name:</label>
<div class="col-sm-10">
<input type="text" class="form-control" th:field="*{name}"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Description:</label>
<div class="col-sm-10">
<input type="text" class="form-control" th:field="*{description}"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Description:</label>
<div class="col-sm-10">
<input type="date" th:value="*{dueDate}" th:field="*{dueDate}" id="dueDate" />
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Select user:</label>
<div class="col-sm-10">
<select class="form-control">
<option th:field="*{assignee}" th:each="assignee : ${users}" th:value="${assignee}" th:text="${assignee.username}">
</option>
</select>
</div>
</div>
<div class="row">
<button type="submit" class="btn btn-default">Submit</button>
</div>
I am trying to pass the object from users list as the value, but it does not work Any idea, how to do it?
Showing the users works as expected:
Working frontend
Null is being assigned to assignee column, where the user should be assigned.
Null in H2 Database
EDIT
Here is my POST method from Task Controller:
#RequestMapping(method = RequestMethod.POST)
public String saveOrUpdate(Task task){
taskService.persistTask(task);
return "redirect:/task/list";
}
Hello I want to update User entity, so in the form edit.html i'm using thymeleaf:
<form action="#" th:action="#{/{id}/edit(id=${user.id})}"
th:object="${user}" method="post">
<div class="md-form">
<input type="text" th:field="*{firstName}"
th:value="${user.firstName}" name="firstName" id="firstName"
class="form-control" /> <label class="active" for="firstName">Prénom</label>
</div>
<div class="md-form">
<input type="text" th:field="*{lastName}"
th:value="${user.lastName}" name="lastName" id="lastName"
class="form-control" /> <label class="active" for="lastName">Nom</label>
</div>
<div class="md-form">
<input type="text" th:field="*{email}" th:value="${user.email}"
name="email" id="email" class="form-control" /> <label
class="active" for="email">email</label>
</div>
<div class="md-form">
<input type="text" th:field="*{password}"
th:value="${user.password}" name="password" id="password"
class="form-control" /> <label class="active" for="password">mot
de passe</label>
</div>
<div class="md-form">
<input type="text" th:field="*{occupation}"
th:value="${user.occupation}" name="occupation" id="occupation"
class="form-control" /> <label class="active" for="occupation">profession</label>
</div>
<div class="md-form">
<input type="text" th:field="*{ville}" th:value="${user.ville}"
name="ville" id="ville" class="form-control" /> <label
class="active" for="ville">ville</label>
</div>
<div>
<input class="btn btn-sm btn-primary waves-effect btn-rounded"
type="submit" value="modifier" />
</div>
</form>
User.java:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String userName;
private int age;
private String ville;
private String email;
private String password;
private String phone;
private String company;
private String occupation;
private String img_profil;
#ManyToMany(mappedBy = "users", cascade = { CascadeType.ALL })
private Set<Discussion> discussion;
UserController.java
#GetMapping("/{userName}/edit")
public String editUser(#PathVariable String userName, Model model) {
User user = ur.findByUserName(userName).get(0);
model.addAttribute(user);
return "edit";
}
#PostMapping("/{id}/edit")
public String updateUser(#ModelAttribute("user") User user, #PathVariable("id") Long id) {
user = ur.findOne(id);
ur.save(user);
return "redirect:/find/" + user.getUserName();
}
UserRepository.java
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
List<User> findByUserName(String userName);
}
PROBLEM is in the console I do not see update request (generated by Hibernate), nothing is changed on the database.
Make the following change in UserController.java:
#PostMapping("/{id}/edit")
public String updateUser(
#ModelAttribute("user") User user,
#PathVariable("id") Long id)
{
//user = ur.findOne(id);
user.setId(id);
ur.save(user);
return "redirect:/find/" + user.getUserName();
}
In my current spring project, one of the renderized forms is the follow:
<form id="Usuario" class="form" role="form" method="post" action="/caixa/Usuario/insert">
<li class="list-group-item">
<field-box>
<label>login</label>
<input type="text" name="login" id="login" class="form-control" />
</field-box>
</li>
<li class="list-group-item">
<field-box>
<label>senha</label>
<input type="password" name="senha" id="senha" class="form-control" />
</field-box>
</li>
<li class="list-group-item">
<field-box>
<label>nome</label>
<input type="text" name="nome" id="nome" class="form-control" />
</field-box>
</li>
<li class="list-group-item">
<field-box>
<label>sobrenome</label>
<input type="text" name="sobrenome" id="sobrenome" class="form-control" />
</field-box>
</li>
<li class="list-group-item">
<field-box>
<label>email</label>
<input type="email" name="email" id="email" class="form-control" />
</field-box>
</li>
<li class="list-group-item">
<field-box>
<label>role</label>
<select multiple="multiple" name="role.id" id="role" class="form-control option" data-classe="Role">
</select>
</field-box>
</li>
<button type="submit" id="cadastrar" class="btn btn-primary">Cadastrar</button>
<div class="alert alert-success alert-dismissible" id="yes" role="alert" style="display: none;">
<button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
Cadastro efetuado com sucesso
</div>
<div class="alert alert-danger alert-dismissible" id="not" role="alert" style="display: none;">
<button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
<span class="text"></span>
</div>
</form>
the data submitted through this form is related to this class:
#Entity
#Table(name = "usuario")
public class Usuario {
#Id
#Column(name = "id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#Column(name = "login")
#Input
private String login;
#Column(name = "senha")
#Input(type = "password")
private String senha;
#Column(name = "nome")
#Input
private String nome;
#Column(name = "sobrenome")
#Input
private String sobrenome;
#Column(name = "email")
#Input(type = "email")
private String email;
#ManyToMany
#JoinTable(name="role_members", joinColumns={#JoinColumn(name="fk_user")}, inverseJoinColumns={#JoinColumn(name="fk_role")})
#LazyCollection(LazyCollectionOption.FALSE)
#Select(classe = Role.class)
private List<Role> role;
}
the submission process is handled by this jquery code:
$(document).on("submit", "form.form", function (event) {
event.preventDefault();
var $form = $( this ), url = $form.attr( "action" );
var posting = $.post( url, $(this).serialize() );
posting.done(function( data ) {
if(data == "") {
$("#yes").show();
} else {
var $temp = $('<div/>', {html:data});
$("#not").find(".text").html( $temp.remove('head').html() );
$("#not").show();
}
$("form.form").each(function(){
this.reset();
});
});
});
and by this methods in my controller, service and dao classes:
#RequestMapping(value = "insert")
#ResponseBody
public void insert(#ModelAttribute("object") E object) throws Exception {
try {
serv.insert(object);
} catch (Exception e) {
throw e;
}
}
public void insert(E object) {
dao.persist(object);
}
#Transactional
public void persist(E object) {
sessionFactory.getCurrentSession().persist(object);
}
but when I run the application and open this view in my browser, and submit this form, the data is saved in the database without the data from the <select> field. I check the browser console (in the firefox), and verify all the fields are being submitted to server, but the field role is the only one which it's not stored.
Anyone can see what's the pronlem here?
I think problem in column names you have given in #JoinTable(name="role_members", joinColumns={#JoinColumn(name="fk_user")}, inverseJoinColumns={#JoinColumn(name="fk_role")})
column name fk_user is not present in your entity class. If you want to use different name then use referencedColumnName attribute and give actual field name.
eg. We are using id from both entities then after change it will be like
#JoinTable(name="role_members", joinColumns={#JoinColumn(name="fk_user", referencedColumnName="id")}, inverseJoinColumns={#JoinColumn(name="fk_role",referencedColumnName="id")})
Hopefully it will work.