Good day again. Post in continuation of the publication
“Spring + Java EE + Persistence, without XML. Part 1 " .
1. Introduction
1.1 Load the project
If you want to start from this part, or there is no project left in the previous part, you can download it from github.
The scheme is simple:
- Enter from the console to the folder with the IDEA projects
- git clone github.com/MaxPovver/ForHabrahabr.git
- cd ForHabrahabr /
- git checkout withauth
- Done, now you can load the project into the studio as described in the first part.
1.2 What will we do in this part?
In this section, we will look at how many-to-many relationships are stored at the level of entity objects;
let's finish allocation of the rights to users;
let's make the simplest REST-controller;
we will register new users (admin only);
and all without XML.
2 Fix the distribution of roles between users
As someone probably noticed, at the moment, instead of obtaining the rights of users from the database, we have attached a creepy-looking stub, which is not at all flexible.
What is needed to fix this? Enter such an entity as a “role”. One user can have several roles, and many users can have the same role. Those. classic
many-to-many .
')
2.1 Working with the base
To begin with, let's get the roles (id, role) sign, not forgetting to specify that the values in the role should be unique. Also create an auxiliary table users_roles (user_id, role_id). And immediately create basic roles ADMIN, USER, GUEST. Well and at once we will create for the connecting page foreign keys user_id -> user.id, role_id -> role.id. All this can be done immediately by running the following script:
2.2 Write the code
First, let's go to Application.class and clarify the location of the Jpa repositories:
@EnableJpaRepositories(basePackages = {"habraspring.repositories"})
Now let's create in entities / Role class a mapped entry in the database:
Role.java @Entity @Table(name="roles") public class Role { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String role; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } protected Role(){} public Role(String name) { role = name; } }
Ok, now how do you associate them with the User class? To do this, just add the following code to User:
@ManyToMany @JoinTable(name = "users_roles", joinColumns = {@JoinColumn(name = "user_id")}, inverseJoinColumns = @JoinColumn(name = "role_id")) private Set<Role> roles; public Set<Role> getRoles() { return roles; } public void setRoles(Set<Role> roles) { this.roles = roles; }
Here we specify the connection table of two entities, which column in this table corresponds to our entity (joinColumns = {@JoinColumn (name = "user_id")}), and which one - to link the entity: inverseJoinColumns = @JoinColumn (name = "role_id").
In the Role class, it's easier:
@ManyToMany(mappedBy = "roles") Set<User> users; public Set<User> getUsers() { return users; } public void setUsers(Set<User> users) { this.users = users; }
In order for Spring to arrange our role as Authorithy, you need to implement the GrantedAuthority interface in the Role class:
public class Role implements GrantedAuthority { ... @Override public String getAuthority() { return getRole(); } }
Done! Now we can rewrite MySQLUserDetailsService:
@Service public class MySQLUserDetailsService implements UserDetailsService { @Autowired UsersRepository users; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserDetails loadedUser; try { User client = users.findByUsername(username); loadedUser = new org.springframework.security.core.userdetails.User( client.getUsername(), client.getPassword(), client.getRoles()); } catch (Exception repositoryProblem) { throw new InternalAuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem); } return loadedUser; } }
Now we load the authorities via user.getRoles (), and not the garbage class, so the user will receive only those roles that are assigned to him in the database.
At the moment we do not use it, but a little later you will see how you can differentiate access depending on the role of the user.
So, we will create a simple rest controller for working with users, which is accessible only to users with admin rights.
3. Create a UsersController controller
To begin, create a folder controllers, and in it - UsersContoller:
package habraspring.controllers; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/users") public class UsersController { }
So, firstly, we noted that this is a rest controller, which means that it will return not full-fledged html pages, but raw data, by default in Json format. @RequestMapping ("/ users") - this means that it will be triggered by a request from a user of the form "
yoursite / users ". But we are going to steer the users here, while any authorized user can open this controller! So add the magic line:
@PreAuthorize("hasRole('ADMIN')")
Inside the controller, the rights check is now no longer needed, only the one who already has the ADMIN role can get there.
Now add the output of all users:
@Autowired UsersRepository users; @RequestMapping(method = RequestMethod.GET) public List<User> getUsers() { List<User> result = new ArrayList<>(); users.findAll().forEach(result::add); return result; }
Try to go to the address
http: // localhost: 8080 / users if everything was done correctly, there will be an error 403. And now add the admin rights to the user, for this you need to add (1.1) in users_roles, if you have the same user id and ADMIN roles like mine. After adding a new value to the table, go to
http: // localhost: 8080 / secret and click there logout to restart again (to upload new rights). Now we try to open
http: // localhost: 8080 / users . It should output something like this:
Many letters[{"Id": 1, "username": "user", "password": "user", "roles": [{"id": 1, "role": "ADMIN", "users": [{ "Id": 1, "username": "user", "password": "user", "roles": [{"id": 1, "role": "ADMIN", "users": [{"id ": 1," username ":" user "," password ":" user "," roles ": [{" id ": 1," role ":" ADMIN "," users ": [{" id ": 1, “username”: “user”, “password”: “user”, “roles”: [{“id”: 1, “role”: “ADMIN”, “users”: [{"id”: 1, “Username”: “user”, “password”: “user”, “roles”: [{“id”: 1, “role”: “ADMIN”, “users”: [{“id”: 1, “username ":" User "," password ":" user "," roles ": [{" id ": 1," role ":" ADMIN "," users ": [{" id ": 1," username ": “User”, “password”: “user”, “roles”: [{“id”: 1, “role”: “ADMIN”, “users”: [{“id”: 1, “username”: “user "," Password ":" user "," roles ": [{" id ": 1," role ":" ADMIN "," users ": [{" id ": 1," username ":" user ", "Password": "user", "roles": [{"id": 1, "role": "ADMIN", "users": [{"id": 1, "username": "user", "password ":" User ", "Roles": [{"id": 1, "role": "ADMIN", "users": [{"id": 1, "username": "user", "password": "user", "roles ": [{" Id ": 1," role ":" ADMIN "," users ": [{" id ": 1," username ":" user "," password ":" user "," roles ": [{"Id": 1, "role": "ADMIN", "users": [{"id": 1, "username": "user", "password": "user", "roles": [{ "Id": 1, "role": "ADMIN", "users": [{"id": 1, "username": "user", "password": "user", "roles": [{"id ": 1," role ":" ADMIN "," users ": [{" id ": 1," username ":" user "," password ":" user "," roles ": [{" id ": 1, "role": "ADMIN", "users":
What's the matter, do you have just one user? Everything is simple - in the automatic output of the object as json, all its fields are displayed, as a result, if there is one in one of them, it turns into an infinite loop. To correct the annoying mistake, add the JsonIgnore annotation to the users field in the Role class:
import com.fasterxml.jackson.annotation.JsonIgnore; ... @JsonIgnore @ManyToMany(mappedBy = "roles") Set<User> users;
Restart the application, restart to
http: // localhost: 8080 / users and see the normal output:
[{"id":1,"username":"user","password":"user","roles":[{"id":1,"role":"ADMIN","authority":"ADMIN"}]}]
Ok, now let's add a couple more methods and a “user-friendly” interface for creating new users.
3.1 Implement the creation of a new user
To do this, we first implement the method that, upon a POST request, adds a new entity:
@RequestMapping(method = RequestMethod.POST) public User addUser(String username, String password, String password_confirm) {
Already not bad, now you can add a post from a third-party api, and how to do it in our application itself?
To do this, create an embedded route / add, which will work on request GET / users / add:
@RequestMapping(value = "/add",method = RequestMethod.GET) public ModelAndView getUserForm() { return new ModelAndView("add"); }
And add resources to the resources / templates / add.html:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>Add user page</title> </head> <body> <form th:action="@{/users}" method="post"> <div><label> User Name : <input type="text" name="username"/> </label></div> <div><label> Password: <input type="password" name="password"/> </label></div> <div><label> Password confirm: <input type="password" name="password_confirm"/> </label></div> <div><input type="submit" value="Sign In"/></div> </form> </body> </html>
The form fields will be directly converted into the parameters of the function of creating a user with the same name, it is extremely convenient, in my opinion.
We cannot directly display the template in @RestController, so we use for this the auxiliary class ModelAndView in which it is enough to pass the name of the view (without .html).
Done, now you can create new users directly on the site using rest or using the form in / users / add.
3.2 We add work with a specific user
It remains to add two simplest methods that issue / delete a specific user (request type GET / DELETE / users / 2):
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE) public void delete(@PathVariable("id") Long id) { users.delete(id); } @RequestMapping(value = "/{id}", method = RequestMethod.GET) public User getUser(@PathVariable("id") Long id) { return users.findOne(id); }
These methods are self-documenting in my opinion. The PathVariable annotation (“value”) pulls from the query what is in it instead of the {value} pattern (in our case, a digit).
4. For those who want to launch a finished project.
The
https://github.com/MaxPovver/ForHabrahabr/tree/withcontroller branch contains everything you need, but you will first need to run import_me.sql in your database. (After downloading / cloning, do not forget to checkout)
5. Conclusion
I wanted to fit much more into this article, in my opinion it is already slightly overloaded, but I haven’t even reached half of it. So testing, OneToMany and a few more interesting things will have to be left for the next article, if, of course, there is interest in the topic.
Good luck!