Overriding user injected by #CreatedBy Annotation in AuditorAware Implementation - spring

Following is my implementation of AuditAware.
#Component
public class AuditorAwareImpl implements AuditorAware<String> {
#Log
private Logger logger;
#Override
public String getCurrentAuditor() {
String user = "SYSTEM";
try {
final Subject subject = SecurityUtils.getSubject();
if (subject != null) {
final Session session = subject.getSession(false);
if (session != null) {
final User userObj = (User) session.getAttribute("user");
if (userObj != null) {
user = userObj.getUsername();
}
}
}
} catch (final Exception e) {
this.logger.error("getCurrentAuditor", "No logged in user information found.", e);
}
return user;
}
}
As we know, The value of user returned by AuditorAwareImpl injected by Spring in attribute marked by #CreatedBy annotation as below.
#CreatedBy
#Column(name = "CREATED_BY", length = 150, nullable = false, updatable = false)
private String createdBy;
There are two problems I want to solve.
I am triggering a separate thread which saves
thousands of objects in database. If session times out in between, session object becomes null and AuditAwareImpl throws error and hence background process errors out.
I also want to run few quartz Schedular related jobs in which in which session object is itself not available. But I still want to inject some hard coded user in that case. But how do I do it?
One thing that I have tried at individual object level, using following code is,
#Transient
private String overRiddenUser;
#PrePersist
public void updateCreatedBy(){
if(overRiddenUser!=null){
this.createdBy=overRiddenUser;
}
}
I explicitly set overRiddenUser="Admin" whenever I want to override user injected by spring in createdBy and overwrite it using PrePersist method. But even this does not seem to work since before even I overwrite this value, spring tries to populate created by using value returned by AuditAware. If session is already expired by then the process errors outs! How do I solve this?

I found the solution to above problems as below.
Step 1
I defined a bean as below that contains a HashMap. This map will store current thread name as key and mapped username as value.
public class OverRiddenUser {
Map<String,String> userThreadMap= new HashMap<>();
//add getters/setters
}
Step 2
Initialised this bean on startup in class annoted by #Configuration.
#Bean("overRiddenUser")
public OverRiddenUser overRideUser(){
OverRiddenUser obj = new OverRiddenUser();
return obj;
// Singleton implementation can be done her if required
}
Step 3
Auto wired this bean in the impl class where I want to override session user.
#Autowired
OverRiddenUser overRiddenUser;
Assigned a random name to currently running thread.
RandomString randomThreadName = new RandomString(9);
Thread.currentThread().setName(randomThreadName.toString());
And then put required user name for current thread in hashmap as below.
try {
if(overRiddenUser!=null){
overRiddenUser.getUserThreadMap().put(Thread.currentThread().getName(),"My Over ridden username");
}
// Entire Database related code here
} catch (Exception e){
// handle the exceptions thrown by code
} finally {
//Perform clean up here
if(overRiddenUser!=null){
overRiddenUser.getUserThreadMap().remove(Thread.currentThread().getName());
}
}
}
Step 4
Then I modified AuditAwareImpl as below.
#Component
public class AuditorAwareImpl implements AuditorAware<String> {
#Log
private Logger logger;
#Autowired
OverRiddenUser overRiddenUser;
#Override
public String getCurrentAuditor() {
String user = "SYSTEM";
try {
if(overRiddenUser!=null && overRiddenUser.getUserThreadMap()!=null && overRiddenUser.getUserThreadMap().get(Thread.currentThread().getName())!=null){
user=overRiddenUser.getUserThreadMap().get(Thread.currentThread().getName());
}else{
final Subject subject = SecurityUtils.getSubject();
if (subject != null) {
final Session session = subject.getSession(false);
if (session != null) {
final User userObj = (User) session.getAttribute("user");
if (userObj != null) {
user = userObj.getUsername();
}
}
}
}
} catch (final Exception e) {
this.logger.error("getCurrentAuditor", "No logged in user information found.", e);
}
return user;
}
}
So if the currently running thread accesses AuditAwareImpl then it gets custom user name which was set for that thread. If this is null then it pulls username from session as it was doing earlier. Hope that helps.

Related

Derive tenant id for signed in user in OAuth2 based multi-tenant Spring Boot application using Spring Security

