Good morning everyone, I have an application in Spring Boot in which I implemented Spring Security. I have managed to log in and have access to all the routes once logged in, and also restriction to all routes when the session has not been started. In my system I have a list of Users - > Roles - > Roles_Menu and Menu as seen in the following image:
I would like to know if it is possible to grant permissions to each role only to the routes that are registered in the database.
Although in the implementation of Spring Security a List of routes is included for each user, this does not make any restriction to the routes whose access is not reflected in said list.
Here is the detail of my code:
This is my User class
package com.escuelaapp.EscuelaApp.entity;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
*
* @author ALEJO
*/
@Entity
@Table(name = "user")
public class User implements UserDetails{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "id")
private Integer id;
@Basic(optional = false)
@Column(name = "document")
private long document;
@Basic(optional = false)
@Column(name = "first_name")
private String firstName;
@Column(name = "second_name")
private String secondName;
@Basic(optional = false)
@Column(name = "surname")
private String surname;
@Column(name = "second_surname")
private String secondSurname;
@Basic(optional = false)
@Column(name = "username")
@NotNull
@Size(min=2,max=10)
private String userName;
@Column(name = "email")
private String email;
@Basic(optional = false)
@Column(name = "password")
@NotNull
private String password;
@Basic(optional = false)
@Column(name = "state")
private short state;
@JoinColumn(name="profile_id")
@ManyToOne(targetEntity=Profile.class,fetch=FetchType.EAGER)
private Profile profile;
public User() {
}
public User(Integer id) {
this.id = id;
}
public User(Integer id, long document, String firstName, String surname, String username, String password,
short state) {
this.id = id;
this.document = document;
this.firstName = firstName;
this.surname = surname;
this.userName = username;
this.password = password;
this.state = state;
}
public User(@NotNull @Size(min = 2, max = 10) String userName, @NotNull String password) {
super();
this.userName = userName;
this.password = password;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public long getDocument() {
return document;
}
public void setDocument(long document) {
this.document = document;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getSecondName() {
return secondName;
}
public void setSecondName(String secondName) {
this.secondName = secondName;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public String getSecondSurname() {
return secondSurname;
}
public void setSecondSurname(String secondSurname) {
this.secondSurname = secondSurname;
}
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;
}
@JsonIgnore
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public short getState() {
return state;
}
public void setState(short state) {
this.state = state;
}
public Profile getProfile() {
return profile;
}
public void setProfile(Profile profile) {
this.profile = profile;
}
@Override
public String toString() {
return "User [id=" + id + ", document=" + document + ", firstName=" + firstName + ", secondName=" + secondName
+ ", surname=" + surname + ", secondSurname=" + secondSurname + ", userName=" + userName + ", email="
+ email + ", password=" + password + ", state=" + state + ", profile=" + profile + "]";
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getUsername() {
return this.getUsername();
}
@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return true;
}
}
This is the Profile Class (or ROL)
package com.escuelaapp.EscuelaApp.entity;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
@Entity
@Table(name="profile")
public class Profile {
@Id
@Column(name="id")
private int id;
@Column(name="name")
private String name;
@OneToMany(mappedBy="profile")
private List<User> users;
@OneToMany(fetch=FetchType.EAGER,mappedBy="profileId")
private List<ProfileMenu> profileMenus;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Transient
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
@Transient
public List<ProfileMenu> getProfileMenus() {
return profileMenus;
}
public void setProfileMenus(List<ProfileMenu> profileMenus) {
this.profileMenus = profileMenus;
}
@Override
public String toString() {
return "Profile [id=" + id + ", name=" + name + ", users=" + users + "]";
}
}
This is the Menu class
package com.escuelaapp.EscuelaApp.entity;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
@Entity
@Table(name = "menu")
public class Menu {
@Id
@Column(name = "id")
private int id;
@Column(name = "name")
private String name;
@Column(name = "url")
private String url;
@Column(name = "icon")
private String icon;
@OneToMany( mappedBy = "menus")
private List<ProfileMenu> profileMenu;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
@Transient
public List<ProfileMenu> getProfileMenu() {
return profileMenu;
}
public void setProfileMenu(List<ProfileMenu> profileMenu) {
this.profileMenu = profileMenu;
}
@Override
public String toString() {
return "Menu [id=" + id + ", name=" + name + ", url=" + url + ", icon=" + icon + ", profileMenu=" + profileMenu
+ "]";
}
}
And this is the class that relates the ROLE with the Menu
package com.escuelaapp.EscuelaApp.entity;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity
@Table(name="profile_menu")
public class ProfileMenu {
@Id
@Column(name="id")
private int id;
@ManyToOne(targetEntity=Profile.class)
@JoinColumn(name="profile_id")
private Profile profileId;
@ManyToOne(fetch=FetchType.EAGER,targetEntity=Menu.class)
@JoinColumn(name="menu_id")
private Menu menus;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Profile getProfileId() {
return profileId;
}
public void setProfileId(Profile profileId) {
this.profileId = profileId;
}
public Menu getMenus() {
return menus;
}
public void setMenus(Menu menus) {
this.menus = menus;
}
@Override
public String toString() {
return "ProfileMenu [id=" + id + ", profileId=" + profileId + ", menus=" + menus + "]";
}
}
This is the configuration of my User table with Spring Security
package com.escuelaapp.EscuelaApp.service.impl;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.escuelaapp.EscuelaApp.entity.User;
import com.escuelaapp.EscuelaApp.repository.UserRepository;
@Service("UserServiceSecurity")
public class UserServiceSecurity implements UserDetailsService {
@Autowired
@Qualifier("UserRepository")
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUserName(username);
List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
user.getProfile().getProfileMenus().stream().forEach((x) -> {
auths.add(new SimpleGrantedAuthority(x.getMenus().getUrl()));
//System.out.println(x.getMenus().getUrl());
});
return userBuiler(user, auths);
}
private org.springframework.security.core.userdetails.User userBuiler(User user,
List<GrantedAuthority> authorities) {
return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassword(),
authorities);
}
}
This is the configuration of Spring Security by http, according to the official documentation
package com.escuelaapp.EscuelaApp.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("UserServiceSecurity")
private UserDetailsService userService;
@Autowired
public void ConfigureGlobal(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/inicio").permitAll()
.antMatchers("/bower_components/**","/imgs/**","/css/**","/js/**","/fonts/**","/favicon.ico").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.loginPage("/inicio")
.failureUrl("/inicio?error")
.loginProcessingUrl("/loginValidation")
.usernameParameter("username")
.passwordParameter("password")
.defaultSuccessUrl("/dashboard")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/inicio?logout")
.permitAll()
;
}
}
The information in Database is as follows:
select U.username, P.name, M.url from user U
join profile P on P.id=U.profile_id
join profile_menu PM on PM.profile_id=P.id
join menu M on M.id=PM.menu_id
Finally in the controller I have the following routes, and I want for example that the user has no access to the non-Administrator route or to any other that is not registered in the database
package com.escuelaapp.EscuelaApp.controller;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import com.escuelaapp.EscuelaApp.configuration.EscuelappProperties;
import com.escuelaapp.EscuelaApp.entity.User;
import com.escuelaapp.EscuelaApp.model.CustomUserDetail;
import com.escuelaapp.EscuelaApp.model.LoggedUser;
@Controller
public class LoginController {
private static final Log LOGGER = LogFactory.getLog(LoginController.class);
@Autowired
EscuelappProperties properties;
@GetMapping({"/inicio","/"})
public String index(Model model,@RequestParam(required=false) String error, @RequestParam(required=false) String logout) {
properties.initSliders();
model.addAttribute("user", new User());
model.addAttribute("prop", properties);
model.addAttribute("error", error);
model.addAttribute("logout", logout);
return "index";
//return "redirect:/login";
}
@GetMapping("/dashboard")
public ModelAndView dashboard() {
ModelAndView mav = new ModelAndView("dashboard");
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
//LoggedUser principal = (LoggedUser)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
//CustomUserDetail principal = (CustomUserDetail) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
System.out.println(SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString());
//User user = principal.getUser();
//mav.addObject("user", user);
mav.addObject("username", user.getUsername());
return mav;
}
@GetMapping("/usuarios")
public @ResponseBody String usuarios() {
return "Tiene acceso a la ruta: usuarios";
}
@GetMapping("/permisos")
public @ResponseBody String permisos() {
return "Tiene acceso a la ruta: permisos";
}
@GetMapping("/noAdministrador")
public @ResponseBody String noAdmin() {
return "Tiene acceso a la ruta: no Admin";
}
}
I appreciate your collaboration in relation to the information that you can provide me to understand how Spring Security works or if this validation of permissions by profiles or roles I have to do it manually.