I’ve been experiencing problems implementing the Authorities
on my spring boot application, and after digging a little I realised that maybe the association between my Credentials
and Authorities
tables, was wrong.
In fact it come to my attention that Spring allowed every type of user, (regardless their authority) to access ever method, even the ones I though have been secured. At that point, I implemented a .findAll()
method to actually see if there was some kind of problem on the association, and indeed there was.
Let me show first the MySQL tables:
CREATE TABLE credentials ( credential_id bigint UNSIGNED PRIMARY KEY AUTO_INCREMENT, email varchar(50) NOT NULL UNIQUE, password varchar(255) NOT NULL, enabled BOOLEAN NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE authorities ( email varchar(50) NOT NULL, authority varchar(50) NOT NULL, PRIMARY KEY (email, authority), CONSTRAINT fk_authorities_credentials FOREIGN KEY(email) REFERENCES credentials(email) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Before jumping onto the associations in the entities, it’s worth noting that the authorities entity has and embedded key reflecting the design of the table. So the email
column its a foreign key
and a primary at the same time:
@Embeddable public class AuthoritiesKey implements Serializable { @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "email", nullable = false ,referencedColumnName = "email") private Credentials credentials; @JoinColumn(name = "authority", nullable = false) private String authority; //getters setters omitted }
Credentials class:
@Entity @Table(name = "credentials") public class Credentials implements Serializable { @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "ak.credentials") private Authorities authorities; //other fields and getters/setters omitted }
Removing the bilateral association (thus leaving it just on the AuthoritiesKey class), hibernate successfully returns all the credentials (without their authorities of course), and all the authorities (this time preceded by the credentials).
I cannot wrap my head around it.
The complete meaningful stack trace i get when I leave the bilateral association is the following:
Error accessing field [private java.lang.String com.server.model.Credentials.email] by reflection for persistent property [com.servermodel.Credentials#email] : 64; nested exception is org.hibernate.property.access.spi.PropertyAccessException: Error accessing field [private java.lang.String com.server.model.Credentials.email] by reflection for persistent property [com.server.model.Credentials#email] : 64
Where 64 is the id of the (last, but get called first) row in the Credential
table.
=== UPDATE ===
public class CredentialsService implements UserDetailsService { @Autowired private CredentialsRepository cr; @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { Credentials user = cr.findByEmail(email); if (user == null){ throw new UsernameNotFoundException(email); } return new UserPrincipal(user); } }
Advertisement
Answer
It looks like you can not use @OneToOne
here due to the hibernate bug. (See HHH-3824) It’s reproduced even with Hibernate 5.4.28.Final
As workaround, I would suggest you to correct your mapping in this way:
@Entity @Table(name = "authorities") public class Authorities { @EmbeddedId private AuthoritiesKey pk; // ... } @Embeddable public class AuthoritiesKey implements Serializable { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "email", referencedColumnName = "email", nullable = false) private Credentials credentials; @Column(name = "authority", nullable = false) private String authority; public AuthoritiesKey(Credentials credentials, String authority) { this.credentials = credentials; this.authority = authority; } public AuthoritiesKey() { } // getters setters omitted @Override public boolean equals(Object o) { if ( this == o ) return true; if ( o == null || getClass() != o.getClass() ) return false; AuthoritiesKey pk = (AuthoritiesKey) o; return Objects.equals( credentials, pk.credentials ) && Objects.equals( authority, pk.authority ); } @Override public int hashCode() { return Objects.hash( credentials, authority ); } } @Entity @Table(name = "credentials") public class Credentials implements Serializable { @Id @Column(name = "credential_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "pk.credentials") private List<Authorities> authorities; @NaturalId @Column(name = "email") private String email; @Transient public Authorities getAuthority() { return this.authorities != null && this.authorities.size() > 0 ? this.authorities.get(0) : null; } // ... }