I am working on a Spring Boot B2B application, where I would like to onboard a considerable number of tenants. Each of the tenants has its own authentication providers. I have decided to support only OAuth2-based authentication.
As of now, my application has been single-tenant, that's why I did not have the need to derive tenant id for any user. But with multi-tenancy, I need to serve the resources based on the tenant, the user belongs, what role the user has for the given tenant, etc. In other words, each and every flow in my application is going to be dependent on the tenant id information of a user.
In order to achieve the same, I have added tenantId column to the existing User entity as follows:
#Entity
#Table(name = "user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private String id;
#NotNull
private String login;
#Column(name = "first_name", length = 50)
private String firstName;
#Column(name = "last_name", length = 50)
private String lastName;
#Email
#Column(length = 254, unique = true)
private String email;
...
#Column(length = 254, unique = true)
private String tenantId;
...
}
Now, I am deriving and capturing tenant id information for a given user at the time of signup to my application as follows:
User getUser(AbstractAuthenticationToken authToken) { <---THIS FUNCTION GETS CALLED WHILE USER SIGNS UP FOR THE APPLICATION
Map<String, Object> attributes;
User user = null;
if (authToken instanceof OAuth2AuthenticationToken) {
attributes = ((OAuth2AuthenticationToken) authToken).getPrincipal().getAttributes();
} else if (authToken instanceof JwtAuthenticationToken) {
attributes = ((JwtAuthenticationToken) authToken).getTokenAttributes();
} else {
throw new IllegalArgumentException("AuthenticationToken is not OAuth2 or JWT!");
}
if (user == null) {
user = getUser(attributes);
}
return Pair.of(user, attributes);
}
private static User getUser(Map<String, Object> details) {
User user = new User();
if (details.get("uid") != null) {
user.setId((String) details.get("uid"));
user.setLogin((String) details.get("sub"));
} else if (details.get("user_id") != null) {
user.setId((String) details.get("user_id"));
} else {
user.setId((String) details.get("sub"));
}
if (details.get("email") != null) {
user.setEmail(((String) details.get("email")).toLowerCase());
} else {
user.setEmail((String) details.get("sub"));
}
user.setTenantId(getTenantIdFromEmail(user.getEmail()));
...
}
private getTenantIdFromEmail(String email) {
String subDomain = getSubDomain(email);
return tenantRepository.findBySubDomainName(subDomain).getTenantId();
}
Now, whenever I need tenant id information for a signed-in user, I can do like below:
public String getTenantId() {
Optional<User> signedInUserOptional =
userRepository.findByLogin(SecurityUtils.getCurrentUserLogin();
return signedInUserOptional.isPresent() ? signedInUserOptional.get().getTenantId() : null;
}
Here, I have two questions as follows:
I am not sure if the code to derive tenant id would work correctly across different Ouath2 authentication providers of multiple tenants, if not, could anyone please help here? (by different authentication providers, I meant, each of the tenants may have their own internal authentication providers)
In almost each application flow (for example, doing any CRUD operation on a resource), I have to derive tenant id information as mentioned above. Is there any way to make this a little efficient i.e. compute it once for a signed-in user and then pass it across the entire application flow for the user session?
Consider using ThreadLocal variables and set this during successful authentication ex:OAuthFilter in your case.
public class TenantIdContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setCurrentTenant(String tenantId) {
contextHolder.set(tenantId);
}
public static String getCurrentTenant() {
return contextHolder.get();
}
public static void clear() {
contextHolder.remove();
}
}
Usage
private static User getUser(Map<String, Object> details) {
User user = new User();
if (details.get("uid") != null) {
user.setId((String) details.get("uid"));
user.setLogin((String) details.get("sub"));
} else if (details.get("user_id") != null) {
user.setId((String) details.get("user_id"));
} else {
user.setId((String) details.get("sub"));
}
if (details.get("email") != null) {
user.setEmail(((String) details.get("email")).toLowerCase());
} else {
user.setEmail((String) details.get("sub"));
}
TenantIdContextHolder.setCurrentTenant(getTenantIdFromEmail(user.getEmail()));
...
}
Getting current tenant anytime
String currentTenant = TenantIdContextHolder.getCurrentTenant();

