Username | Roles |
patrick |
USER
|
alex
|
CREATOR |
john |
EDITOR
|
namhm
|
CREATOR, EDITOR |
admin
|
ADMIN |
CREATE TABLE `users` ( `user_id` int(11) NOT NULL AUTO_INCREMENT, `email` varchar(45) NOT NULL, `full_name` varchar(45) NOT NULL, `password` varchar(64) NOT NULL, `enabled` tinyint(4) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `email_UNIQUE` (`email`) ); CREATE TABLE `roles` ( `role_id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(45) NOT NULL, PRIMARY KEY (`id`) ); CREATE TABLE `users_roles` ( `user_id` int(11) NOT NULL, `role_id` int(11) NOT NULL, KEY `user_fk_idx` (`user_id`), KEY `role_fk_idx` (`role_id`), CONSTRAINT `role_fk` FOREIGN KEY (`role_id`) REFERENCES `roles` (`role_id`), CONSTRAINT `user_fk` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) );
INSERT INTO `roles` (`name`) VALUES ('USER'); INSERT INTO `roles` (`name`) VALUES ('CREATOR'); INSERT INTO `roles` (`name`) VALUES ('EDITOR'); INSERT INTO `roles` (`name`) VALUES ('ADMIN');Run the following SQL statements to create 5 users in the users table:
INSERT INTO `users` (`username`, `password`, `enabled`) VALUES ('patrick', '$2a$10$cTUErxQqYVyU2qmQGIktpup5chLEdhD2zpzNEyYqmxrHHJbSNDOG.', '1'); INSERT INTO `users` (`username`, `password`, `enabled`) VALUES ('alex', '$2a$10$.tP2OH3dEG0zms7vek4ated5AiQ.EGkncii0OpCcGq4bckS9NOULu', '1'); INSERT INTO `users` (`username`, `password`, `enabled`) VALUES ('john', '$2a$10$E2UPv7arXmp3q0LzVzCBNeb4B4AtbTAGjkefVDnSztOwE7Gix6kea', '1'); INSERT INTO `users` (`username`, `password`, `enabled`) VALUES ('namhm', '$2a$10$GQT8bfLMaLYwlyUysnGwDu6HMB5G.tin5MKT/uduv2Nez0.DmhnOq', '1'); INSERT INTO `users` (`username`, `password`, `enabled`) VALUES ('admin', '$2a$10$IqTJTjn39IU5.7sSCDQxzu3xug6z/LPU6IF0azE/8CkHCwYEnwBX.', '1');Note that the passwords are encoded in BCrypt format and same as usernames.And execute the following script to assign permissions to users based on the table above:
INSERT INTO `users_roles` (`user_id`, `role_id`) VALUES (1, 1); -- user patrick has role USER INSERT INTO `users_roles` (`user_id`, `role_id`) VALUES (2, 2); -- user alex has role CREATOR INSERT INTO `users_roles` (`user_id`, `role_id`) VALUES (3, 3); -- user john has role EDITOR INSERT INTO `users_roles` (`user_id`, `role_id`) VALUES (4, 2); -- user namhm has role CREATOR INSERT INTO `users_roles` (`user_id`, `role_id`) VALUES (4, 3); -- user namhm has role EDITOR INSERT INTO `users_roles` (`user_id`, `role_id`) VALUES (5, 4); -- user admin has role ADMINSo that’s the setup for the database.
spring.jpa.hibernate.ddl-auto=none spring.datasource.url=jdbc:mysql://localhost:3306/sales spring.datasource.username=root spring.datasource.password=passwordModify URL, username and password matching your MySQL database.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> </dependency>Note that by default, if the Spring Security library present in the classpath, the users must login to use the application.
package net.codejava; import javax.persistence.*; @Entity @Table(name = "roles") public class Role { @Id @Column(name = "role_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String name; public Integer getId() { return id; } // remaining getters and setters }The second class is User:
package net.codejava; import java.util.*; import javax.persistence.*; @Entity @Table(name = "users") public class User { @Id @Column(name = "user_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; private boolean enabled; @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @JoinTable( name = "users_roles", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id") ) private Set<Role> roles = new HashSet<>(); public Long getId() { return id; } // remaining getters and setters are not shown for brevity }Here, you can see we use a Set of Roles in the User class to map a unidirectional many-to-many association from User to Role, e.g. user.roles. Refer to this tutorial for details about Hibernate many-to-many relationship mapping.
package net.codejava; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; public interface UserRepository extends CrudRepository<User, Long> { @Query("SELECT u FROM User u WHERE u.username = :username") public User getUserByUsername(@Param("username") String username); }This interface is a subtype of CrudRepository defined by Spring Data JPA so Spring will generate implementation class at runtime. We define the getUserByUsername() method annotated by a JPA query to be used by Spring Security for authentication. If you’re new to Spring Data JPA, kind check this quick start guide.
Spring Boot E-Commerce Ultimate Course
Code Real-life Shopping Website with Java and Spring Boot. Full-stack Development. Hands-on Practices. Job-ready Skills.
package net.codejava; import java.util.*; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; public class MyUserDetails implements UserDetails { private User user; public MyUserDetails(User user) { this.user = user; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { Set<Role> roles = user.getRoles(); List<SimpleGrantedAuthority> authorities = new ArrayList<>(); for (Role role : roles) { authorities.add(new SimpleGrantedAuthority(role.getName())); } return authorities; } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return user.isEnabled(); } }You can see, this class wraps an instance of User class and delegates almost overriding methods to the User’s ones. For authorization, pay attention to this method:
@Override public Collection<? extends GrantedAuthority> getAuthorities() { Set<Role> roles = user.getRoles(); List<SimpleGrantedAuthority> authorities = new ArrayList<>(); for (Role role : roles) { authorities.add(new SimpleGrantedAuthority(role.getName())); } return authorities; }This method returns a set of roles (authorities) to be used by Spring Security in the authorization process.Next, we need to code an implementation of the UserDetailsService interface defined by Spring Security with the following code:
package net.codejava; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.*; public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.getUserByUsername(username); if (user == null) { throw new UsernameNotFoundException("Could not find user"); } return new MyUserDetails(user); } }As you can see, this class makes use an instance of UserRepository interface in the loadUserByUsername() method which will be invoked by Spring Security when authenticating the users.
package net.codejava; import org.springframework.context.annotation.*; import org.springframework.security.authentication.dao.*; import org.springframework.security.config.annotation.authentication.builders.*; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.*; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean public UserDetailsService userDetailsService() { return new UserDetailsServiceImpl(); } @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService()); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(authenticationProvider()); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/").hasAnyAuthority("USER", "CREATOR", "EDITOR", "ADMIN") .antMatchers("/new").hasAnyAuthority("ADMIN", "CREATOR") .antMatchers("/edit/**").hasAnyAuthority("ADMIN", "EDITOR") .antMatchers("/delete/**").hasAuthority("ADMIN") .anyRequest().authenticated() .and() .formLogin().permitAll() .and() .logout().permitAll() .and() .exceptionHandling().accessDeniedPage("/403") ; } }The first 4 methods are needed to configure an authentication provider that uses Spring Data JPA and Hibernate. And in the last method we configure HTTP Security for authentication and authorization. We also configure a custom URL for displaying access denied error in case the users do not have permission.
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">To display username of a logged-in user, use the following code:
<span sec:authentication="name">Username</span>To show all the roles (authorities/permissions/rights) of the current user, use the following code:
<span sec:authentication="principal.authorities">Roles</span>To show a section that is for only authenticated users, use the following code:
<div sec:authorize="isAuthenticated()"> Welcome <b><span sec:authentication="name">Username</span></b> <i><span sec:authentication="principal.authorities">Roles</span></i> </div>To display the Logout button:
<form th:action="@{/logout}" method="post"> <input type="submit" value="Logout" /> </form>Because only the users have role CREATOR or ADMIN can create new products, so write the following code to show the Create New Product link which is visible to only authorized users:
<div sec:authorize="hasAnyAuthority('CREATOR', 'ADMIN')"> <a href="/new">Create New Product</a> </div>The users with role EDITOR or ADMIN can see the links to edit/update products, thus the following code:
<div sec:authorize="hasAnyAuthority('ADMIN', 'EDITOR')"> <a th:href="/@{'/edit/' + ${product.id}}">Edit</a> </div>And only ADMIN users can see the link to delete products:
<div sec:authorize="hasAuthority('ADMIN')"> <a th:href="/@{'/delete/' + ${product.id}}">Delete</a> </div>That’s basically how to authorize users in the view layer using Thymeleaf with Spring Security. For more advanced authorization, you can learn in Spring Boot E-Commerce Ultimate Course.I recommend you to watch the following video to see the coding in action and how I test the login, logout and authorization: Conclusion:So far you have learned how to authorize users based on their roles using Spring Security and Thymeleaf for a Spring Boot Application. You see, Spring framework makes it easy and convenient to implement authorization with minimal effort. For reference, you can download the sample project attached below.You can also the the sample project on GitHub here.