image.png
image.png

3.2 Describing the user

3.2.1 Demystifying the definition of the UserDetails contract

  1. The UserDetails interface

    1. public interface UserDetails extends Serializable {
    2. Collection<? extends GrantedAuthority> getAuthorities();
    3. String getPassword();
    4. String getUsername();
    5. boolean isAccountNonExpired();
    6. boolean isAccountNonLocked();
    7. boolean isCredentialsNonExpired();
    8. boolean isEnabled();
    9. }

    3.2.2 Detailing on the GrantedAuthority contract

    1. public interface GrantedAuthority extends Serializable {
    2. String getAuthority();
    3. }

    two examples of implementing a GrantedAuthority

    1. GrantedAuthority g1 = () -> "READ";
    2. GrantedAuthority g2 = new SimpleGrantedAuthority("READ");

    3.2.3 Writing a minimal implementation of UserDetails

    The DummyUser class

    1. public class DummyUser implements UserDetails {
    2. @Override
    3. public String getUsername(){
    4. return "bill";
    5. }
    6. @Override
    7. public String getPassword(){
    8. return "12345";
    9. }
    10. //...
    11. }

    Implementation of the getAuthorities() method

    1. public class DummyUser implements UserDetails {
    2. //...
    3. @Override
    4. public Collection<? extends GrantedAuthority> getAuthorities() {
    5. return List.of(() -> "READ");
    6. }
    7. //...
    8. }

    Implementation of the last four UserDetails interface methos

    1. public class DummyUser implements UserDetails {
    2. @Override
    3. public boolean isAccountNonExpired() {
    4. return true;
    5. }
    6. @Override
    7. public boolean isAccountNonLocked() {
    8. return true;
    9. }
    10. @Override
    11. public boolean isCredentialsNonExpired() {
    12. return true;
    13. }
    14. @Override
    15. public boolean isEnabled() {
    16. return true;
    17. }
    18. }

    A more practical implementation of the UserDetails interface

    1. public class User implements UserDetails {
    2. private final String username;
    3. private final String password;
    4. private final String authority;
    5. public User(String username, String password, String authority) {
    6. this.username = username;
    7. this.password = password;
    8. this.authority = authority;
    9. }
    10. @Override
    11. public Collection<? extends GrantedAuthority> getAuthorities() {
    12. return List.of(() -> authority);
    13. }
    14. @Override
    15. public String getPassword() {
    16. return password;
    17. }
    18. @Override
    19. public String getUsername() {
    20. return username;
    21. }
    22. @Override
    23. public boolean isAccountNonExpired() {
    24. return true;
    25. }
    26. @Override
    27. public boolean isAccountNonLocked() {
    28. return true;
    29. }
    30. @Override
    31. public boolean isCredentialsNonExpired() {
    32. return true;
    33. }
    34. @Override
    35. public boolean isEnabled() {
    36. return true;
    37. }
    38. }

    3.2.4 Using a builder to create instances of the UserDetails type

    Constructing a user withe the User builder class

    1. UserDetails u = User.withUsername("bill")
    2. .password("12345")
    3. .authorities("read", "write")
    4. .accountExpired(false)
    5. .disabled(true)
    6. .build();

    Creating the User.UserBuilder instance ```java User.UserBuilder builder1 = User.withUsername(“bill”);

UserDetails u1 = builder1.password(“12345”) .password(“12345”) .authorities(“read”, “write”) .passwordEncoder(p -> encode(p)) .accountExpired(false) .disabled(true) .build();

User.UserBuilder builder2 = User.withUserDetails(u); UserDetails u2 = builder2.build();

  1. <a name="iAvNr"></a>
  2. ## 3.2.5 Combing multiple responsibilities related to the user
  3. Defining the JPA User entity class
  4. ```java
  5. @Entity
  6. @Data
  7. public class User {
  8. @Id
  9. private Long id;
  10. private String username;
  11. private String password;
  12. private String authority;
  13. }

The User class has two responsibilities

  1. @Entity
  2. public class User implements UserDetails {
  3. private final String username;
  4. private final String password;
  5. private final String authority;
  6. public User(String username, String password, String authority) {
  7. this.username = username;
  8. this.password = password;
  9. this.authority = authority;
  10. }
  11. @Override
  12. public Collection<? extends GrantedAuthority> getAuthorities() {
  13. return List.of(() -> authority);
  14. }
  15. @Override
  16. public String getPassword() {
  17. return password;
  18. }
  19. @Override
  20. public String getUsername() {
  21. return username;
  22. }
  23. @Override
  24. public boolean isAccountNonExpired() {
  25. return true;
  26. }
  27. @Override
  28. public boolean isAccountNonLocked() {
  29. return true;
  30. }
  31. @Override
  32. public boolean isCredentialsNonExpired() {
  33. return true;
  34. }
  35. @Override
  36. public boolean isEnabled() {
  37. return true;
  38. }
  39. }

