Spring Security Series: Custom Form-based Implementation with Database Integration

Spring Security Series: Custom Form-based Implementation with Database Integration

#springboot?#springsecurity?#cloudnative

Today, we will dive into the process of storing and retrieving user and role information from a database, by replacing the in-memory user services with the DaoDetailsServiceImpl of last blog https://tinyurl.com/2h9xj5ys

We will cover key classes and components involved in spring security process, including AuthenticationFilter, DaoAuthenticationProvider, UserDetailsService, PasswordEncoder, Form Login, and MySQL Database.

No alt text provided for this image

The diagram above illustrates the process flow in Spring Security architecture. It begins with the AuthenticationFilter intercepting the request, triggering the authentication process. The DaoAuthenticationProvider, called by the authentication manager, handles the validation of user credentials. Additionally, the UserDetailsService retrieves the necessary user information from the database. This architecture allows for a flexible and customizable approach to user authentication and authorization within Spring Security. In the previous blog, we utilized the InMemoryUserDetailsManager object through UserDetailsService and relied on the default authentication providers.

Firstly, we will examine the necessary modifications to the pom.xml and application.properties files in order to establish a connection with a MySQL database using JPA, based on the previously blog code (accessible at: https://tinyurl.com/25fa2xhc).

The following dependencies need to be included in the pom.xml file:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <version>1.18.26</version>
</dependency>
<dependency>
   <groupId>com.mysql</groupId>
   <artifactId>mysql-connector-j</artifactId>
   <scope>runtime</scope>
</dependency>        

Additionally, the following properties have been incorporated into the application.properties file as mysql dependency.

spring.datasource.url = jdbc:mysql://localhost:3306/users
spring.datasource.username = root
spring.datasource.password = root

## Hibernate Properties
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL8Dialect
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = create        

In this segment, we will be implementing the DaoUserDetailsServiceImpl and injecting the DaoAuthenticationProvider in the following manner.

DaoDetailsServiceImpl.java

@Service
public class DaoDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        User user = userRepository.findById(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not present"));
        return user;
    }
    public void createUser(UserDetails user) {
        userRepository.save((User) user);
    }
}


        

We also have to make the following modifications in the SecurityConfiguration class: we will remove userDetailsService method and add the authenticationProvider as follows.

@Bean
public DaoAuthenticationProvider authenticationProvider(DaoDetailsServiceImpl securityUserDetailsService) {
    DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
    authProvider.setUserDetailsService(securityUserDetailsService);
    authProvider.setPasswordEncoder(passwordEncoder());

    return authProvider;
}        

Furthermore, it is necessary to implement the User and Role domain beans, along with their respective repository classes, to facilitate interactions with MySQL.

Role.java

@Data
@NoArgsConstructor
@Entity
@AllArgsConstructor
@Table(name = "role")
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
 
   public Role(String name) {
        this.name=name;
    }
}        

User.java

@Data
@Entity
@Table(name = "user", uniqueConstraints = @UniqueConstraint(columnNames = "email"))
@AllArgsConstructor
@NoArgsConstructor
public class User implements UserDetails {

    @Id
    @Column(name = "email")
    private String username;

    private String password;
 
    @Column(name = "account_non_locked")
    private boolean accountNonLocked;
 
    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;
 
   @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinTable(
            name = "users_roles",
            joinColumns = @JoinColumn(
                    name = "user_id", referencedColumnName = "email"),
            inverseJoinColumns = @JoinColumn(
                    name = "role_id", referencedColumnName = "id"))
    private Collection<Role> roles;        

Furthermore, since User class have implemented the Spring Security's UserDetail interface as shown above, we also need to implement the following methods.

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    return this.mapRolesToAuthorities(this.roles);
}

private Collection <? extends GrantedAuthority>  mapRolesToAuthorities(Collection < Role > roles) {
    return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList());
}
public void setAccountNonLocked(Boolean accountNonLocked) {
    this.accountNonLocked = accountNonLocked;
}
public String getPassword() {
    return password;
}

@Override
public String getUsername() {
    return this.username;
}

@Override
public boolean isAccountNonExpired() {
    return true;
}

@Override
public boolean isAccountNonLocked() {
    return this.accountNonLocked;
}

@Override
public boolean isCredentialsNonExpired() {
    return true;
}

@Override
public boolean isEnabled() {
    return this.accountNonLocked;
}        

RoleRepository.java

@Repository
public interface RoleRepository extends JpaRepository<Role,Long> {
}        

UserRepository.java

@Repository
public interface UserRepository extends JpaRepository<User, String>{

}        

Finally, in the SpringSecurityApplication.java file, we will create two sample users for testing our code. One user will possess the "admin" role, resulting in a redirect to the admin welcome page. Conversely, the other user, lacking the admin role, will be redirected to the user welcome page.

@Bean
CommandLineRunner commandLineRunner(UserRepository users, RoleRepository rolesRepo, PasswordEncoder encoder) {
    return args -> {
        List<Role> roles = new ArrayList<>();
        roles.add(new Role("ROLE_ADMIN"));
       
        users.save(new User("[email protected]", encoder.encode("password"), true, "Haroon", "Idrees", null));
        users.save(new User("[email protected]", encoder.encode("admin"), true, "admin", "admin", roles));
    };
}        

Conclusion:

By following these instructions, you will be able to create a customized login page and effortlessly integrate role-based functionalities with user management in a MySQL database using JPA. In our upcoming blog post, we will delve into the implementation of authentication and authorization using JWT (JSON Web Tokens), which is an essential component for microservices and cloud-native architectures. Stay tuned for more captivating content on its way to you!

you can find full source code for above example in following branch? https://tinyurl.com/mryk75aw

No alt text provided for this image
No alt text provided for this image
No alt text provided for this image

要查看或添加评论,请登录

Haroon Idrees的更多文章

社区洞察

其他会员也浏览了