Spring Boot Auditing - map current user to #CreatedBy, #LastModifiedBy - java

So I have an audit class that is using #MappedSuperClass, it updates the values createdBy and updatedBy but it does not add a foreign key on the User entity, so there's no database validation
Here's the Audit Class
#Getter
#Setter
#MappedSuperclass
#JsonIgnoreProperties(
value = {"createdBy", "updatedBy"},
allowGetters = true
)
public abstract class UserDateAudit extends DateAudit {
#CreatedBy
#Column(name = "created_by", nullable = false, updatable = false)
public Long createdBy;
#LastModifiedBy
#Column(name = "updated_by", nullable = false)
public Long updatedBy;
}
I searched on how to do this using #Inheritance but did not completely get it.
so How do I achieve the connection between this class and User entity?
Edit 1
Here's the auditing configuration I implement.
#Configuration
#EnableJpaAuditing
public class AuditingConfig {
#Bean
public AuditorAware<Long> auditorProvider() {
return new SpringSecurityAuditAwareImpl();
}
}
class SpringSecurityAuditAwareImpl implements AuditorAware<Long> {
#Override
public Optional<Long> getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null ||
!authentication.isAuthenticated() ||
authentication instanceof AnonymousAuthenticationToken) {
return Optional.empty();
}
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
return Optional.ofNullable(userPrincipal.getId());
}
}
Edit 2
to set clear what exactly I mean
so how to use something other than #MappedSuperclass to be able achieve this "I want to be able to map the user reference in all tables that inherit the UserDateAudit so that it is a foreign key for all these table (which would add a validation that the user id actually exist) not just a regular column".

You need to implement the org.springframework.data.domain.AuditorAware interface and add the logic to retrieve currently logged in user in the getCurrentAuditor method.
#Component
#RequiredArgsConstructor
public class AuditorResolver implements AuditorAware<YOUR_TYPE> {
#Override
public Optional<YOUR_TYPE> getCurrentAuditor() {
//code to retrieve the currently logged in user and return the id/user object
}
}
See this for an example: https://github.com/gtiwari333/spring-boot-blog-app/blob/master/src/main/java/gt/app/config/AuditorResolver.java
The documentation also describes it beautifully: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#auditing.basics

Related

Spring JPA, Lazy Initialization and Dto [duplicate]

This question already has answers here:
How to fix Hibernate LazyInitializationException: failed to lazily initialize a collection of roles, could not initialize proxy - no Session
(15 answers)
Closed 2 years ago.
I am trying to fetch user's profile data post login (from login success filter) but I am seeing an exception for Lazy loading the data. Please see the following sample code:
AuthenticationSuccessHandler.java
#Component
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
#Autowired
private UserService userService;
#Autowired
private Gson gson;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException {
User user = (User) authentication.getPrincipal();
UserLoginResponseDto userLoginResponseDto = userService.login(user.getUsername());
response.setStatus(HttpStatus.OK.value());
response.setContentType("application/json; charset=UTF-8");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.getWriter().println(gson.toJson(userLoginResponseDto));
response.getWriter().flush();
clearAuthenticationAttributes(request);
}
}
UserService.java
public class UserService implements UserDetailsService, TokenService {
#Autowired
private UserRepository userRepository;
#Transactional(readOnly = true)
public UserLoginResponseDto login(String email) {
Optional<UserModel> userOptional = userRepository.findByEmailIgnoreCase(email);
UserModel userModel = userOptional.get();
UserLoginResponseDto userLoginResponseDto = userModel.toUserLoginResponseDto();
return userLoginResponseDto;
}
}
UserModel.java
public class UserModel {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false, unique = true, updatable = false)
private UUID id;
[A FEW MORE FIELDS]
#Column(length = 256, nullable = false, unique = true, updatable = false)
private String email;
#OneToMany(cascade = { CascadeType.ALL })
private List<RoleModel> roleModels;
public UserLoginResponseDto toUserLoginResponseDto() {
return new UserLoginResponseDto().setId(id).setEmail(email).setRoles(roleModels);
}
}
UserLoginResponseDto.java
public class UserLoginResponseDto {
private UUID id;
private String email;
private List<RoleModel> roles;
}
When an object of type UserLoginResponseDto is serialized in AuthenticationSuccessHandler, I see the following error message -
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: UserModel.roleModels, could not initialize proxy - no Session
QQ - How do I resolve this correctly without employing any of the following techniques?
[ANTIPATTERN] Open-In-View
[ANTIPATTERN] hibernate.enable_lazy_load_no_trans
FetchType.EAGER
Your problem is that you're passing the actual lazy List into setRoles, which doesn't trigger the full load. This indicates (immediately) that while you've separated your top-level database class from your top-level DTO, it's a "shallow" separation, which doesn't fully materialize the values. You haven't shown whether RoleModel is an entity or an embeddable, and that matters.
So the first step is to copy the items into a non-JPA form. If RoleModel embeddable (essentially a POJO), this could be as simple as setRoles(new ArrayList<>(roles)). Otherwise, you need a nested DTO, and at that point might consider something like MapStruct.
In either case, though, you're likely to run into the N+1 problem. You in fact do want an eager fetch in this case, and that's what JPA entity graph is for. You can tell Spring Data to fetch the list eagerly only when you want it, and this is a perfect example of when to do that.

