JPA AuditorAware not getting created for each user - spring

I have implemented auditing using JPA auditing. My code looks like this:
#Configuration
#EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class JpaConfiguration {
#Bean
#Scope(value= ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public AuditorAware<String> auditorAware() {
final String currentUser;
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(null != authentication) {
currentUser = authentication.getName();
} else {
currentUser = null;
}
return () -> Optional.ofNullable(currentUser);
}
}
The issue I am facing is if I login with one user and perform some operation, it's working fine. But when I logout and login with another user, It's still using the last user only.
After debugging the code what I found is spring not creating bean of AuditorAware for each user. It's behaving like singleton bean. Even I specify the scope as prototype also, still it's behaving like singleton.

The AuditorAware is supposed to be a singleton. You should retrieve the current user, each time the AuditAware.getCurrentAuditor is called. Not just once.
Rewrite your code to something like this.
#Bean
public AuditorAware<String> auditorAware() {
return () -> getCurrentAuthentication().map(Authentication::getName());
}
private Optional<Authentication> getCurrentAuthentication() {
return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication());
}

Related

Togglz - Username Activation Strategy Implementation

I am trying to implement UsernameActivation Startegy in Springboot using togglz, but due to insufficient example/documentation on this, I am unable to do so. It is a simple Poc in maven. Here are my classes:
public enum Features implements Feature{
#Label("just a description")
#EnabledByDefault
HELLO_WORLD,
#Label("Hello World Feature")
#DefaultActivationStrategy(id = UsernameActivationStrategy.ID, parameters =
{#ActivationParameter(name = UsernameActivationStrategy.PARAM_USERS, value = "suga")
})
HELLO,
#Label("another descrition")
#EnabledByDefault
REVERSE_GREETING;
public boolean isActive() {
return FeatureContext.getFeatureManager().isActive(this);
}
}
#Component
public class Togglz implements TogglzConfig {
public Class<? extends Feature> getFeatureClass() {
return Features.class;
}
public StateRepository getStateRepository() {
return new FileBasedStateRepository(new File("/tmp/features.properties"));
}
public UserProvider getUserProvider() {
return new SpringSecurityUserProvider("ADMIN_ROLE");
}
}
I want to use UsernameActivation strategy but i am not sure what more code changes I need to do in order for it to work. I do know that it is somehow related to UserProvider. Also, I am not sure how will it compare the username value and how will it capture the current user value. Any idea around this will be of great help!
I had to override the getUserProvider method. Since I am using Spring for auto-configuration, and not extending ToggleConfig, I added this as a bean to load at startup.
#Bean
public UserProvider getUserProvider() {
return new UserProvider() {
#Override
public FeatureUser getCurrentUser() {
String username = <MyAppSecurityProvider>.getUserName();
boolean isAdmin = "admin".equals(username);
return new SimpleFeatureUser(username, isAdmin);
}
};
}
Note: I had to do this as my app uses our inbuilt security mechanism. Reading the documentation, it looks like its easier if you are using standard security such as Spring or Servlet.
My config in application.yml(the same thing you have in the annotation)
togglz:
features:
FRIST_FEATURE:
enabled: true
strategy: username
param:
users: user1,user2
SECOND_FEATURE:
enabled: true
strategy: username
param:
users: user2,user3

How to make AuditorAware work with Spring Data Mongo Reactive

