Search code examples
springspring-mvcspring-security

Spring Security 6 Authentication and Authorization with user in database


I have a project like platform for online food spring ordering system I am using Hibernate with two Models like this:

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    private String password;
    private String email;
    private Long balance;
}

@Entity
@Table(name = "user_roles")
public class UserRole {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
}

I think this system has three roles: ROLE_SYSADMIN, ROLE_ADMIN (for food store), ROLE_CUSTOMER Users and UsersRole stored in Database.

How can I config authorization and authentication for my app using Spring Boot 6?

I searched and made some codes. Firstly, I wrote a custom UserDetailService like this

@Service
public class HiruezUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;
    
    public HiruezUserDetailsService(UserRepository userRepository) {
        super();
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));
        return new HiruezUserDetails(user);
    }

}

Then, custom UserDetails like:

public class HiruezUserDetails implements UserDetails {
    
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private final User user;

    public HiruezUserDetails(User user) {
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return Collections.singleton(new SimpleGrantedAuthority("ROLE_" + user.getRole().getName()));
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

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

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

    @Override
    public boolean isAccountNonLocked() {
        
        return false;
    }

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

    @Override
    public boolean isEnabled() {
        
        return false;
    }

}

The last is SecurityConfig.java like:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Autowired
    private HiruezUserDetailsService hiruezUserDetailsService;
    
    @Bean
    public PasswordEncoder passwordEncoder( ) {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    public JdbcUserDetailsManager jdbcUserDetailManager() {
        JdbcUserDetailsManager userDetailManager = new JdbcUserDetailsManager();
        userDetailManager.setJdbcTemplate(jdbcTemplate);
        return userDetailManager;
    }
    
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .userDetailsService(hiruezUserDetailsService)
            .passwordEncoder(passwordEncoder());
    }
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authorize) -> authorize
                .requestMatchers("/admin/**").hasRole("SYSADMIN")
                .requestMatchers("/store/**").hasRole("ADMIN")
                .requestMatchers("/customer/**").hasRole("CUSTOMER")
                .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults())
            .formLogin(form -> form
                    .loginPage("/login")
                    .permitAll()
                )
            .logout((logout) -> logout.logoutUrl("/logout"));

        return http.build();
    }
}

But I work.... ummmm

Please, help me fix or give me another approach. Thank you very much....


Solution

  • let see how we can solve this

    • update your users table entity with a new column roleId which previously set by the admin
    
    @Entity
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @Table(name = "users")
    public class User implements UserDetails {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String username;
        private String password;
        private String email;
        private Long balance;
    
        private Long roleId; // new field , this field will set when admin create user by selecting role
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
    
            List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
            //authorities.add(new SimpleGrantedAuthority("ROLE_SYSADMIN"));
            //authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
            //authorities.add(new SimpleGrantedAuthority("ROLE_CUSTOMER"));
    
            // we will set the permission leater
    
            return authorities;
        }
    
        @Override
        public String getPassword() {
            return password;
        }
    
        @Override
        public String getUsername() {
            return username;
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return true;
        }
    }
    
    
    • repo for users table
    public interface UserRepository extends JpaRepository<User, Long> {
    }
    
    
    • user role table and repo
    @Entity
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @Table(name = "user_roles")
    public class UserRole {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String name;
    }
    
    public interface UserRoleRepository extends JpaRepository<UserRole, Long> {
    }
    
    
    • create a custom authentication provider to read permission name form user role table and uses for authentication and authorization
    @Component
    @RequiredArgsConstructor
    public class UserAuthenticationProvider implements AuthenticationProvider, UserDetailsService {
    
        private final UserRepository userRepository;
        private final UserRoleRepository userRoleRepository;
    
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            final String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
            if (Utils.isEmpty(username)) {
                throw new BadCredentialsException("invalid login details");
            }
            // get user details using Spring security user details service
            UserDetails user = null;
            try {
                user = loadUserByUsername(username);
    
            } catch (UsernameNotFoundException exception) {
                throw new BadCredentialsException("invalid login details");
            }
            return createSuccessfulAuthentication(authentication, user);
        }
    
        private Authentication createSuccessfulAuthentication(final Authentication authentication, final UserDetails user) {
    
            User dbUser =  userRepository.findByUsername(user.getUsername());
            UserRole userRole = userRoleRepository.findById(dbUser.getRoleId());
    
            List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
            authorities.add(new SimpleGrantedAuthority(userRole.getName()));
    
    
            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user.getUsername(), authentication.getCredentials(), authorities);
            token.setDetails(authentication.getDetails());
            return token;
        }
    
        @Override
        public boolean supports(Class < ? > authentication) {
            return authentication.equals(UsernamePasswordAuthenticationToken.class);
        }
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
            return userRepository.findByUsername(username)
                    .orElseThrow(() -> new UsernameNotFoundException("User not found"));
        }
    }
    
    
    • update your security as per your need but use above authentication provider
    
    @Configuration
    @EnableWebSecurity
    @RequiredArgsConstructor
    public class SecurityConfig {
        private final UserAuthenticationProvider userAuthenticationProvider;
    
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http
                    // ...
                    .authenticationProvider(userAuthenticationProvider);
            return http.build();
        }
    }