Hibernate JPA loop - spring-boot

I created an entity class :
#Entity
#Table(name="users")
#Getter #Setter
public class UserModel implements Serializable {
#Setter(AccessLevel.NONE)
#Getter(AccessLevel.NONE)
private static final long serialVersionUID = -5608230793232883579L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(nullable = false, unique = true)
private String userId;
#Column(nullable = false, length = 50)
private String firstName;
#Column(nullable = false, length = 50)
private String lastName;
#Email
#Column(nullable = false, length = 120, unique = true)
private String email;
#Column(nullable = false)
private String encryptedPassword;
private Boolean emailVerificationStatus = false;
private String emailVerificationToken;
#ManyToMany(cascade= { CascadeType.PERSIST }, fetch = FetchType.EAGER )
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns=#JoinColumn(name = "role_id", referencedColumnName = "id"))
private List<RoleModel> roles;
#JsonManagedReference
#OneToMany(mappedBy = "user")
private List<ProjectModel> projects;
}
For the list of projects, I also have an entity class:
#Entity
#Table(name= "projects")
#Getter #Setter
public class ProjectModel implements Serializable {
#Setter(AccessLevel.NONE)
#Getter(AccessLevel.NONE)
public static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(nullable = false, unique = true)
private String projectId;
// ...
#Column
#JsonManagedReference
#OneToMany(mappedBy = "project")
private List<ObjectiveModel> objectives;
// ...
#JsonBackReference
#ManyToOne(
cascade = { CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH },
fetch = FetchType.LAZY
)
private UserModel user;
}
I also use a DTO layer to communicate with database:
#Getter #Setter
public class UserDto implements Serializable {
#Setter(AccessLevel.NONE)
#Getter(AccessLevel.NONE)
private static final long serialVersionUID = -5352357837541477260L;
// contains more information than models used for rest
private long id;
private String userId;
private String firstName;
private String lastName;
private String email;
private String password;
private String encryptedPassword;
private String emailVerificationToken;
private Boolean emailVerificationStatus = false;
private List<String> roles;
private List<ProjectDto> projects;
}
Each entity has its own Dto equivalent. I can create a user. My issue is trying to log in. My userServiceImpl implements Spring Security UserService. Here is my implementation :
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
UserModel userModel = userRepository.findByEmail(email);
if(userModel == null)
throw new UsernameNotFoundException("User with email " + email + " not found");
return new UserPrincipalManager(userModel);
}
My UserPrincipalManager :
public class UserPrincipalManager implements UserDetails {
private static final long serialVersionUID = 7464059818443209139L;
private UserModel userModel;
private ProjectModel projectModel;
#Getter #Setter
private String userId;
#Autowired
public UserPrincipalManager(UserModel userModel) {
this.userModel = userModel;
this.userId = userModel.getUserId();
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new HashSet<>();
Collection<AuthorityModel> authorityModelEntities = new HashSet<>();
// get user roles
Collection<RoleModel> roleModels = userModel.getRoles();
if (roleModels == null) {
return authorities; // null
}
// get user roles
roleModels.forEach((role) ->{
authorities.add(new SimpleGrantedAuthority(role.getName()));
authorityModelEntities.addAll(role.getAuthorities());
});
// get user authorities
authorityModelEntities.forEach(authorityModel -> {
authorities.add(new SimpleGrantedAuthority(authorityModel.getName()));
});
return authorities;
}
#Override
public String getPassword() {
return this.userModel.getEncryptedPassword();
}
#Override
public String getUsername() {
return this.userModel.getEmail();
}
// we do not store this information in DB
#Override
public boolean isAccountNonExpired() {
return true;
}
// we do not store this information in DB (yet)
#Override
public boolean isAccountNonLocked() {
return true;
}
// we do not store this information in DB (yet)
#Override
public boolean isCredentialsNonExpired() {
return true;
}
// isEnabled depending if account is activated => email verification status value
#Override
public boolean isEnabled() {
return this.userModel.getEmailVerificationStatus();
}
}
While trying to log in a User sql request is looping.
at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:59)
at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:31)
at org.modelmapper.internal.MappingEngineImpl.convert(MappingEngineImpl.java:303)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:110)
at org.modelmapper.internal.MappingEngineImpl.setDestinationValue(MappingEngineImpl.java:242)
at org.modelmapper.internal.MappingEngineImpl.propertyMap(MappingEngineImpl.java:188)
at org.modelmapper.internal.MappingEngineImpl.typeMap(MappingEngineImpl.java:152)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:106)
at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:59)
at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:31)
at org.modelmapper.internal.MappingEngineImpl.convert(MappingEngineImpl.java:303)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:110)
at org.modelmapper.internal.MappingEngineImpl.setDestinationValue(MappingEngineImpl.java:242)
at org.modelmapper.internal.MappingEngineImpl.propertyMap(MappingEngineImpl.java:188)
at org.modelmapper.internal.MappingEngineImpl.typeMap(MappingEngineImpl.java:152)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:106)
at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:59)
at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:31)
at org.modelmapper.internal.MappingEngineImpl.convert(MappingEngineImpl.java:303)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:110)
at org.modelmapper.internal.MappingEngineImpl.setDestinationValue(MappingEngineImpl.java:242)
at org.modelmapper.internal.MappingEngineImpl.propertyMap(MappingEngineImpl.java:188)
at org.modelmapper.internal.MappingEngineImpl.typeMap(MappingEngineImpl.java:152)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:106)
In the end the application crashes and returns a 403 error.
2020-10-05 12:07:22.215 DEBUG 4564 --- [nio-8080-exec-8] o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-5.3.3.RELEASE.jar:5.3.3.RELEASE]
The login fonction works if user do not have project associated.