Spring Security 5 provides a ReactiveSecurityContextHolder to fetch the SecurityContext from a Reactive context, but when I want to implement AuditorAware and get audition work automatically, but it does not work. Currently I can not find a Reactive variant for AuditorAware.
#Bean
public AuditorAware<Username> auditor() {
return () -> ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.log()
.filter(a -> a != null && a.isAuthenticated())
.map(Authentication::getPrincipal)
.cast(UserDetails.class)
.map(auth -> new Username(auth.getName()))
.switchIfEmpty(Mono.empty())
.blockOptional();
}
I have added #EnableMongoAuduting on my boot Application class.
On the Mongo document class. I added audition related annotations.
#CreatedDate
private LocalDateTime createdDate;
#CreatedBy
private Username author;
When I added a post, the createdDate is filled, but author is null.
{"id":"5a49ccdb9222971f40a4ada1","title":"my first post","content":"content of my first post","createdDate":"2018-01-01T13:53:31.234","author":null}
The complete codes is here, based on Spring Boot 2.0.0.M7.
Update: Spring Boot 2.4.0-M2/Spring Data Common 2.4.0-M2/Spring Data Mongo 3.1.0-M2 includes a ReactiveAuditorAware, Check this new sample, Note: use #EnableReactiveMongoAuditing to activiate it.
I am posting another solution which counts with input id to support update operations:
#Component
#RequiredArgsConstructor
public class AuditCallback implements ReactiveBeforeConvertCallback<AuditableEntity> {
private final ReactiveMongoTemplate mongoTemplate;
private Mono<?> exists(Object id, Class<?> entityClass) {
if (id == null) {
return Mono.empty();
}
return mongoTemplate.findById(id, entityClass);
}
#Override
public Publisher<AuditableEntity> onBeforeConvert(AuditableEntity entity, String collection) {
var securityContext = ReactiveSecurityContextHolder.getContext();
return securityContext
.zipWith(exists(entity.getId(), entity.getClass()))
.map(tuple2 -> {
var auditableEntity = (AuditableEntity) tuple2.getT2();
auditableEntity.setLastModifiedBy(tuple2.getT1().getAuthentication().getName());
auditableEntity.setLastModifiedDate(Instant.now());
return auditableEntity;
})
.switchIfEmpty(Mono.zip(securityContext, Mono.just(entity))
.map(tuple2 -> {
var auditableEntity = (AuditableEntity) tuple2.getT2();
String principal = tuple2.getT1().getAuthentication().getName();
Instant now = Instant.now();
auditableEntity.setLastModifiedBy(principal);
auditableEntity.setCreatedBy(principal);
auditableEntity.setLastModifiedDate(now);
auditableEntity.setCreatedDate(now);
return auditableEntity;
}));
}
}
Deprecated: see the update solution in the original post
Before the official reactive AuditAware is provided, there is an alternative to implement these via Spring Data Mongo specific ReactiveBeforeConvertCallback.
Do not use #EnableMongoAuditing
Implement your own ReactiveBeforeConvertCallback, here I use a PersistentEntity interface for those entities that need to be audited.
public class PersistentEntityCallback implements ReactiveBeforeConvertCallback<PersistentEntity> {
#Override
public Publisher<PersistentEntity> onBeforeConvert(PersistentEntity entity, String collection) {
var user = ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.filter(it -> it != null && it.isAuthenticated())
.map(Authentication::getPrincipal)
.cast(UserDetails.class)
.map(userDetails -> new Username(userDetails.getUsername()))
.switchIfEmpty(Mono.empty());
var currentTime = LocalDateTime.now();
if (entity.getId() == null) {
entity.setCreatedDate(currentTime);
}
entity.setLastModifiedDate(currentTime);
return user
.map(u -> {
if (entity.getId() == null) {
entity.setCreatedBy(u);
}
entity.setLastModifiedBy(u);
return entity;
}
)
.defaultIfEmpty(entity);
}
}
Check the complete codes here.
To have createdBy attribute filled, you need to link your auditorAware bean with the annotation #EnableMongoAuditing
In your MongoConfig class, define your bean :
#Bean(name = "auditorAware")
public AuditorAware<String> auditor() {
....
}
and use it in the annotation :
#Configuration
#EnableMongoAuditing(auditorAwareRef="auditorAware")
class MongoConfig {
....
}

Unit testing #PreAuthorized(hasRole) controller methods

