简体   繁体   中英

How to audit Hibernate reads with Envers?

I've been working on application auditing and have successfully wired up Hibernate Envers 4.3.11 to capture create, update, and delete but I cannot find any documentation on auditing reads of entities.

Is this possible with Envers or would it be best to rely on log4j or similar?

I believe its not possible. We have similar use case and we use intercepters to generate audit records on our service .

Yes, it is possible with Hibernate Envers on auditing reads of entities. Here is an example to read a record from audit table for 1st revision.

/*Audit Reader*/

public AuditReader getAuditReader() {
        return  AuditReaderFactory.get(getCrntSession());
    }

/*The method to read a record from an entity*/

Integer revisionNumber = (Integer) getAuditReader().createQuery()
                .forRevisionsOfEntity(Employee.class, false, true)
                .addProjection(AuditEntity.revisionNumber().min())
                .getSingleResult();

        AuditReader reader = getAuditReader();
        Employee emp=reader.findRevision(Employee.class, (Number)revisionNumber);

/* and if Employee has an createdDate record , we can read it using below*/:
       System.out.println(emp.getCreatedDate());

Here's what was done to log reads. Some time has passed so I may be missing a detail, but I think this is everything.

Here's an Envers RevisionListener implementation. It gets the IP address and authenticated username and creates an entity that is written to the database on each read.

package com.example.audit;

import javax.servlet.http.HttpServletRequest;

import org.hibernate.envers.RevisionListener;
import com.example.utilities.RequestUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Component
public class UserRevisionListener implements RevisionListener {

    @Override
    public void newRevision(Object revisionEntity) {
        final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        Assert.notNull(auth, "Could not find an Authentication object for this user");
        Assert.notNull(auth.getPrincipal(), "Principal must not be null.");

        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder
                .currentRequestAttributes();
        Assert.notNull(servletRequestAttributes, "Current request attributes was null, remote address not attainable");

        HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();
        Assert.notNull(httpServletRequest, "Request was null, remote address not attainable");

        String remoteAddress = RequestUtils.getClientIpAddr(httpServletRequest);
        Assert.notNull(remoteAddress, "Remote address was null");

        String username = ((UserDetails) auth.getPrincipal()).getUsername();
        Assert.notNull(username, "Could not audit record due to missing username");

        UserRevisionEntity userRevisionEntity = (UserRevisionEntity) revisionEntity;
        userRevisionEntity.setUsername(username);
        userRevisionEntity.setIp(remoteAddress);
    }
}

Here's the Hibernate entity:

package com.example.audit;

import java.util.Set;

import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;

import org.hibernate.envers.DefaultRevisionEntity;
import org.hibernate.envers.ModifiedEntityNames;
import org.hibernate.envers.RevisionEntity;

@Entity
@RevisionEntity(UserRevisionListener.class)
public class UserRevisionEntity extends DefaultRevisionEntity {

    private static final long serialVersionUID = -3922860601141228497L;

    private String username;

    private String ip;

    @ElementCollection
    @JoinTable(name = "REVCHANGES", joinColumns = @JoinColumn(name = "REV"))
    @Column(name = "ENTITYNAME")
    @ModifiedEntityNames
    private Set<String> modifiedEntityNames;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

}

Here's RequestUtils.java :

package com.example.utilities;

import java.util.stream.Stream;

import javax.servlet.http.HttpServletRequest;

import org.springframework.util.Assert;

public final class RequestUtils {

    private static final String UNKNOWN = "unknown";

    private RequestUtils() {
    }

    /**
    * If any proxy or load balancer exists between the client and the server {@link HttpServletRequest#getRemoteAddr()}
    * will return localhost or the address of the middle machine.
    *
    * @param request
    *            a {@link HttpServletRequest}
    * @return The remote address of the client accessing the server.
    */
    public static String getClientIpAddr(HttpServletRequest request) {
        Assert.notNull(request, "Request must not be null");
        return Stream
                .of("X-Forwarded-For", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP",
                        "HTTP_X_FORWARDED_FOR")
                .filter(header -> validHeader(header, request))
                .map(header -> request.getHeader(header))
                .findFirst()
                .orElse(request.getRemoteAddr());
    }

    private static Boolean validHeader(String header, HttpServletRequest request) {
        String ip = request.getHeader(header);
        return !(ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip));
    }
}

It requires these properties:

spring.jpa.properties.org.hibernate.envers.audit_table_suffix=_audit
spring.jpa.properties.org.hibernate.envers.store_data_at_delete=true
spring.jpa.properties.org.hibernate.envers.audit_strategy=org.hibernate.envers.strategy.ValidityAuditStrategy
spring.jpa.properties.org.hibernate.envers.audit_strategy_validity_store_revend_timestamp=true
spring.jpa.properties.org.hibernate.envers.track_entities_changed_in_revision=true

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM