New users are not able to register a new account from the front-end of my web app because the persist() function fails with the following error:
java.lang.ClassCastException: class java.lang.String cannot be cast to class nathanlively.subalignercss.Models.UserPrincipal (java.lang.String is in module java.base of loader 'bootstrap'; nathanlively.subalignercss.Models.UserPrincipal is in unnamed module of loader 'app')
Interestingly, this problem does not occur on database initialization that includes persisting some test users using the exact same methods.
I have verified that the error goes away when I remove "extends BaseEntity" from the UserPrincipal, therefore removing the auditing fields. I've been through every answer on StackOverflow I could find, tried a bunch of different changes, but nothing worked.
I'm using Java 17 with PostgreSQL 14.8 and Spring 3.1.
@Service
public class SpringSecurityAuditorAware implements AuditorAware<Long> {
@Override
public @NotNull Optional<Long> getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return Optional.empty();
}
Long userId = ((UserPrincipal) authentication.getPrincipal()).getId();
return Optional.of(userId);
}
}
@Configuration
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
class AuditorConfig {
}
@Getter
@Setter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
@CreatedBy
@Column(name = "created_by")
private Long createdBy;
@CreatedDate
@Column(name = "created_date")
private LocalDateTime createdDate;
@LastModifiedBy
@Column(name = "last_modified_by")
private Long lastModifiedBy;
@LastModifiedDate
@Column(name = "last_modified_date")
private LocalDateTime lastModifiedDate;
}
@Entity(name = "UserPrincipal")
@Table(name = "users")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class UserPrincipal extends BaseEntity implements UserDetails, Serializable {
@Serial
private static final long serialVersionUID = 4935519277851217959L;
@Id
@GeneratedValue
private Long id;
@Version
@Column(nullable = false)
private short version;
@Email(message = "Please use normal email format: email@domain.com")
@NotBlank(message = "The email must not be blank.")
@Column(nullable = false, unique = true)
private String email;
@NotEmpty(message = "The first name must not be blank.")
@Column(nullable = false)
private String firstName;
@NotBlank(message = "The password must not be blank.")
@Column(nullable = false)
@JsonIgnore
private String password;
@ManyToOne(optional = false)
@JoinColumn(name = "authority_id", nullable = false)
private Authority authority;
@Builder.Default
@DecimalMin(value = "300.0", message = "Speed of sound must be between 300 and 400.")
@DecimalMax(value = "400.0", message = "Speed of sound must be between 300 and 400.")
@Column(nullable = false)
private float soundVelocityInMeters = 345.0F;
@Builder.Default
private float soundVelocityInFeet = 1133.0F;
@NotNull
@Enumerated(EnumType.STRING)
@Builder.Default
@Column(name = "user_units", columnDefinition = "units_enum", nullable = false)
@Type(PostgreSQLEnumType.class)
private UnitsEnum userUnits = UnitsEnum.METERS;
@Lob
private byte[] avatar;
@Column(name = "avatar_url")
private String avatarURL;
@Builder.Default
private boolean accountNonExpired = true;
@Builder.Default
private boolean accountNonLocked = true;
@Builder.Default
private boolean credentialsNonExpired = true;
@Builder.Default
private boolean enabled = true;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Set.of(authority);
}
// username = email
public String getUsername() {
return email;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
UserPrincipal that = (UserPrincipal) o;
return getId() != null && Objects.equals(getId(), that.getId());
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}
@Transactional(readOnly = true)
@Repository
public interface UserPrincipalRepository extends BaseJpaRepository<UserPrincipal, Long> {
Optional<UserPrincipal> findByEmailIgnoreCase(String email);
boolean existsByEmailAllIgnoreCase(String email);
}
The problem was in SpringSecurityAuditorAware. Thanks to Jared for the tip!
@Service
public class SpringSecurityAuditorAware implements AuditorAware<Long> {
@Override
public @NotNull Optional<Long> getCurrentAuditor() {
try {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return Optional.empty();
}
Long userId = ((UserPrincipal) authentication.getPrincipal()).getId();
return Optional.of(userId);
} catch (Exception e) {
return Optional.empty();
}
}
}