I am having issues setting up ACL through Java config in a Spring Boot application. I have created one small project to reproduce the issues.
I have tried a few different approaches. First issue I had was with EhCache, and after I fixed that (I assume I did) I couldn't login any more, and it looks like all the data is gone.
There are 4 classes with different configurations:
ACLConfig1.class
ACLConfig2.class
ACLConfig3.class
ACLConfig4.class
All #PreAuthorize and #PostAuthorize annotations are working as expected, except hasPermission.
Controller holds 4 endpoints: one for User, one for Admin, one Public and the last one which gives me headache #PostAuthorize("hasPermission(returnObject,'administration')")
I am pretty sure that inserts in DB are correct. This class is one of four, the last one that I have tried:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class ACLConfig4 {
#Autowired
DataSource dataSource;
#Bean
public EhCacheBasedAclCache aclCache() {
return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(), permissionGrantingStrategy(), aclAuthorizationStrategy());
}
#Bean
public EhCacheFactoryBean aclEhCacheFactoryBean() {
EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
ehCacheFactoryBean.setCacheName("aclCache");
return ehCacheFactoryBean;
}
#Bean
public EhCacheManagerFactoryBean aclCacheManager() {
return new EhCacheManagerFactoryBean();
}
#Bean
public DefaultPermissionGrantingStrategy permissionGrantingStrategy() {
ConsoleAuditLogger consoleAuditLogger = new ConsoleAuditLogger();
return new DefaultPermissionGrantingStrategy(consoleAuditLogger);
}
#Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ADMINISTRATOR"));
}
#Bean
public LookupStrategy lookupStrategy() {
return new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), new ConsoleAuditLogger());
}
#Bean
public JdbcMutableAclService aclService() {
JdbcMutableAclService service = new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache());
return service;
}
#Bean
public DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {
return new DefaultMethodSecurityExpressionHandler();
}
#Bean
public MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = defaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService()));
expressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService()));
return expressionHandler;
}
}
What am I missing here? Why I have no data if I use ACLConfig3.class or
ACLConfig4.class. Is there any example on how this should be configured programmatically in Spring Boot?
The reason why you have no data was a bit tricky to find out. As soon as you define a MethodSecurityExpressionHandler bean in your config, there is no data in the database tables. This is because your data.sql file isn't executed.
Before explaining why data.sql isn't executed I'd first like to point out that you're not using the file as intended.
data.sql is executed by spring-boot after hibernate has been initialized and normally only contains DML statements. Your data.sql contains both DDL (schema) statements and DML (data) statements. This isn't ideal as some of your DDL statements clash with hibernate's hibernate.hbm2ddl.auto behaviour (note that spring-boot uses 'create-drop' when an embedded DataSource is being used). You should put your DDL statements in schema.sql and your DML statements in data.sql. As you're manually defining all tables you should disable hibernate.hbm2ddl.auto (by adding spring.jpa.hibernate.ddl-auto=none to applciation.properties).
That being said, let's take a look at why data.sql isn't executed.
The execution of data.sql is triggered via an ApplicationEvent that's fired via a BeanPostProcessor. This BeanPostProcessor (DataSourceInitializedPublisher) is created as a part of spring-boot's Hibernate/JPA auto configuration (see org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.DataSourceInitializedPublisher and org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer).
Normally the DataSourceInitializedPublisher is created before the (embedded) DataSource is created and everything works as expected but by defining a custom MethodSecurityExpressionHandler the normal bean creation order alters.
As you've configured #EnableGlobalMethodSecurity, your're automatically importing GlobalMethodSecurityConfiguration.
spring-security related beans are created early on. As your MethodSecurityExpressionHandler requires a DataSource for the ACL stuff and the spring-security related beans require your custom MethodSecurityExpressionHandler, the DataSource is created earlier than usual; in fact it's created so early on that spring-boot's DataSourceInitializedPublisher isn't created yet.
The DataSourceInitializedPublisher is created later on but as it didn't notice the creation of a DataSource bean, it also doesn't trigger the execution of data.sql.
So long story short: the security configuration alters the normal bean creation order which results in data.sql not being loaded.
I guess that fixing the bean creation order would do the trick, but as I don't now how (without further experimentation) I propose the following solution: manually define your DataSource and take care of data initialization.
#Configuration
public class DataSourceConfig {
#Bean
public EmbeddedDatabase dataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2)
//as your data.sql file contains both DDL & DML you might want to rename it (e.g. init.sql)
.addScript("classpath:/data.sql")
.build();
}
}
As your data.sql file contains all DDL required by your application you can disable hibernate.hbm2ddl.auto. Add spring.jpa.hibernate.ddl-auto=none to applciation.properties.
When defining your own DataSource spring-boot's DataSourceAutoConfiguration normally back's out but if you want to be sure you can also exclude it (optional).
#SpringBootConfiguration
#EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
#ComponentScan
#EnableCaching
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
This should fix your 'no data' problem. But in order to get everything working as expected you need to make 2 more modifications.
First of all, you should only define one MethodSecurityExpressionHandler bean. Currently you're defining 2 MethodSecurityExpressionHandler beans. Spring-security won't know which one to use and will (silently) use it's own internal MethodSecurityExpressionHandler instead. See org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration#setMethodSecurityExpressionHandler.
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MyACLConfig {
//...
#Bean
public MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler securityExpressionHandler = new DefaultMethodSecurityExpressionHandler();
securityExpressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService()));
securityExpressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService()));
return securityExpressionHandler;
}
}
The last thing you need to do is make the getId() method in Car public.
#Entity
public class Car {
//...
public long getId() {
return id;
}
//...
}
The standard ObjectIdentityRetrievalStrategy will look for a public method 'getId()' when trying to determine an object's identity during ACL permission evaluation.
(Note that I've based my answer upon ACLConfig4.)
Related
So at first I added a properties file with:
spring.data.mongodb.uri=mongodb://axon:axon#aurl:27017/axonframework
which works but I was forced to use axonframework as db name because it is what was created in my mongo db.
Now controlling the db name and other details isn't an option in this case, so I went and checked around and found the following:
#configuration
public class AxonConfiguration {
#Value("${mongo.host:127.0.0.1}")
private String mongoHost;
#Value("${mongo.port:27017}")
private int mongoPort;
#Value("${mongo.db:test}")
private String mongoDB;
#Bean
public MongoSagaStore sagaStore() {
return new MongoSagaStore(axonMongoTemplate());
}
#Bean
public TokenStore tokenStore(Serializer serializer) {
return new MongoTokenStore(axonMongoTemplate(), serializer);
}
#Bean
public EventStorageEngine eventStorageEngine(Serializer serializer) {
return new MongoEventStorageEngine(serializer, null, axonMongoTemplate(), new DocumentPerEventStorageStrategy());
}
#Bean
public MongoTemplate axonMongoTemplate() {
return new DefaultMongoTemplate(mongo(), mongoDB);
}
#Bean
public MongoClient mongo() {
MongoFactory mongoFactory = new MongoFactory();
mongoFactory.setMongoAddresses(Collections.singletonList(new ServerAddress(mongoHost, mongoPort)));
return mongoFactory.createMongo();
}
}
Now apparently this worked for people but what I'm not being able to get right is how am I supposed to set the username and password?
I'm using axon 4.1, axonframework.extensions.mongo 4.1
The snippet of code you share does not correspond with Axon Framework release 4.x or Axon Mongo Extension release 4.x. The shift from version 3 to 4 has replaced almost all constructors of the infrastructure components in favor of the Builder pattern.
As such, you should not be able to do new MongoEventStorageEngine(...), but instead should do:
MongoEventStorageEngine.builder().mongoTemplate(axonMongoTemplate).build()
If you're still able to use the constructor, I assume you still have Axon 3 somewhere on the class path!
Regarding the Mongo specifics, I'd trust #PolishCivil's statement by the way.
Hope this helps!
This issue is not really related to the axon itself but more likely to the spring configuration of the mongo client instance since the usage of mongo is just an extension over the axon framework.
AFAIK it's
spring.data.mongodb.password
and
spring.data.mongodb.username
Also theres one thing in the code you should consider changing
return new DefaultMongoTemplate(mongo(), mongoDB);
You call the method that is specified as a bean, so instead in spring you should just wire it to your method parameter like so :
public MongoTemplate axonMongoTemplate(MongoClient client) {
return new DefaultMongoTemplate(client, mongoDB);
}
Good evening all,
I'm new to Spring, let along Spring's caching. That said, I have a group of Maven projects all under the umbrella of a project, each of which requires some amount of caching. That said, my initial attempt at caching with multiple cache managers was failing... in some research I read some about using a custom CacheResolver and binding multiple cacheManagers within that. This thread spawned my interest.
Based on the answers there, I had some questions and was advised to post it in a new question. The gist of my question(s) is:
First off, is there an advantage to having multiple cacheManagers. For example on the project I'm working on, I originally thought we would need multiple managers, but I think if we were to put a singular manager in a -common maven project that all of our other projects use, then we could get away with the one (and it manages all of our caches (30+)).
Secondly, in the above example, let's assume I declare multiple cacheManagers in CustomCacheManager. I don't quite follow on how I determine which manager to use?
Thanks in advance!
Declare one cache manager as primary. Here is an example for reference:
#Configuration
#EnableCaching
#PropertySource(value = { "classpath:/cache.properties" })
public class CacheConfig {
#Bean
#Primary
public CacheManager hazelcastCacheManager() {
ClientConfig config = new ClientConfig();
HazelcastInstance client = HazelcastClient.newHazelcastClient(config);
return new HazelcastCacheManager(client);
}
#Bean
public CacheManager guavaCacheManager() {
GuavaCacheManager cacheManager = new GuavaCacheManager("mycache");
CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.MINUTES);
cacheManager.setCacheBuilder(cacheBuilder);
return cacheManager;
}
}
Then specify it class level:
#Service
#CacheConfig(cacheManager="hazelcastCacheManager")
public class PolicyServiceImpl implements PolicyService {
}
You can also put it to method level:
#Service
public class PolicyServiceImpl implements PolicyService {
#Override
#Cacheable(value = "POLICY_", key = "#id", cacheManager= "guavaCacheManager")
public Policy getPolicyDetails(int id) {
return new Policy(id, "IX4546");
}
}
Also another way of mentioning at method level or class level would be:
#CacheConfig(cacheManager = "ehCacheManager")
#Target(value = ElementType.TYPE)
#Retention(value = RetentionPolicy.RUNTIME)
public #interface EhCacheable {
}
#EhCacheable
#Service
public class PolicyServiceImpl implements Policy {
}
You can also refer this link for custom cache resolver: https://github.com/isaolmez/spring-cache-samples/tree/master/spring-cache-custom
Have two different modules currently Let say Project A and Project B. Project B imported/used into/in Project A. Currently Project B already have CacheManager.
Project B
public class CacheConfig {
#Bean
public CacheManager cacheManager() {
// using SimpleCacheManager()
}
}
But now planed to implement CacheManager in Project A for someother Purpose.
class SomeCacheConfig{
#Bean
public CacheManager someCacheManager(){
// using SimpleCacheManager()
}
}
While loading application throws below exception.
java.lang.IllegalStateException: No CacheResolver specified, and no unique bean of type CacheManager found. Mark one as primary (or give it the name 'cacheManager') or declare a specific CacheManager to use, that serves as the default one.
Can you please help me how to achieve multiple cacheManager in multiple modules/projects.
ok then.
put #Primary on the CacheManager bean that will use as default.
#Primary
#Bean(name = "primaryCacheManager")
public CacheManager primaryCacheManager() {
return new SimpleCacheManager();
}
#Bean(name = "myCacheManager")
public CacheManager myCacheManager() {
return new SimpleCacheManager();
}
and when you want to use another one(i.e. not a default), explictly define a name of CacheManager bean with #Qualifier annotation.
#Autowired
#Qualifier("myCacheManager")
private CacheManager myCacheManager;
or if you use annotation base Spring Cache implementation, you can also define a CacheManager name as property of those annotations
#Cacheable(value = "some",cacheManager = "myCacheManager")
public String getSome(){
return "";
}
You can use the CompositeCacheManager implementation provided by Spring (https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/cache/support/CompositeCacheManager.html)
This allows you to compose a list of cache managers. The composite manager will iterate through the list and get the cache in the first manager it exists in. Please note that "Note: Regular CacheManagers that this composite manager delegates to need to return null from getCache(String) if they are unaware of the specified cache name, allowing for iteration to the next delegate in line. However, most CacheManager implementations fall back to lazy creation of named caches once requested; check out the specific configuration details for a 'static' mode with fixed cache names, if available."
What eventually worked for me as Erik Ahlswede suggested
#Bean
public CacheManager cacheManager() {
return new CompositeCacheManager(
new ConcurrentMapCacheManager("cacheA") {
#Override
protected Cache createConcurrentMapCache(final String name) {
return new ConcurrentMapCache(name,
CacheBuilder.newBuilder()
.expireAfterWrite(CACHE_TTL_IN_SECONDS, TimeUnit.SECONDS)
.maximumSize(MAX_ENTRIES_IN_CACHE)
.build().asMap(), false);
}
},
new ConcurrentMapCacheManager("cacheB") {
#Override
protected Cache createConcurrentMapCache(final String name) {
return new ConcurrentMapCache(name,
CacheBuilder.newBuilder()
.expireAfterWrite(CACHE_TTL_IN_SECONDS, TimeUnit.SECONDS)
.maximumSize(MAX_ENTRIES_IN_CACHE)
.build().asMap(), false);
}
}
);
}
And then use it with
#Cacheable(cacheNames = "someComplicatedAction", cacheManager = "cacheA")
public String someComplicatedAction() {
}
I have 4 databases with similar schema on PostgreSQL
My current code is like this
ressources
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=postgres
DAO
public interface AccountRepository extends JpaRepository<Account, Long>{}
Configuration
#Configuration
public class AccountServiceConfiguration {
#Autowired
private AccountRepository accountRepository;
#Bean
public AccountService accountService() {
return new AccountService(accountRepository);
}
}
Controller
#RestController
#RequestMapping("/accounts")
public class AccountController {
#Autowired
private AccountService accountService;
#RequestMapping(name = "/", method = RequestMethod.GET)
public Page<Account> getAccounts(Integer page, Integer size) {
return accountService.getAll(page, size);
}
}
Service
public class AccounttService {
public AccounttService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
public Page<Account> getAll(Integer page, Integer size) {
PageRequest pageRequest = new PageRequest(page, size);
return accountRepository.findAll(pageRequest);
}
}
I want to change like this
ressources
spring.db1.url=jdbc:postgresql://db1:5432/postgres
spring.db1.username=postgres1
spring.db1.password=postgres1
spring.db2.url=jdbc:postgresql://db2:5432/postgres
spring.db2.username=postgres2
spring.db2.password=postgres2
spring.db3.url=jdbc:postgresql://db3:5432/postgres
spring.db3.username=postgres3
spring.db3.password=postgres3
spring.db4.url=jdbc:postgresql://db4:5432/postgres
spring.db4.username=postgres4
spring.db4.password=postgres4
Controller
...
public Page<Account> getAccounts(Integer page, Integer size, string env) {
return accountService.getAll(page, size, env);
}
...
Service
public class AccounttService {
public AccounttService(Map<AccountRepository> mapAccountRepository) {
this.mapAccountRepository = mapAccountRepository;
}
public Page<Account> getAll(Integer page, Integer size, String env) {
PageRequest pageRequest = new PageRequest(page, size);
// search in specific env
}
}
How to load 4 data sources (may be on map) and search by environnement !
If i send env=db1 i want to run my request on db1
If you have other solution, i take it, but must use one repository and one entity to search in all databases.
Thank you :)
According to your comments you want a single Repository instance to switch between different schemata.
This won't work.
What you can do is provide a Facade for multiple Repository instance that delegates each call to on of the many instances according to some parameter/field/property.
But one way or the other you have to create a separate Repository instance with a different database connection for each.
What you are describing is called multi-tenancy using multiple databases.
To accomplish so you would need to manually configure the persistence layer and not to rely completely in Spring Boot auto-configuration capabilities.
The persistence layer configuration involves:
Hibernate, JPA and datasources properties
Datasources beans
Entity manager factory bean (in the case of Hibernate, with properties specifying this is a mult-tenant entity manager factory bean and tenant connection provider as well as tenant resolver)
Transaction manager bean
Spring Data JPA and transaction support configuration
In a blog post I recently published: Multi-tenant applications using Spring Boot, JPA, Hibernate and Postgres I cover in this exact problem with a detailed implementation.
I'd like to configure "transactional" beans from my Spring #Configuration class instead of annotating the class implementation itself with #Transactional.
Kind of like the old school way, configuring transactional advice from an XML file, but without needing a String reference to my class/method names to create pointcuts.
The reason is that the bean implementation is in another code base, and the module it belongs to doesn't depend on Spring. Read : I'm not touching the source code of that bean, just instanciating it. The class is final, can't extend it either to add Spring annotations to the child class.
Let's say that all the methods must be transactional, for simplicity.
The bean implementation :
/** This class has no Spring dependency... */
// #Transactional <- which means I can't use this here
public final class ComplexComponentImpl implements ComplexComponent {
private SomeRepository repo;
public ComplexComponentImpl(SomeRepository repository) { this.repo = repository }
public void saveEntities(SomeEntity e1, SomeEntity e2) {
repo.save(e1);
throw new IllegalStateException("Make the transaction fail");
}
What I want to do in my configuration class (and which doesn't work in my unit test) :
#Configuration
#EnableTransactionManagement
public class ComplexComponentConfig {
#Bean
#Transactional // <- Make the bean transactional here
public ComplexComponent complexComponent() {
return new ComplexComponentImpl(repository());
}
// ...
}
The example above doesn't work, indeed, as nothing gets "transactional" at runtime : entity e1 is persisted even though the exception is thrown.
Note that my transaction management setup works works perfectly well with an implementation class marked with #Transactional.
Question : Is is it possible to declare #Beans transactional from a #Configuration class, or is there any alternative taking into accounts the constraints above ?
Found something built-in that is the sum of #Mecon's and #Erik Gillespie's answers, with limited boilerplate.
Spring already provides a TransactionProxyFactoryBean that just sets up a transactional proxy on any object. Much of the setup could be refactored to some utility method :
#Configuration
#EnableTransactionManagement
public class ComplexComponentConfig {
/** NOT A #Bean, this object will be wrapped with a transactional proxy */
public ComplexComponent complexComponentImpl() {
return new ComplexComponentImpl(repository());
}
#Bean
public ComplexComponent complexComponent() {
TransactionProxyFactoryBean proxy = new TransactionProxyFactoryBean();
// Inject transaction manager here
proxy.setTransactionManager(txManager());
// Define wich object instance is to be proxied (your bean)
proxy.setTarget(complexComponentImpl());
// Programmatically setup transaction attributes
Properties transactionAttributes = new Properties();
transactionAttributes.put("*", "PROPAGATION_REQUIRED");
proxy.setTransactionAttributes(transactionAttributes);
// Finish FactoryBean setup
proxy.afterPropertiesSet();
return (ComplexComponent) proxy.getObject;
}
// ...
}
I think you probably can't use #Transactional in that manner. One of spring's in-built PostProcessors, are supposed to scan all classes (beans) that have that annotation, and the create Aspects accordingly.
About alternatives: I would write an Adapter class for each 3rd party class I have to use. And then make those Adapter classes be Spring Beans.
You can't use #Transactional in that way but you can programmatically configure aspects with Spring.
Spring documentation for programmatically defining aspects:
http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-aspectj-programmatic
http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/aop-api.html
The examples in the documentation are very simple. Defining transaction aspects will likely be more complicated and I wouldn't be surprised if you find it easier to just use the convenience of XML-based proxies or take #Mecon's advice and write adapters.
You can use spring's AOP capabilities to add the transaction interceptor to your bean. Just create an Advisor bean that specifies a pointcut and adds a TranscationInterceptor.
#Bean
public ComplexComponent complexComponentImpl() {
return new ComplexComponentImpl(repository());
}
#Bean
public Advisor advisorBean(TransactionManager txManager) {
Class<?> targetClass = ComplexComponent .class;
int propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED;
return allMethodsTxAdvice(txManager, targetClass, propagationBehavior);
}
/**
* Extracted method for reuse.
*/
private DefaultPointcutAdvisor allMethodsTxAdvice(TransactionManager txManager, Class<?> targetClass, int propagationBehavior) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
String pointcutExpression = MessageFormat.format("execution(* {0}.*(..)))", targetClass.getName());
pointcut.setExpression(pointcutExpression);
MatchAlwaysTransactionAttributeSource tas = new MatchAlwaysTransactionAttributeSource();
TransactionAttribute transactionAttribute = new DefaultTransactionAttribute(propagationBehavior);
tas.setTransactionAttribute(transactionAttribute);
TransactionInterceptor transactionInterceptor = new TransactionInterceptor(txManager, tas);
return new DefaultPointcutAdvisor(pointcut, transactionInterceptor);
}
PS: You don't need to call afterPropertiesSet just return the FactoryBean and spring will handle all lifecycle callbacks. E.g.
#Bean
public FactoryBean<Object> complexComponent(TransactionManager tx) {
TransactionProxyFactoryBean proxyFactory = new TransactionProxyFactoryBean();
proxyFactory.setTransactionManager(tx);
proxyFactory.setTarget(complexComponentImpl());
Properties transactionAttributes = new Properties();
transactionAttributes.put("*", "PROPAGATION_REQUIRED");
proxyFactory.setTransactionAttributes(transactionAttributes);
return proxyFactory;
}