How to achieve row level authorization in spring-boot?

Assuming I've the following endpoints in spring boot
GET /todo
DELETE /todo/{id}
How can ensure that only entries for the userid are returned and that the user can only update his own todos?
I've a populated Authentication object.
Is there any build in way I can use? Or just make sure to always call findXyzByIdAndUserId where userid is always retrieved from the Principal?
I'm a bit worried about the possibility to forget the check and displaying entries from other users.
My approach to this would be a 3 way implementation: (using jpa & hibernate)
a user request context
a mapped superclass to get your context
a statement inspector to inject your userid
For example:
public final class UserRequestContext {
public static String getUserId() {
// code to retrieve your userid and throw when there is none!
if (userId == null) throw new IllegalStateException("userid null");
return userId;
}
}
#MappedSuperclass
public class UserResolver {
public static final String USER_RESOLVER = "USER_RESOLVER";
#Access(AccessType.PROPERTY)
public String getUserId() {
return UserRequestContext.getUserId();
}
}
#Component
public class UserInspector implements StatementInspector {
#Override
public String inspect(String statement) {
if (statement.contains(UserResolver.USER_RESOLVER)) {
statement = statement.replace(UserResolver.USER_RESOLVER, "userId = '" + UserRequestContext.getUserId() + "'" );
}
return sql;
}
#Bean
public HibernatePropertyCustomizer hibernatePropertyCustomizer() {
return hibernateProperies -> hibernateProperties.put("hibernate.session_factory.statement_inspector",
UserInspector.class.getName());
}
}
So your Entity looks like this:
#Entity
...
#Where(clause = UserResolver.USER_RESOLVER)
public class Todo extends UserResolver {
...
}

No session to write JSON lazy load

I'm just starting with spring and hibernate. I'm trying to create some basic service using DAO.
Here is one of it:
#SuppressWarnings("unchecked")
#Override
public Users findByUserId(int id) {
List<Users> users = new ArrayList<Users>();
if(getSessionFactory() != null) {
try {
session = getSessionFactory().getCurrentSession();
users = session
.createQuery("from Users where id=?")
.setParameter(0, id)
.list();
} catch (HibernateException e) {
LOGGER.error("HibernateException: " + e);
}
}
if (!users.isEmpty()) {
return users.get(0);
} else {
return null;
}
}
And I called this service from a controller:
#RestController
public class JSONController {
#Autowired
private UserDao userDao;
#RequestMapping(value = "/rest/userbyid/{id}",
method = RequestMethod.GET,
headers = "Accept=application/json")
public Users getUserById(#PathVariable("id") int id) {
return userDao.findByUserId(id);
}
}
I understand that the session had been closed when the process come to controller. I can solve this by using openSession() method.
I just want to know is there any better way to handle this? Better still using getCurrentSession() (or any).
It's not good to return Entity to be serialized in controller. Imagine serializer which invokes all methods and even lazy collections.
For User instance it calls let's say getProjects() lazy method, then for each Project returned it agains call getUser() and so on.
Good practice is to define Service layer and return DTO (Data Transfer Object) which contains only necessary fields.
There is alternative approach to unproxy entities before return defining depth.
#SuppressWarnings("unchecked")
protected T unproxy(T entity){
if (entity == null) {
return null;
}
if (entity instanceof HibernateProxy) {
try {
Hibernate.initialize(entity);
} catch (ObjectNotFoundException e) {
return null;
}
entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation();
}
return entity;
}
From
https://gist.github.com/maikelsperandio/6889130

OptimisticLockException not thrown when version has changed in spring-boot project