I'm trying to write unit test for this controller method
#PreAuthorize("hasRole(#d.code) or hasRole('ROLE_ALL_ACCESS')")
#RequestMapping(value = "{department}/{examination}", method = RequestMethod.GET)
public String show(ModelMap model, Principal principal, Locale locale, D d) {
model.addAttribute(service.getItem(d) //THIS CAUSES NULL ON TEST
.addAttribute(new DateTime())
.addAttribute(locale);
return "show";
I tried this
#Test (expected=AccessDeniedException.class)
public void testShowAccessDenied() {
super.simulateRole("ROLE_STUDENT");
controller.show(new ModelMap(), Helper.getTestPrincipal(), Locale.getDefault(), new D());
But it causes NullPointerException on the marked line, which means test is running the method instead of throwing AccessDeniedException based on the wrong role.
My super test class is
public class AuthorizeTestBase {
private Mockery jmock = new JUnit4Mockery();
private AuthenticationManager authenticationManager= jmock.mock(AuthenticationManager.class);
#Before
public void setUp() {
jmock.checking(new Expectations() {{ ignoring(anything()); }});
}
protected void simulateRole(String role) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority(role));
PreAuthenticatedAuthenticationToken pat = new PreAuthenticatedAuthenticationToken(Helper.getTestPrincipal(), "", authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationManager.authenticate(pat));
}
Helper.getTestPrincipal is
LdapPerson.Essence essence = new LdapPerson.Essence();
LdapPerson user = (LdapPerson) essence.createUserDetails();
return new PreAuthenticatedAuthenticationToken(user, "password");
I think I'm missing something on mocking authenticationManager. Help me!
The NPE is happeing when you try to call the service. Your class AuthorizeTestBase doesn't know about any spring beans service/dao etc . So it should load the spring wire configuration. The below is an example.
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(locations = {"classpath:/applicationContext-test.xml",
"classpath:/applicationContext-ABC-test.xml",
"classpath:/applicationContext-DEF-test.xml",
"classpath:/applicationContext-serviceGHI-test.xml",
"classpath:/applicationContext-securityHIJ-test.xml"
})
public class AuthorizeTestBase {
.....
}
Also please have a look into - Spring MVC Test Framework

Can't create an AbstractRoutingDataSource that needs some data from another database

We currently have an application which uses multiple databases with the same schema. At the moment we're using a custom solution for switching between them based on the user's session. This works via
public final class DataSourceProxy extends BasicDataSource {
...
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getDetails() instanceof Map) {
Map<String, String> details = (Map<String, String>) auth.getDetails();
String targetUrl = details.get("database");
Connection c = super.getConnection();
Statement s = c.createStatement();
s.execute("USE " + targetUrl + ";");
s.close();
return c;
} else {
return super.getConnection();
}
}
Now we want to build a solution using AbstractRoutingDataSource. The problem is:
#Component
public class CustomRoutingDataSource extends AbstractRoutingDataSource {
#Autowired
Environment env;
#Autowired
DbDetailsRepositoy repo;
public CustomRoutingDataSource() {
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
for(DBDetails dbd : repo.findAll() {
// create DataSource and put it into the map
}
setTargetDataSources(new HashMap<Object, Object>());
}
#Override
protected Object determineCurrentLookupKey() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getDetails() instanceof Map) {
Map<String, String> details = (Map<String, String>) auth.getDetails();
return details.get("database");
}
return null;
}
}
Inside the constructor (or even via #PostConstruct) we have to fill the targetDataSources Map. But(!) for this we need the connection details which are stored in another database, which has its own DataSource and Entity Manager.
It seems like Spring can't determine the order of Bean construction, or maybe I'm just missing something. It always gives a NullPointerException when accessing the repository (which btw is a JpaRepository).
We're using Spring 3.2.3, Spring Data, Hibernate 4.2. Complete Annotation and Java-Code configuration of Spring and Spring Security.
Please help us!
Spring of course has to call the constructor before it can populate the properties. But that's not a Spring thing, that's basic Java 101 and one of the plenty downsides of using field injection.
To avoid this, simply add your dependencies to the constructor:
#Component
class CustomRoutingDataSource extends AbstractRoutingDataSource {
#Autowired
public CustomRoutingDataSource(DbDetailsRepository repo, Environment environment) {
…
}
…
}

Get resteasy servlet context without annotation params

Quick project explanation: We have a built application based on JSF2 + Spring with Dynamic data sources. The data reference control is made with a spring-config:
<bean id="dataSource" class="com.xxxx.xxxx.CustomerRoutingDataSource">
....
and a class (referenced above):
public class CustomerRoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return CustomerContextHolder.getCustomerType();
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
the CustomerContextHolder called above is as follows:
public class CustomerContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setCustomerType(String customerType) {
contextHolder.set(customerType);
}
public static String getCustomerType() {
String manager = (String)FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("dataBaseManager");
if (manager != null) {
contextHolder.set(manager);
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("dataBaseManager", null);
} else {
String base = (String)FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("currentDatabBase");
if (base != null)
contextHolder.set(base);
}
return (String) contextHolder.get();
}
public static void clearCustomerType() {
contextHolder.remove();
}
}
The problem is that the last guy is calling FacesContext.getCurrentInstance() to get the servlet context. Just to explain, it uses the session Attribute dataBaseManager to tell which base it should use.
For the actual solution it was working fine, but with the implementation of a RESTEASY web service, when we make a get request the FacesContext.getCurrentInstance() is obviously returning null and crashing.
I searched a lot and could not find a way of getting the servlet-context from outside of the #GET params. I would like to know if is there any way of getting it, or if there is another solution for my dynamic datasource problem.
Thanks!
Like magic and probably not much people know.
I searched deep into the Resteasy documentation, and found a part of springmvc plugin that comes with the resteasy jars, that has a class called RequestUtil.class.
With that I was able to use the method getRequest() without the "#Context HttpServletRequest req" param.
Using that I was able to set the desired database on the request attributes, and from another thread (called by spring) get it and load the stuff from the right place!
I'm using it for a week now and it works like a charm. Only thing that I needed to do is change the determineLookupKey() above to this:
#Override
protected String determineCurrentLookupKey() {
if (FacesContext.getCurrentInstance() == null) {
//RESTEASY
HttpServletRequest hsr = RequestUtil.getRequest();
String lookUpKey = (String) hsr.getAttribute("dataBaseManager");
return lookUpKey;
}else{
//JSF
return CustomerContextHolder.getCustomerType();
}
}
Hope this helps other people!
Thiago

Resources