'Replacing Username property in Spring security

Firstly, please feel free to ask, if anyone think title of this stack should be changed.

I'm newbie in spring security, just learned it & start working on an authentication project.

The project I've been assigned has something new, that I did not learn, in fact the base of this authentication / registration app is ACCOUNT NUMBER not Username as usually we use.

As far as I've learned spring security so far, Username is the basic & fundamental element of spring security (I'm still learning), for example two major interfaces we use in Spring sec app are UserDetails & UserDetailsService, both interfaces bring some properties for authorisation which seems only relates to only Username, such as loadUsername or getUsername().

In this case, the only solution I could find is make everything as usually we do for authentication with username, & following changes I've just made

in CustomUserDetailService.java

loadUserByUsername(String accountNumber) instead of loadUserByUsername(String username)

in UserDetailsImpl.java

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

App working perfect!!, I can register AccountNumber with Password and without any error I can login accountNUmber with password, it means authentication works well.

But I feel this is not mature way to make an app. Therefore I'm here and looking for an advise from the spring security expert, if there is any other way to handle this ? many thanks.

Following I copy all classes:

Account.java

public class Account {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String accountNumber;
private String email;
private String password;

@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "account_roles",
        joinColumns = @JoinColumn(name = "account_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();

AccountRepository

public interface AccountRepository extends JpaRepository<Account, Long> {
User findAccountNumber(String accountNumber);
Boolean existsAccountNumber(String accountNumber);

SignupDto

public class SignupDto {
private String accountNumber;
private String email;
private Set<String> role;
private String password;

LoginDto

public class LoginDto {
private String accountNumber;
private String password;

AuthController

@PostMapping("/signup")
public ResponseEntity<?> registerAccount(@RequestBody SignupDto signUpDto) {
    if (userRepository.existsAccountNumber(signUpDto.getAccountNumber())) {
        return ResponseEntity
                .badRequest()
                .body(new MessageResponse("Error: Account is already taken!"));
    }

    // Create new user's account
    Account account = new Account(signUpDto.getAccountNumber(),
            encoder.encode(signUpRequest.getPassword()));

    Set<String> strRoles = signUpDto.getRole();
    Set<Role> roles = new HashSet<>();

    if (strRoles == null) {
        Role userRole = roleRepository.findByName(ERole.ROLE_USER)
                .orElseThrow(() -> new RuntimeException("Error: Role is not found."));
        roles.add(userRole);
    } else {
        strRoles.forEach(role -> {
            switch (role) {
                case "admin":
                    Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN)
                            .orElseThrow(() -> new RuntimeException("Error: Role is not 
               found."));
                    roles.add(adminRole);

                    break;
                    default:
                    Role userRole = roleRepository.findByName(ERole.ROLE_USER)
                            .orElseThrow(() -> new RuntimeException("Error: Role is not 
                 found."));
                    roles.add(userRole);
            }
        });
    }

    account.setRoles(roles);
    accountRepository.save(account);

    return ResponseEntity.ok(new MessageResponse("Account registered successfully!"));
  }

CustomUserDetailService

@Service
public class CustomUserDetailService implements UserDetailsService {
private static final Logger LOG = LoggerFactory.getLogger(CustomUserDetailService.class);

@Autowired
private AccountRepository accountRepository;

@Override
@Transactional
public UserDetails loadUserByUsername (String accountNumber) throws UsernameNotFoundException 
   {Account account = accountRepository.findAccountNumber(accountNumber);
    if (null == account) {
        LOG.warn("Account {} not found", accountNumber);
        throw new UsernameNotFoundException("Account " + accountNumber + " not found");
    }
    return UserDetailsImpl.build(account);
}
}

UserDetailsImpl

public class UserDetailsImpl implements UserDetails {
private static final long serialVersionUID = 1L;

private Long id;
private String accountNumber;
private String email;

@JsonIgnore
private String password;

private Collection<? extends GrantedAuthority> authorities;

public UserDetailsImpl(Long id, String accountNumber, String email, String password,
                       Collection<? extends GrantedAuthority> authorities) {
    this.id = id;
    this.accountNumber = accountNumber;
    this.email = email;
    this.password = password;
    this.authorities = authorities;
}

public static UserDetailsImpl build(Account account){
    List<GrantedAuthority> authorities = account.getRoles().stream()
            .map(role -> new SimpleGrantedAuthority(role.getName().name()))
            .collect(Collectors.toList());

    return new UserDetailsImpl(
            user.getId(),
            user.getaccountNumber(),
            user.getEmail(),
            user.getPassword(),
            authorities
    );
}


@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    return authorities;
}

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

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

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

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

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

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

}



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source