I don't know anything about model mapper, but I would like to provide you an alternative solution because I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(UserModel.class)
public interface UserDto extends Serializable {
#IdMapping
Long getId();
String getUserId();
String getFirstName();
String getLastName();
String getEmail();
String getPassword();
String getEncryptedPassword();
String getEmailVerificationToken();
Boolean getEmailVerificationStatus();
Set<String> getRoles();
Set<ProjectDto> getProjects();
#EntityView(ProjectModel.class)
interface ProjectDto {
#IdMapping
Long getId();
String getProjectId();
// Other mappings...
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
UserDto a = entityViewManager.find(entityManager, UserDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
The big bonus here, it will only fetch the columns that are actually needed and it validates the DTO model against your JPA model during boot time, so there are no more runtime surprises!

Related

JPA lazy initialization error with #OneToMany #EmbeddedId

In Sprinboot/JPA I defined an entity with one-to-may association as follows:
#Entity
#Table(name = "useraccount", catalog = "useraccount")
public class UserAccount implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//other stuff...
#OneToMany(mappedBy ="tokenId.user", cascade = {CascadeType.REMOVE, CascadeType.MERGE, CascadeType.REFRESH}, orphanRemoval =true, fetch=FetchType.LAZY)
private Set<SecureToken> tokens = new HashSet<>();
public Set<SecureToken> getTokens()
{
return this.tokens;
}
//other getter and setter
}
The SecureToken entity:
#Entity
#Table(name = "secureToken", catalog = "useraccount")
public class SecureToken implements Serializable
{
#EmbeddedId
public SecureTokenId tokenId= new SecureTokenId();
#Column(unique = true)
private String token;
private Timestamp isConsumed;
#CreationTimestamp
#Column(updatable = false)
private Timestamp timestamp;
#Column(updatable = false)
#Basic(optional = false)
private Timestamp expireAt;
#MapsId("user_id")
#JoinColumn(name = "user_id", referencedColumnName ="id")
#ManyToOne
private UserAccount user;
public SecureToken(UserAccount user, String token, String tokenType, Timestamp timestamp, Timestamp expire)
{
super();
this.token=token;
this.tokenId.setTokenType(tokenType);
this.tokenId.setUser(user);
this.timestamp=timestamp;
this.expireAt=expire;
this.isExpired=false;
}
}
The SecureTokenId:
#Embeddable
public class SecureTokenId implements Serializable
{
#Column(name="tokenType")
private String tokenType;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private UserAccount user;
public SecureTokenId()
{
super();
}
public SecureTokenId(String tokenType)
{
//this.user_id=user_id;
this.tokenType=tokenType;
}
#Override
public boolean equals(Object o)
{
if (o == null || getClass() != o.getClass())
return false;
SecureTokenId that = (SecureTokenId) o;
return Objects.equals(this.tokenType, that.tokenType) &&
Objects.equals(this.user.getId(), that.user.getId());
}
#Override
public int hashCode() {
return Objects.hash(tokenType, this.user.getId());
}
public void setTokenType(String tokenType)
{
this.tokenType=tokenType;
}
public String getTokenType()
{
return this.tokenType;
}
public void setUser(UserAccount user)
{
this.user=user;
}
public UserAccount getUser()
{
return this.user;
}
public Long getTokenId()
{
return this.user.getId();
}
}
But calling the method getToken() of entity UserAccount gets the famous "LazyInitializationException". I generally use Hibernate.initialize, but with this configuration I cannot get rid of the problem.
This how I create a token within a #Service annoted SecureTokenService class.
#Override
#Transactional
public SecureToken generateToken(UserAccount user, String tokenType)
{
byte[] random = new byte[64];
new SecureRandom().nextBytes(random);
Timestamp timestamp = java.sql.Timestamp.valueOf(LocalDateTime.now());
LocalDateTime expire= LocalDateTime.now().plusHours(12);
SecureToken token = new SecureToken(new SecureTokenId(user, tokenType),Base64.encodeBase64URLSafeString(random),
timestamp, Timestamp.valueOf(expire));
return token;
}
Then in the UserService class (#Service annotated) I try to create a token:
SecureToken token = secureTokenService.generateToken(user, type);
secureTokenService.save(token);
user.addSecureToken(token); //Error
this.save(user)
When I try to associate the token with the user the error is thrown. Without that statement, the application seems working but even with "spring.jpa.open-in-view = false" in application.properties calling user.getTokens() rises the lazy initialization error.
In parent child relationship, you didn't declare any parent reference from child side.
In the parent side (UserAccount), you declared as follows
#OneToMany(mappedBy ="user"....
Which means your child side (SecureToken) there is no such property named user.
To get rid of this situation,
First you need to declare user inside of SecureToken / SecureTokenId. From your definition, you declared user_id inside SecureTokenId, instead declare user inside SecureTokenId.
...
public class SecureTokenId ... {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private UserAccount user;
...
}
Then in the UserAccount declare the #OneToMany as follows
#OneToMany(mappedBy ="tokenId.user"...
private Set<SecureToken> tokens;

Spring Controller Returns Object Incompletely

There are three classes (Course, Lesson, User).
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "usr")
#Data
public class User extends RepresentationModel<User> implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstname;
private String lastname;
private String username;
private String password;
#ElementCollection(targetClass = ERole.class, fetch = FetchType.EAGER)
#CollectionTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"))
#Enumerated(EnumType.STRING)
private Set<ERole> roles;
}
#Data
#Entity
#NoArgsConstructor
public class Lesson extends RepresentationModel<Lesson> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String startTime;
private String endTime;
private String dayOfWeek;
#ManyToOne
private User teacher;
}
#EqualsAndHashCode(callSuper = true)
#Data
#Entity
public class Course extends RepresentationModel<Course> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Date startDate;
private Date endDate;
private String name;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<User> teachers;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<User> students;
private String description;
#ManyToMany(cascade = CascadeType.ALL)
private Set<Lesson> lessons;
}
And also RestController (CoursesController). When accessing the server at /courses, I get the correct server response with all fields
.
#RestController
#RequestMapping("/courses")
public class CoursesController {
private final CourseService courseService;
private final UserService userService;
private final LessonService lessonService;
#Autowired
public CoursesController(CourseService courseService, UserService userService, LessonService lessonService) {
this.courseService = courseService;
this.userService = userService;
this.lessonService = lessonService;
}
#GetMapping
#Operation(
summary = "getAllCourses",
description = "Returns all available courses"
)
public ResponseEntity<Page<Course>> getAllCourses(#PageableDefault(sort = "id", size = 5) Pageable pageable) {
try {
Page<Course> coursePage = courseService.findAll(pageable);
for (Course course : coursePage.getContent())
course.add(linkTo(methodOn(CoursesController.class).getCourse(course.getId().toString())).withSelfRel());
return ResponseEntity.ok(courseService.findAll(pageable));
}
catch (Exception e) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
#GetMapping("/{course-id}")
#Operation(
summary = "getCourse",
description = "Returns course by ID"
)
public ResponseEntity<Course> getCourse(#PathVariable ("course-id") String courseId) {
try {
Course course = courseService.getCourseById(courseId);
course.add(linkTo(methodOn(CoursesController.class).getCourse(courseId)).withSelfRel());
return ResponseEntity.ok(course);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
}
Why, when requesting a course by ID (GET /courses/{id}), does Spring return an incomplete object (despite the fact that I manually added several teachers, students and lessons)?
I need to get all the fields of my object.
My CourseRepository below.
#Repository
#Transactional
public interface CourseRepository extends JpaRepository<Course, Long> {
}
My CourseService below.
#Service
public class CourseService {
private final CourseRepository courseRepository;
private final LessonRepository lessonRepository;
private final UserRepository userRepository;
#Autowired
public CourseService(CourseRepository courseRepository, LessonRepository lessonRepository, UserRepository userRepository) {
this.courseRepository = courseRepository;
this.lessonRepository = lessonRepository;
this.userRepository = userRepository;
}
public Page<Course> findAll(Pageable pageable) {
return courseRepository.findAll(pageable);
}
public Course createCourse(CourseDto courseDto) {
Course course = new Course(courseDto.getStartDate(), courseDto.getEndDate(), courseDto.getName(), courseDto.getDescription());
return courseRepository.saveAndFlush(course);
}
public Optional<Course> getCourseById(String id) {
return courseRepository.findById(Long.parseLong(id));
}
public Course updateCourse(CourseDto courseDto, String id) {
Course course = courseRepository.findById(Long.parseLong(id)).get();
course.setStartDate(courseDto.getStartDate());
course.setEndDate(courseDto.getEndDate());
course.setName(courseDto.getName());
course.setDescription(courseDto.getDescription());
return courseRepository.saveAndFlush(course);
}
public Page<Lesson> getLessonsByCourse(String courseId, Pageable pageable) {
Course course = courseRepository.findById(Long.parseLong(courseId)).get();
return new PageImpl<>(new ArrayList<>(course.getLessons()), pageable, course.getLessons().size());
}
public Course addLesson(String courseId, LessonDto lessonDto) {
Course course = courseRepository.findById(Long.parseLong(courseId)).get();
Lesson lesson = new Lesson();
lesson.setStartTime(lessonDto.getStartTime());
lesson.setEndTime(lessonDto.getFinishTime());
lesson.setDayOfWeek(lessonDto.getDayOfWeek());
lesson.setTeacher(userRepository.getUserById(lessonDto.getTeacherId()));
lessonRepository.saveAndFlush(lesson);
System.out.println(lesson);
course.getLessons().add(lesson);
return courseRepository.saveAndFlush(course);
}
public void deleteCourse(String id) {
courseRepository.deleteById(Long.parseLong(id));
}
}
Which I would (or might) expect as well. I would links to be generated for those additional relationshps (at least normally with Spring Data RESt handling this is what would happen). I wonder what happens if you ditch the RepresentationModel from your JPA model and just expose Course then. As stated you don't really want your JPA and HATEOAS stuff to be intertwined. You want to have a specialized projection/dto to expose. WHy does it work for your findAll. well you aren't adding links to it (although you think it does but your findAll executes twice!).
Removed RepresentationModel from User class.
Thx to #M.Deinum

How to fix jpa one to many

Need to fetch data from one table to another.I performed jpa one to many mapping. But id cannot fetched. Where is my mistake?
I have tried mapping using one to many and many to one concepts but can't able to fetch data from one table to another
User.java
#Entity
#Table(name = "users")
public class User implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "User_ID")
#GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid", strategy = "uuid")
private String id;
private String firstName;
private String lastName;
private Long phoneNumber;
#NotNull(message="Password is compulsory")
#Email(message = "Email is invalid")
private String email;
private String password;
#OneToMany(mappedBy="user", cascade = CascadeType.ALL)
Set<Data> data = new HashSet<Data>();
public Set<Data> getData() {
return data;
}
public void setData(Set<Data> data) {
this.data = data;
}
public User() {
super();
}
Data.java
#Entity
#Table(name = "data")
public class Data implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "DataID")
#GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid", strategy = "uuid")
private String id;
#ManyToOne(fetch = FetchType.EAGER,cascade= CascadeType.ALL)
#JoinColumn(name = "User_ID")
private User user;
public Data() {
super();
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
DataController.java
#PostMapping("/data/{userId}")
public Data createData(
#PathVariable(value= "userId") String userId,
#Valid #RequestBody Data data) {
return userRepository.findById(userId).map(user -> {
data.setUser(user);
return dataRepository.save(data);
}).orElseThrow(() -> new ResourceNotFoundException("userId" + userId +
"not found"));
}
Results in no error but can't able to fetch user id

Roles not getting fetched from database along with the user object

I am creating a user registration flow with spring boot (2.1.3.RELEASE) . With the help of few articles I am able to successfully add a user along with its roles and user is able to login into the system. The problem is when user is successfully loged-in, the authentication obect has empty role even when I can see th correct role mapping in mysql database (honestly I am not able to get exactly how roles are fetched from database when findByUserName method is called.
Below is my code:
Entity objects
1. User.java
public class User implements UserDetails {
private static final long serialVersionUID = 1L;
public User() {
//Verification flow 2. set enabled = false
this.enabled = false;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_unique_number")
private long id;
#UniqueUser(groups = UniqueUserOrder.class)
#Column(name = "username", length = 60,nullable = false, unique = true)
private String username;
#Column(name = "email", nullable = false, unique = true)
private String email;
#Column(name = "password", nullable = false)
private String password;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<UserRole> userRoles = new HashSet<>();
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> authorities = new HashSet<>();
userRoles.forEach(ur -> authorities.add(new Authority(ur.getRole().getName())));
return authorities;
}
public Set<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRoles(Set<UserRole> userRoles) {
this.userRoles = userRoles;
}
...//OTHER GETTERS AND SETTERS
}
Roles.java
public class Role
{#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
private String name;
#OneToMany(mappedBy = "role", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<UserRole> userRoles = new HashSet<>();
public Role() {
}
public Role(RolesEnum rolesEnum) {
this.id = rolesEnum.getId();
this.name = rolesEnum.getRoleName();
}
public Set<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRoles(Set<UserRole> userRoles) {
this.userRoles = userRoles;
}
...//OTHER GETTERS AND SETTERS }
UserRole.java
public class UserRole {
public UserRole() {}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
public UserRole(User user, Role role) {
this.user = user;
this.role = role;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "role_id")
private Role role;
...//OTHER GETTERS AND SETTERS
}
4.RolesEnum.java
public enum RolesEnum {
ADMIN(1, "ROLE_ADMIN"),
BASIC(2, "ROLE_BASIC");
private final int id;
private final String roleName;
...//OTHER GETTERS AND SETTERS
}
New user is getting created as below:
...
...
String encryptedPassword = passwordEncoder.encode(adminPassword);
user.setPassword(encryptedPassword);
user.setUsername(adminUsername);
user.setEmail(adminEmail);
user.setUserCreateTime(LocalDateTime.now());
Set<UserRole> userRoles = new HashSet<>();
UserRole userRole = new UserRole();
userRole.setUser(user);
userRole.setRole(new Role(RolesEnum.ADMIN));
userRoles.add(userRole);
user.getUserRoles().addAll(userRoles);
user.setAccountNonLocked(true);
user.setEnabled(true);
user.setAccountNonExpired(true);
user.setCredentialsNonExpired(true);
user = userRepository.save(user);
...
...
At this point user is added successfully along with the roles in database
User is also able to successfully log-in but the problem is after logging, authentication object has an empty list of roles
Below is the code which is failing
public class SecurityConfig extends WebSecurityConfigurerAdapter {
......
......
private AuthenticationSuccessHandler loginSuccessHandler() {
return (request, response, **authentication**) -> {
Collection<? extends GrantedAuthority> authorities
= authentication.getAuthorities();
for (GrantedAuthority grantedAuthority : authorities) {
if (grantedAuthority.getAuthority().equals("ROLE_ADMIN")) {
isAdmin = true;
break;
} else if (grantedAuthority.getAuthority().equals("ROLE_ADMIN")) {
isBasic = true;
break;
}
}
if (isAdmin) { return "/admin/"; } else if (isBasic) { return
"/profile.html"; } else { throw new IllegalStateException(); }
response.sendRedirect("/");
};
}
......
......
When i inspect authentication object using eclipse the roles are not getting fetched from database
Here is the code for fetching user#Override
public User findByUserName(String username) {
return userRepository.findByUsername(username);
}
Do I need to add some additional logic to fetch roles along with user or Spring handles it behind the scene? Please let me know what I am doing wrong here... Thanks

Spring Security Set Role On Registration

I'm new to Spring security, so I've followed some tutorials but I'm having trouble understanding how the structure of roles really works under the hood. I have two tables, one for the User:
#Entity
#Table(name = "UserProfile", schema = "dbo", catalog = "DevTestTeam")
public class UserProfileEntity implements UserDetails{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private long id;
#Column(name = "enabled", nullable = false)
private boolean enabled;
#NotEmpty(message = "Enter a password.")
#Size(min = 6, max = 15, message = "Password must be between 6 and 15 characters.")
#Column(name = "password", nullable = true, length = 100)
private String password;
#NotEmpty(message = "Enter a username.")
#Size(min = 6, max = 20, message = "Username must be between 6 and 20 characters.")
#Column(name = "username", nullable = true, length = 20, unique = true)
private String username;
#OneToOne
#JoinColumn(name = "role_id")
private RoleEntity role;
public RoleEntity getRole() {
return role;
}
public void setRole(RoleEntity role) {
this.role = role;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return authorities;
}
and one for the role:
#Entity
#Table(name = "Role", schema = "dbo", catalog = "DevTestTeam")
public class RoleEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private long id;
#Column(name = "name", nullable = true, length = 255)
private String name;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
My confusion comes when creating a new user. I have a registration form backed by a UserProfileEntity object, and that populates the username and password. Then obviously it's easy to setEnabled()=true (I left some of the getters/setters out of this code for clarity).
My question is how to set the role when instantiating a UserProfileEntity to be saved in the database. My role_id foreign key should just take an integer and return the role from the Role table, but I'm not sure how to express this when instantiating. I have a ROLE_USER in the roles table with an id of 1, and I feel like this is pretty simple to instantiate but I can't find the answer I'm looking for.
UserImpl:
#Service
public class UserProfileServiceImpl implements UserProfileService{
#Autowired
private UserProfileDao userDao;
#Override
public UserProfileEntity findByUser(String username) {
return userDao.findByUsername(username);
}
#Override
public List<UserProfileEntity> findAll() {
List<UserProfileEntity> list = userDao.findAll();
return list;
}
#Override
public UserProfileEntity save(UserProfileEntity persisted) {
userDao.save(persisted);
return null;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserProfileEntity user = userDao.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found.");
}
return user;
}
}
You'll need some repository method to obtain user role by name:
RoleEntity roleEntity = roleEntityRepository.findByName("ROLE_USER");
Then set that RoleEntity to UserProfileEntity before persisting it:
UserProfileEntity userProfileEntity = new UserProfileEntity();
userProfileEntity.setRoleEntity(roleEntity);
userService.save(userProfileEntity);
What you also want is to leave your UserProfileEntity unextended. For Spring Security, you'll need UserDetailsService implementation:
#Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserProfileEntity userProfileEntity = userRepository.findByUsername(username);
if (userProfileEntity == null) {
throw new UsernameNotFoundException("Non existing user!");
}
return new org.springframework.security.core.userdetails.User(userProfileEntity.getUsername(),
userProfileEntity.getPassword(),
Arrays.asList(new SimpleGrantedAuthority(userByUsername.getRoleEntity().getName())));
}
}
However, I see that your requirements are quite simple - one role per user. Therefore, your RoleEntity could simply be an enum with predefined roles:
public enum RoleEntity {
ROLE_USER
}
And in UserProfileEntity you'd use it like this:
public class UserProfileEntity {
#Enumerated(EnumType.STRING)
private RoleEntity roleEntity;
}
To persist user with role:
UserProfileEntity userProfileEntity = new UserProfileEntity();
userProfileEntity.setRoleEntity(RoleEntity.USER);
userService.save(userProfileEntity);

Resources