Model structure:
#MappedSuperclass
public class BaseModel<K extends Comparable> implements Serializable, Comparable<Object> {
private static final long serialVersionUID = 1L;
#Id
private K id;
#Version
private Integer version;
// getter/setter
}
#Entity
public class MyEntity extends BaseModel<String> {
// some fields and it's getter/setter
}
Record in my database for my_entity:
id: 1
version: 1
...
Below is my update method:
void update(String id, Integer currentVersion, ....) {
MyEntity myEntity = myRepository.findOne(id);
myEntity.setVersion(currentVersion);
// other assignments
myRepository.save(myEntity);
}
Below is the query being fired when this method is invoked.
update my_entity set version=?, x=?, y=?, ...
where id=? and version=?
I am expecting OptimisticLockException when currentVersion passed in above method is other than 1.
Can any body help me why I am not getting OptimisticLockException?
I am using spring-boot for my webmvc project.
Section 11.1.54 of the JPA specification notes that:
In general, fields or properties that are specified with the Version
annotation should not be updated by the application.
From experience, I can advise that some JPA providers (OpenJPA being one) actually throw an exception should you try to manually update the version field.
While not strictly an answer to your question, you can re-factor as below to ensure both portability between JPA providers and strict compliance with the JPA specification:
public void update(String id, Integer currentVersion) throws MyWrappedException {
MyEntity myEntity = myRepository.findOne(id);
if(currentVersion != myEntity.getVersion()){
throw new MyWrappedException();
}
myRepository.save(myEntity);
//still an issue here however: see below
}
Assuming your update(...) method is running in a transaction however you still have an issue with the above as section 3.4.5 of the JPA specification notes:
3.4.5 OptimisticLockException Provider implementations may defer writing to the database until the end of the transaction, when
consistent with the lock mode and flush mode settings in effect. In
this case, an optimistic lock check may not occur until commit time,
and the OptimisticLockException may be thrown in the "before
completion" phase of the commit. If the OptimisticLockException must
be caught or handled by the application, the flush method should be
used by the application to force the database writes to occur. This
will allow the application to catch and handle optimistic lock
exceptions.
Essentially then, 2 users can submit concurrent modifications for the same Entity. Both threads can pass the initial check however one will fail when the updates are flushed to the database which may be on transaction commit i.e. after your method has completed.
In order that you can catch and handle the OptimisticLock exception, your code should then look something like the below:
public void update(String id, Integer currentVersion) throws MyWrappedException {
MyEntity myEntity = myRepository.findOne(id);
if(currentVersion != myEntity.getVersion()){
throw new MyWrappedException();
}
myRepository.save(myEntity);
try{
myRepository.flush()
}
catch(OptimisticLockingFailureException ex){
throw new MyWrappedException();
}
}
Use EVICT before updating when using JPA. I did not get the #Version to work either. The property was increased but no exception was thrown when updating an object that had the wrong version-property.
The only thing I have got to work is to first EVICT the object and then save it. Then the HibernateOptimisticLockingException is thrown if the Version properties does not match.
Set the hibernates ShowSQL to 'true' to verify that the actual update sql ends with "where id=? and version=?". If the object is not evicted first, the update statement only has "where id=?", and that will (for obvious reasons) not work.
Optimistic hibernation lock works out of the box (You don't must put a version for Entity):
#Entity
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long quantity;
private Long likes;
#Version
private Long version;
public Product() {
}
//setter and getter
//equals and hashcode
repository
public interface ProductRepository extends JpaRepository<Product, Long> {}
service
#Service
public class ProductOptimisticLockingService {
private final ProductRepository productRepository;
public ProductOptimisticLockingService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
#Transactional(readOnly = true)
public Product findById(Long id, String nameThread){
Product product =
productRepository
.findById(id)
.get();
System.out.printf(
"\n Select (%s) .... " +
"(id:) %d | (likes:) %d | (quantity:) %d | (version:) %d \n",
nameThread,
product.getId(),
product.getLikes(),
product.getQuantity(),
product.getVersion()
);
return product;
}
#Transactional(isolation = Isolation.READ_COMMITTED)
public void updateWithOptimisticLocking(Product product, String nameThread) {
try {
productRepository.save(product);
} catch (ObjectOptimisticLockingFailureException ex) {
System.out.printf(
"\n (%s) Another transaction is already working with a string with an ID: %d \n",
nameThread,
product.getId()
);
}
System.out.printf("\n--- Update has been performed (%s)---\n", nameThread);
}
}
test
#SpringBootTest
class ProductOptimisticLockingServiceTest {
#Autowired
private ProductOptimisticLockingService productService;
#Autowired
private ProductRepository productRepository;
#Test
void saveWithOptimisticLocking() {
/*ID may be - 1 or another. You must put the ID to pass in your methods. You must think how to write right your tests*/
Product product = new Product();
product.setLikes(7L);
product.setQuantity(5L);
productRepository.save(product);
ExecutorService executor = Executors.newFixedThreadPool(2);
Lock lockService = new ReentrantLock();
Runnable taskForAlice = makeTaskForAlice(lockService);
Runnable taskForBob = makeTaskForBob(lockService);
executor.submit(taskForAlice);
executor.submit(taskForBob);
executorServiceMethod(executor);
}
/*------ Alice-----*/
private Runnable makeTaskForAlice(Lock lockService){
return () -> {
System.out.println("Thread-1 - Alice");
Product product;
lockService.lock();
try{
product = productService
.findById(1L, "Thread-1 - Alice");
}finally {
lockService.unlock();
}
setPause(1000L); /*a pause is needed in order for the 2nd transaction to attempt
read the line from which the 1st transaction started working*/
lockService.lock();
try{
product.setQuantity(6L);
product.setLikes(7L);
update(product,"Thread-1 - Alice");
}finally {
lockService.unlock();
}
System.out.println("Thread-1 - Alice - end");
};
}
/*------ Bob-----*/
private Runnable makeTaskForBob(Lock lockService){
return () -> {
/*the pause makes it possible to start the transaction first
from Alice*/
setPause(50L);
System.out.println("Thread-2 - Bob");
Product product;
lockService.lock();
try{
product = findProduct("Thread-2 - Bob");
}finally {
lockService.unlock();
}
setPause(3000L); /*a pause is needed in order for the 1st transaction to update
the string that the 2nd transaction is trying to work with*/
lockService.lock();
try{
product.setQuantity(5L);
product.setLikes(10L);
update(product,"Thread-2 - Bob");
}finally {
lockService.unlock();
}
System.out.println("Thread-2 - Bob - end");
};
}
private void update(Product product, String nameThread){
productService
.updateWithOptimisticLocking(product, nameThread);
}
private Product findProduct(String nameThread){
return productService
.findById(1L, nameThread);
}
private void setPause(long timeOut){
try {
TimeUnit.MILLISECONDS.sleep(timeOut);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void executorServiceMethod(ExecutorService executor){
try {
executor.awaitTermination(10L, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.shutdown();
}
}

Spring , Transactions , Hibernate Filters

I am using declarative transactions in Spring. I have a service layer which is annotated with "Transactional". This service layer calls the DAO. I need to enable a hibernate filter in all the dao methods. I don't want to have to explicitly call teh session.enablefilter each time. So is there a way using spring transaction aop etc such that a intercepter can be called when the hibernate session is created?
My Service layer:
#Service("customerViewService")
#Transactional
public class CustomerViewServiceImpl extends UFActiveSession implements CustomerViewService {
private static final Logger log = LoggerFactory.getLogger(CustomerViewServiceImpl.class);
private CustomerDAO daoInstance = null;
private CustomerDAO getCustomerDAO() {
if (daoInstance == null)
daoInstance = DAOFactory.getDao(CustomerDAO.class);
return daoInstance;
}
#Transactional(propagation=Propagation.REQUIRED, rollbackFor=DAOException.class)
public CustomerModel getCustomerModel() throws UFClientException {
CustomerModel model = null;
try {
Customer customerTbl = getCustomerDAO().getCustomerDetail(getUserName());
if (customerTbl == null) {
log.error("DAO-02: No entry found for Customer id- " + getUserName());
throw new UFClientException("DAO-02");
}
model = DozerConverter.hibernateToDto(customerTbl, CustomerModel.class);
}
catch (DAOException e) {
log.error("DAO-01: Not able to fetch entry from database for customer.");
throw new UFClientException();
}
return model;
}
}
My Dao Layer
public class CustomerDAOImpl extends HibernateDaoSupport implements CustomerDAO {
#SuppressWarnings("unchecked")
public Customer getCustomerDetail(String email) throws DAOException {
try {
List<Customer> customers = getHibernateTemplate().find(sb.toString(), email);
if (customers.size() == 0)
return null;
return customers.get(0);
}
catch (Exception e) {
throw new DAOException(e);
}
}
Appreciate your help!!
You can create your own interceptor, and apply it to methods that havbe transactional:
#AroundInvoke("#annotation(transactional)")
public ... handle(Transactional transactional) {
...
}

Resources