Implementing the User class only as a JPA entitty

  1. @Entity
  2. public class User {
  3. @Id
  4. private int id;
  5. private String username;
  6. private String password;
  7. private String authority;
  8. // Omitted getters and setters
  9. }

The SecurityUser class implements the UserDetails contract

  1. public class SecurityUser implements UserDetails {
  2. private final User user;
  3. public SecurityUser(User user) {
  4. this.user = user;
  5. }
  6. @Override
  7. public String getUsername() {
  8. return user.getUsername();
  9. }
  10. @Override
  11. public String getPassword() {
  12. return user.getPassword();
  13. }
  14. @Override
  15. public Collection<? extends GrantedAuthority> getAuthorities() {
  16. return List.of(() -> user.getAuthority());
  17. }
  18. // Omitted code
  19. }

3.3 Instructing Spring Security on how to manage users

3.3.1 Understanding the UserDetailsService contract

  1. public interface UserDetailsService {
  2. UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
  3. }

image.png

3.3.2 Implementing the UserDetailsService contract

The implementation of the UserDetails interface

  1. public class User implements UserDetails {
  2. private final String username;
  3. private final String password;
  4. private final String authority;
  5. public User(String username, String password, String authority) {
  6. this.username = username;
  7. this.password = password;
  8. this.authority = authority;
  9. }
  10. @Override
  11. public Collection<? extends GrantedAuthority> getAuthorities() {
  12. return List.of(() -> authority);
  13. }
  14. @Override
  15. public String getPassword() {
  16. return password;
  17. }
  18. @Override
  19. public String getUsername() {
  20. return username;
  21. }
  22. @Override
  23. public boolean isAccountNonExpired() {
  24. return true;
  25. }
  26. @Override
  27. public boolean isAccountNonLocked() {
  28. return true;
  29. }
  30. @Override
  31. public boolean isCredentialsNonExpired() {
  32. return true;
  33. }
  34. @Override
  35. public boolean isEnabled() {
  36. return true;
  37. }
  38. }

The implementation of the UserDetailsService interface

  1. public class InMemoryUserDetailsService implements UserDetailsService {
  2. private final List<UserDetails> users;
  3. public InMemoryUserDetailsService(List<UserDetails> users) {
  4. this.users = users;
  5. }
  6. @Override
  7. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  8. return users.stream()
  9. .filter(u -> u.getUsername().equals(username))
  10. .findFirst()
  11. .orElseThrow(() -> new UsernameNotFoundException("User not found"));
  12. }
  13. }

UserDetailsService registered as a bean in the configuration class

  1. @Configuration
  2. public class ProjectConfig {
  3. @Bean
  4. public UserDetailsService userDetailsService() {
  5. UserDetails u = new User("john", "12345", "read");
  6. List<UserDetails> users = List.of(u);
  7. return new InMemoryUserDetailsService(users);
  8. }
  9. @Bean
  10. public PasswordEncoder passwordEncoder() {
  11. return NoOpPasswordEncoder.getInstance();
  12. }
  13. }

The definition of the endpoint used for testing the implementation

  1. @RestController
  2. public class HelloController {
  3. @GetMapping("/hello")
  4. public String hello() {
  5. return "Hello";
  6. }
  7. }

image.png

3.3.3 Implementing the UserDetailsManager contract

  1. public interface UserDetailsManager extends UserDetailsService {
  2. void createUser(UserDetails user);
  3. void updateUser(UserDetails user);
  4. void deleteUser(String username);
  5. void changePassword(String oldPassword, String newPassword);
  6. boolean userExists(String username);
  7. }

Registering the JdbcUserDetailsManager in the configuration class

  1. @Configuration
  2. public class ProjectConfig extends WebSecurityConfigurerAdapter {
  3. @Bean
  4. public UserDetailsService userDetailsService(DataSource dataSource) {
  5. return new JdbcUserDetailsManager(dataSource);
  6. }
  7. @Bean
  8. public PasswordEncoder passwordEncoder() {
  9. return NoOpPasswordEncoder.getInstance();
  10. }
  11. }

Changing JdbcUserDetailsManager’s queries to find the user

  1. @Configuration
  2. public class ProjectConfig extends WebSecurityConfigurerAdapter {
  3. @Bean
  4. public UserDetailsService userDetailsService(DataSource dataSource) {
  5. String usersByUsernameQuery = "select username, password, enabled from spring.users where username = ?";
  6. String authsByUserQuery = "select username, authority from spring.authorities where username = ?";
  7. var userDetailsManager = new JdbcUserDetailsManager(dataSource);
  8. userDetailsManager.setUsersByUsernameQuery(usersByUsernameQuery);
  9. userDetailsManager.setAuthoritiesByUsernameQuery(authsByUserQuery);
  10. return userDetailsManager;
  11. // return new JdbcUserDetailsManager(dataSource);
  12. }
  13. @Bean
  14. public PasswordEncoder passwordEncoder() {
  15. return NoOpPasswordEncoder.getInstance();
  16. }
  17. }

image.png