3.2 Describing the user
3.2.1 Demystifying the definition of the UserDetails contract
The UserDetails interface
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
3.2.2 Detailing on the GrantedAuthority contract
public interface GrantedAuthority extends Serializable {
String getAuthority();
}
two examples of implementing a GrantedAuthority
GrantedAuthority g1 = () -> "READ";
GrantedAuthority g2 = new SimpleGrantedAuthority("READ");
3.2.3 Writing a minimal implementation of UserDetails
The DummyUser class
public class DummyUser implements UserDetails {
@Override
public String getUsername(){
return "bill";
}
@Override
public String getPassword(){
return "12345";
}
//...
}
Implementation of the getAuthorities() method
public class DummyUser implements UserDetails {
//...
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(() -> "READ");
}
//...
}
Implementation of the last four UserDetails interface methos
public class DummyUser implements UserDetails {
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
A more practical implementation of the UserDetails interface
public class User implements UserDetails {
private final String username;
private final String password;
private final String authority;
public User(String username, String password, String authority) {
this.username = username;
this.password = password;
this.authority = authority;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(() -> authority);
}
@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;
}
}
3.2.4 Using a builder to create instances of the UserDetails type
Constructing a user withe the User builder class
UserDetails u = User.withUsername("bill")
.password("12345")
.authorities("read", "write")
.accountExpired(false)
.disabled(true)
.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();
<a name="iAvNr"></a>
## 3.2.5 Combing multiple responsibilities related to the user
Defining the JPA User entity class
```java
@Entity
@Data
public class User {
@Id
private Long id;
private String username;
private String password;
private String authority;
}
The User class has two responsibilities
@Entity
public class User implements UserDetails {
private final String username;
private final String password;
private final String authority;
public User(String username, String password, String authority) {
this.username = username;
this.password = password;
this.authority = authority;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(() -> authority);
}
@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;
}
}
Implementing the User class only as a JPA entitty
@Entity
public class User {
@Id
private int id;
private String username;
private String password;
private String authority;
// Omitted getters and setters
}
The SecurityUser class implements the UserDetails contract
public class SecurityUser implements UserDetails {
private final User user;
public SecurityUser(User user) {
this.user = user;
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(() -> user.getAuthority());
}
// Omitted code
}
3.3 Instructing Spring Security on how to manage users
3.3.1 Understanding the UserDetailsService contract
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
3.3.2 Implementing the UserDetailsService contract
The implementation of the UserDetails interface
public class User implements UserDetails {
private final String username;
private final String password;
private final String authority;
public User(String username, String password, String authority) {
this.username = username;
this.password = password;
this.authority = authority;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(() -> authority);
}
@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;
}
}
The implementation of the UserDetailsService interface
public class InMemoryUserDetailsService implements UserDetailsService {
private final List<UserDetails> users;
public InMemoryUserDetailsService(List<UserDetails> users) {
this.users = users;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return users.stream()
.filter(u -> u.getUsername().equals(username))
.findFirst()
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
}
}
UserDetailsService registered as a bean in the configuration class
@Configuration
public class ProjectConfig {
@Bean
public UserDetailsService userDetailsService() {
UserDetails u = new User("john", "12345", "read");
List<UserDetails> users = List.of(u);
return new InMemoryUserDetailsService(users);
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
The definition of the endpoint used for testing the implementation
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello";
}
}
3.3.3 Implementing the UserDetailsManager contract
public interface UserDetailsManager extends UserDetailsService {
void createUser(UserDetails user);
void updateUser(UserDetails user);
void deleteUser(String username);
void changePassword(String oldPassword, String newPassword);
boolean userExists(String username);
}
Registering the JdbcUserDetailsManager in the configuration class
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Bean
public UserDetailsService userDetailsService(DataSource dataSource) {
return new JdbcUserDetailsManager(dataSource);
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
Changing JdbcUserDetailsManager’s queries to find the user
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Bean
public UserDetailsService userDetailsService(DataSource dataSource) {
String usersByUsernameQuery = "select username, password, enabled from spring.users where username = ?";
String authsByUserQuery = "select username, authority from spring.authorities where username = ?";
var userDetailsManager = new JdbcUserDetailsManager(dataSource);
userDetailsManager.setUsersByUsernameQuery(usersByUsernameQuery);
userDetailsManager.setAuthoritiesByUsernameQuery(authsByUserQuery);
return userDetailsManager;
// return new JdbcUserDetailsManager(dataSource);
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}