org.hibernate.id.IdentifierGenerationException: "attempted to assign id from null one-to-one property"

I have a problem when i try to save the user object to database using Spring Boot 2.1.6.RELEASE and Spring Data JPA.
user object and detail object have a bidirectional one-to-one relation.
The id of detail have a foreign key to id of user(the id of user is autoincrement).
user information is map from json format to a object with #RequestBody.
json:
{"name" : "Jhon",
"detail":
{
"city" : "NY"
}
}
userController.java:
...
#PostMapping(value="/user")
public Boolean agregarSolicitiud(#RequestBody User user) throws ClassNotFoundException, InstantiationException, IllegalAccessException
{
userRepository.save(user);
...
User.java:
...
#Entity
public class User {
#Id
#Column(updatable=false, nullable=false, unique=true)
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#OneToOne(mappedBy = "solicitud",optional=false,cascade=CascadeType.ALL)
private UserDetail userDetail;
}
UserDetail.java:
...
#Entity
public class UserDetail {
#Id
#Column
private Long id;
#Column
private String city;
#MapsId
#OneToOne(optional = false,cascade = CascadeType.ALL)
#JoinColumn(name = "id", nullable = false)
private User user;
}
userRepository.java
...
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
...
Error:
org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property [proyect.model.Detail.user]
What can i do?
Thanks
By following this post I am able to save both the entities.
https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
Think of this as setting a relationship between the two java classes without using the persistence provider.These properties need to be set manually by the developer so that it can access the relation from the direction he wants.
This code needs to be added to the user entity which binds the userdetail appropriately
public void setUserDetail(UserDetail userDetail) {
if (userDetail == null) {
if (this.userDetail != null) {
this.userDetail.setUser(null);
}
} else {
userDetail.setUser(this);
}
this.userDetail = userDetail;
}
````
This code sets the user to the userDetails which is causing the issue.
As mentioned in the comments the deserializer is not able to bind the objects properly.The above code will binds the userDetails.user.

Hibernate One-to-One DTO-Entity population

I have 2 entities in my DB with one-to-one one directional mapping:
User and PasswordResetToken. The idea behind this is to create new token each time user requests password reset and store only the latest one.
Below are my entities:
#Entity
#Table(name = "USERS")
#Getter #Setter
public class User implements Serializable {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.AUTO, generator = "usersSeq")
#SequenceGenerator(name = "usersSeq", sequenceName = "SEQ_USERS", allocationSize = 1)
private long id;
#Column(name = "NAME")
private String name;
#Column(name = "PASSWORD")
private String password;
#Column(name = "EMAIL")
private String email;
#Column(name = "ROLE")
private Integer role;
}
///...
#Entity
#Table(name = "PASSWORD_RESET_TOKENS")
#Getter
#Setter
public class PasswordResetToken implements Serializable {
private static final int EXPIRATION = 24;
#Column(name = "TOKEN")
private String token;
#Id
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(nullable = false, name = "user_id")
private User user;
#Column(name = "EXPIRY_DATE")
private Instant expiryDate;
public PasswordResetToken() {
}
public void setExpiryDate(ZonedDateTime expiryDate) {
this.expiryDate = expiryDate.plus(EXPIRATION, ChronoUnit.HOURS).toInstant();
}
}
Also, I have DTOs created for both of them to pass them around my app.
Code snippets:
#Getter #Setter
public class PasswordResetTokenModel {
private String token;
private ZonedDateTime expiryDate;
private UserModel user;
}
UserModel is also used for Spring Security
#Getter
#Setter
public class UserModel extends User {
public UserModel(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
private long id;
private String name;
public String getEmail() {
return this.getUsername();
}
}
For population I've created 2 populators:
#Component
public class UserPopulatorImpl implements UserPopulator {
#Autowired
UserDetailsService userDetailsService;
#Override
public UserModel populateToDTO(User user) {
UserModel userModel = new UserModel(user.getEmail(), user.getPassword(), userDetailsService.getAuthorities(user.getRole()));
userModel.setId(user.getId());
return userModel;
}
#Override
public User populateToDAO(UserModel userModel) {
User user = new User();
user.setEmail(userModel.getEmail());
user.setName(userModel.getName());
user.setPassword(userModel.getPassword());
//TODO: change it!
user.setRole(1);
return user;
}
}
//...
#Component
public class PasswordResetTokenPopulatorImpl implements PasswordResetTokenPopulator {
#Autowired
UserPopulator userPopulator;
#Override
public PasswordResetTokenModel populateToDTO(PasswordResetToken passwordResetToken) {
PasswordResetTokenModel passwordResetTokenModel = new PasswordResetTokenModel();
passwordResetTokenModel.setUser(userPopulator.populateToDTO(passwordResetToken.getUser()));
passwordResetTokenModel.setToken(passwordResetToken.getToken());
passwordResetTokenModel.setExpiryDate(ZonedDateTime.ofInstant(passwordResetToken.getExpiryDate(), ZoneId.systemDefault()));
return passwordResetTokenModel;
}
#Override
public PasswordResetToken populateToDAO(PasswordResetTokenModel passwordResetTokenModel) {
PasswordResetToken passwordResetToken = new PasswordResetToken();
passwordResetToken.setExpiryDate(passwordResetTokenModel.getExpiryDate());
passwordResetToken.setUser(userPopulator.populateToDAO(passwordResetTokenModel.getUser()));
passwordResetToken.setToken(passwordResetTokenModel.getToken());
return passwordResetToken;
}
}
I'm saving object using
sessionFactory.getCurrentSession().saveOrUpdate(token);
When I use this code, I'm getting following exception
object references an unsaved transient instance - save the transient instance before flushing: com.demo.megaevents.entities.User
There are currently 2 issues in this code:
Seems like Cascade.ALL in my OneToOne mapping is not working. If
I create separate primary key in Token class everything works almost
as expected but storing every created token in DB (more like
OneToMany relation), however I want to avoid it as I need to store
only one token per user in my DB
I don't like using new in populators, as it forces hibernate to create new object while flushing session. However, I also don't want to do another select to fetch this data from DB because just before mentioned populator I already do this query to fetch it and I think that it's an overhead.
Also, I really want to have DTOs and I don't want to remove DTO layer.
So, my questions:
What is the correct way to handle population between DTO and entities?
Are there any other improvements (probably architectural) to my solution?
Thanks a lot.
I'm not sure why you would let UserModel extend User, but I guess you did that because you didn't want to have to copy all properties from User into UserModel. Too bad, because that's what is going to be needed to have a clean separation between the entity model and data transfer model.
You get that exception because you try to persist a PasswordResetToken that has a reference to a User object with an id, but the User isn't associated with the current session. You don't have to query the user, but at least association it with the session like this:
PasswordResetToken token = // wherever you get that from
Session s = sessionFactory.getCurrentSession();
token.setUser(s.load(User.class, token.getUser().getId());
s.persist(token);
Cascading would cause the User to be created/inserted or updated via a SQL INSERT or UPDATE statement which is apparently not what you want.
You could do the Session.load() call in you populators if you want, but I'd not do that. Actually I would recommend not having populators at all, but instead create the entity objects in your service instead.
Normally you only have a few(mostly 1) ways of actually creating a new entity object, so the full extent of the transformation from DTO to entity will only be relevant in very few cases.
Most of the time you are going to do an update and for that, you should first select the existing entity and apply the fields that are allowed to be changed from the DTO on the entity object.
For providing the presentation layer with DTOs I would recommend using Blaze-Persistence Entity Views to avoid the manual mapping boilerplate and also improve performance of select queries.

Choosing which attributes to publish in RESTful web service

Given a User entity with the following attributes mapped:
#Entity
#Table(name = "user")
public class User {
//...
#Id
#GeneratedValue
#Column(name = "user_id")
private Long id;
#Column(name = "user_email")
private String email;
#Column(name = "user_password")
private String password;
#Column(name = "user_type")
#Enumerated(EnumType.STRING)
private UserType type;
#Column(name = "user_registered_date")
private Timestamp registeredDate;
#Column(name = "user_dob")
#Temporal(TemporalType.DATE)
private Date dateOfBirth;
//...getters and setters
}
I have created a controller method that returns a user by ID.
#RestController
public class UserController {
//...
#RequestMapping(
value = "/api/users/{id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> getUser(#PathVariable("id") Long id) {
User user = userService.findOne(id);
if (user != null) {
return new ResponseEntity<User>(user, HttpStatus.OK);
}
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
}
//...
}
A service in my business logic layer.
public class UserServiceBean implements UserService {
//...
public User findOne(Long id) {
User user = userRepository.findOne(id);
return user;
}
//...
}
And a repository in my data layer.
public interface UserRepository extends JpaRepository<User, Long> {
}
This works fine, it returns everything about the user, but I use this in several different parts of my application, and have cases when I only want specific fields of the user.
I am learning spring-boot to create web services, and was wondering: Given the current implementation, is there a way of picking the attributes I want to publish in a web service?
If not, what should I change in my implementation to be able to do this?
Thanks.
Firstly, I agree on using DTOs, but if it just a dummy PoC, you can use #JsonIgnore (jackson annotation) in User attributes to avoid serializing them, for example:
#Entity
#Table(name = "user")
public class User {
//...
#Column(name = "user_password")
#JsonIgnore
private String password;
But you can see there, since you are not using DTOs, you would be mixing JPA and Jackson annotations (awful!)
More info about jackson: https://github.com/FasterXML/jackson-annotations

How to secure RepositoryRestController

Let's say I have 2 have to entities:
#Entity
public class Post {
#NotEmpty
private String title;
#NotEmpty
#Lob
private String html;
#NotEmpty
#Lob
private String text;
#ManyToOne
private Topic topic;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "content_media", joinColumns = {#JoinColumn(name = "content_id")}, inverseJoinColumns = {#JoinColumn(name = "media_id")})
private Set<Media> medias = new HashSet<>();
#CreatedBy
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn
private User createdBy;
#LastModifiedBy
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn
private User lastModifiedBy;
...
}
#Entity
public class Media {
#NotEmpty
private String localPath;
#NotEmpty
private String fileName;
private long fileLength;
private String fileType;
private int focusPointX;
private int focusPointY;
...
}
And I'm exposing them using:
#RepositoryRestController
public interface MediaRepository extends JpaRepository<Media, Long> {
}
#RepositoryRestController
public interface PostRepository extends JpaRepository<Post, Long> {
}
I want these controllers to be secure. Let me explain myself.
If logged in user does not have ROLE_ADMIN, Medias should only be
accessable through posts and /medias/ should return 403 or 404
Only users that have ROLE_USER should be able to create to posts
Only the user that have created the post or the ones that have the ROLE_ADMIN should be able to update post.
Only the users that have ROLE_ADMIN should be able to delete posts
Is there a way to do these using RepositoryRestController and Spring Security or RepositoryRestController is for public resources only and I should write service layer myself using RestController?
Yes you can directly use Spring Security with Spring Data REST. You need to define the security of your routes using Spring Security Configuration as shown below:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests().
antMatchers(HttpMethod.POST, "/posts").hasRole("USER").
antMatchers(HttpMethod.DELETE, "/posts/**").hasRole("ADMIN").and().
csrf().disable();
}
}
Repository methods will be secured using Spring Security annotations. e.g.
#RepositoryRestController
public interface PostRepository extends JpaRepository<Post, Long> {
#Override
#PreAuthorize("hasRole('ROLE_ADMIN')")
void delete(Long aLong);
}
Code above is just a pointer. You can customize it as per your needs. Here is the link to Spring Data examples repository.
Update
To handle the update of the post by the user who created or by any user who is in ADMIN_ROLE you need to create a controller class and define a method with to handle the update
#RequestMapping(method={RequestMethod.PUT}, value={"posts/{id}"})
public void updatePost(#PathVariable("id") Long id, HttpServletRequest request)
{
//Fetch the authenticated user name
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername();
}
// Make a database call to verify if the user is owner of the post
Post post = postRepository.getPostByUserName(String username, Long postId);
if (post == null && !request.isUserInRole("ADMIN");) {
//return 403 error code
}
//proceed with the update
}

Categories

Resources