====== 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. 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' } ===== 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. @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 roles; public User() {} public User(String username, String password, Collection 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 getRoles() {return roles;} public void setRoles(Collection 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 getAuthorities() { Collection grantedAuthorities = new HashSet<>(); for (Role role : getRoles()) { grantedAuthorities.add(new SimpleGrantedAuthority(role.getRole())); } return grantedAuthorities; } } Create the role entity. @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 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 getUsers() {return users;} public void setUsers(List users) {this.users = users;} } Also need to create the crud repository for accessing the data in DB. public interface UserRepository extends CrudRepository { User findUserById(Long id); User findUserByUsername(String username); } public interface RoleRepository extends CrudRepository { Role findRoleByRole(String role); } ===== Create Your own UserDetailsService ===== We will use this for getting user in auth config. @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); } } ===== 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. @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); } } ===== Create BCryptPasswordEncoder Bean ===== Simply add the following code inside the main application class. @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } ===== 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. @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 allRoles = new HashSet<>(); allRoles.add(roleAdmin); allRoles.add(roleUser); Set userRoles = new HashSet<>(); userRoles.add(roleUser); Set 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)); } A testing controller can be as simple as @Controller public class HomeController { @GetMapping("/") public String home() { return "home/index"; } } With an html file in resources/templates/ Home
Hello!
===== 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.