Show pageBacklinksBack to top This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. ====== Spring Simple Username-Password Authentication with H2 ====== Basically, we have 6 steps: - Setup the project dependencies - Create User Role entities. User entity implement UserDetails - Create Your own UserDetailsService - Create WebSecurityConfig extends WebSecurityConfigurerAdapter - Create BCryptPasswordEncoder bean - Create some fake account in main class, and a controller for testing ===== Setup the project dependencies ===== Add those in your build.gradle. We need JPA, security, thymeleaf templates engine, web MVC, and h2 database. <code> dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' runtimeOnly 'com.h2database:h2' testImplementation('org.springframework.boot:spring-boot-starter-test') { exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' } testImplementation 'org.springframework.security:spring-security-test' } </code> ===== Create User Role entities. User entity implement UserDetails ===== Create a User entity that implements UserDetails. To make it simple, we return true for all UserDetails methods, except getAuthorities(). We also tells JPA to create a table that link user and role. We need to ''(fetch = FetchType.EAGER)'' for user and role relationship. <code> @Entity public class User implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, unique = true) private String username; private String password; @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "users_roles", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id")) private Collection<Role> roles; public User() {} public User(String username, String password, Collection<Role> roles) { this.username = username; this.password = password; this.setRoles(roles); } public Long getId() {return id;} public void setId(Long id) {this.id = id;} public String getUsername() {return username;} public void setUsername(String username) {this.username = username;} public String getPassword() {return password;} public void setPassword(String password) {this.password = password;} public Collection<Role> getRoles() {return roles;} public void setRoles(Collection<Role> roles) {this.roles = roles;} @Override public boolean isAccountNonExpired() {return true;} @Override public boolean isAccountNonLocked() {return true;} @Override public boolean isCredentialsNonExpired() {return true;} @Override public boolean isEnabled() {return true;} @Override public Collection<? extends GrantedAuthority> getAuthorities() { Collection<GrantedAuthority> grantedAuthorities = new HashSet<>(); for (Role role : getRoles()) { grantedAuthorities.add(new SimpleGrantedAuthority(role.getRole())); } return grantedAuthorities; } } </code> Create the role entity. <code> @Entity public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, unique = true) private String role; @ManyToMany(mappedBy = "roles") private List<User> users; 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;} public List<User> getUsers() {return users;} public void setUsers(List<User> users) {this.users = users;} } </code> Also need to create the crud repository for accessing the data in DB. <code> public interface UserRepository extends CrudRepository<User, Long> { User findUserById(Long id); User findUserByUsername(String username); } public interface RoleRepository extends CrudRepository<Role, Long> { Role findRoleByRole(String role); } </code> ===== Create Your own UserDetailsService ===== We will use this for getting user in auth config. <code> @Service public class H2UserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findUserByUsername(username); if (user != null) { return user; } throw new UsernameNotFoundException("User not found with username: " + username); } } </code> ===== Create WebSecurityConfig extends WebSecurityConfigurerAdapter ===== For debugging, we let the path /h2-console to be accessed without authentication. To make H2 console works, we do ''.frameOptions().disable()'' here. <code> @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; @Autowired private UserDetailsService h2UserDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().headers() .frameOptions().disable() .and() .authorizeRequests() .antMatchers("h2-console/**").permitAll() .anyRequest().hasRole("ADMIN") .and() .formLogin().permitAll() .and() .logout().permitAll(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(h2UserDetailsService).passwordEncoder(bCryptPasswordEncoder); } } </code> ===== Create BCryptPasswordEncoder Bean ===== Simply add the following code inside the main application class. <code> @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } </code> ===== Create some fake account in main class, and a controller for testing ===== Creating fake account on run. One way to do it is to have the main class to implements CommandLineRunner. <code> @Override public void run(String... args) throws Exception { Role roleUser = new Role(); roleUser.setRole("ROLE_USER"); Role roleAdmin = new Role(); roleAdmin.setRole("ROLE_ADMIN"); roleRepository.save(roleUser); roleRepository.save(roleAdmin); Set<Role> allRoles = new HashSet<>(); allRoles.add(roleAdmin); allRoles.add(roleUser); Set<Role> userRoles = new HashSet<>(); userRoles.add(roleUser); Set<Role> adminRole = new HashSet<>(); adminRole.add(roleAdmin); userRepository.save(new User("adminuser", bCryptPasswordEncoder.encode("password"), allRoles)); userRepository.save(new User("user", bCryptPasswordEncoder.encode("password"), userRoles)); userRepository.save(new User("admin", bCryptPasswordEncoder.encode("password"), adminRole)); } </code> A testing controller can be as simple as <code> @Controller public class HomeController { @GetMapping("/") public String home() { return "home/index"; } } </code> With an html file in resources/templates/ <code> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Home</title> </head> <body> <div> Hello! </div> </body> </html> </code> ===== To Test ===== Go to http://127.0.0.1:8080/, and it will redirect you to the /login site. Type admin:password to login. The system will return you back to the root page, and you will see Hello!. If you use the user:password, you will see a 403 error because we set ''.anyRequest().hasRole("ADMIN")'' in the WebSecurityConfig. springboot/spring_simple_username-password_authentication_with_h2.txt Last modified: 2020/06/09 16:30by chongtin