MongoDB - how to handle write failures during primary re-election with Spring? - spring

I configured my MongoDB replica set with Spring, and I'm trying to test the auto-failover.
I know that if the primary goes down, it takes a few seconds for a new primary to be elected, so in that time period, all writes will fail.
I have a test application that writes to the db every 1 sec, and when I take down the primary, I get a java.io.IOException (because there's no primary to write to). If I restart my application the writes are executed without a problem to the new primary.
I thought that the MongoDB Java driver can handle those cases using retries (was I wrong?), but I was unable to configure Spring to do that, so I'd appriciate some help. :)
My configuration is like so:
<mongo:mongo id="mongo" replica-set="host1:27017,host2:27017,host3:27017">
<mongo:options
connections-per-host="8"
threads-allowed-to-block-for-connection-multiplier="4"
connect-timeout="1000"
max-wait-time="1500"
auto-connect-retry="true"
socket-keep-alive="true"
socket-timeout="1500"
slave-ok="true"
write-number="1"
write-timeout="0"
write-fsync="true"/>
</mongo:mongo>
<mongo:repositories base-package="my.repositories" />
<mongo:db-factory dbname="my_db" mongo-ref="mongo" />
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
</bean>
Thanks!

Here is an initial stab at a spring aop/spring retry custom RetryPolicy for generic retry in various circumstances.
This is quite brittle (as it uses exception messages, etc which are subject to change). I would recommend robust testing, and definitely repeating on change of MongoDB and/or java driver version.
Firstly, maven dependancies used:
<dependencies>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>2.11.3</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.0.3.RELEASE</version>
</dependency>
</dependencies>
Second, a custom org.springframework.retry.RetryPolicy
import org.springframework.retry.RetryContext;
import org.springframework.retry.policy.SimpleRetryPolicy;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
public class CustomMongoDBRetryPolicy extends SimpleRetryPolicy {
private static final Logger logger = Logger.getLogger(CustomMongoDBRetryPolicy.class.getName());
public CustomMongoDBRetryPolicy(int maxAttempts) {
super(maxAttempts, createRetryableExceptions(), true);
}
private static Map<Class<? extends Throwable>, Boolean> createRetryableExceptions() {
HashMap<Class<? extends Throwable>, Boolean> classBooleanHashMap = new HashMap<Class<? extends Throwable>, Boolean>();
classBooleanHashMap.put(org.springframework.dao.DataAccessResourceFailureException.class, true);
classBooleanHashMap.put(org.springframework.data.mongodb.UncategorizedMongoDbException.class, true);
classBooleanHashMap.put(com.mongodb.MongoException.class, true);
classBooleanHashMap.put(java.net.ConnectException.class, true);
return classBooleanHashMap;
}
#Override
public boolean canRetry(RetryContext context) {
boolean retry = super.canRetry(context);
if (retry) {
#SuppressWarnings("ThrowableResultOfMethodCallIgnored")
Throwable lastThrowable = context.getLastThrowable();
if (lastThrowable != null) {
String message = lastThrowable.getMessage();
Throwable cause = lastThrowable.getCause();
if (message != null) {
if (message.startsWith("No replica set members available in")) {
logger.info("Retrying because no replica set members available. "+message);
return true;
}
if (message.startsWith("not talking to master and retries used up")) {
logger.info("Retrying because no master. "+message);
return true;
}
if (message.startsWith("can't find a master")) {
logger.info("Retrying because no master. "+message);
return true;
}
if (message.matches("Read operation to server [^\\s]* failed on database .*")) {
logger.info("Retrying because read operation failed. "+message);
return true;
}
}
if (cause != null) {
String causeMessage = cause.getMessage();
if (causeMessage != null) {
if (causeMessage.startsWith("Connection refused")) {
logger.info("Retrying because connection not available. "+causeMessage+"("+message+")");
return true;
}
}
}
logger.info("Not retrying. "+message+" "+lastThrowable.getClass().getName());
return false;
}
}
return retry;
}
}
Finally, tie into Dao using spring AOP
<aop:config proxy-target-class="false">
<aop:pointcut id="retry"
expression="execution(* IMyDao.count(..))" />
<aop:pointcut id="retry2"
expression="execution(* IMyDao.insert(..))" />
<aop:advisor pointcut-ref="retry"
advice-ref="retryAdvice" order="-1"/>
<aop:advisor pointcut-ref="retry2"
advice-ref="retryAdvice" order="-1"/>
</aop:config>
The following combines org.springframework.retry.backoff.ExponentialBackOffPolicy, to delay retries, org.springframework.retry.policy.TimeoutRetryPolicy, to limit retry time and the CustomMongoDBRetryPolicy, which retries what seems to be retry-able...
<bean id="retryAdvice"
class="org.springframework.retry.interceptor.RetryOperationsInterceptor">
<property name="retryOperations">
<bean class="org.springframework.retry.support.RetryTemplate">
<property name="retryPolicy">
<bean class="org.springframework.retry.policy.CompositeRetryPolicy">
<property name="optimistic" value="false"/>
<property name="policies">
<set>
<bean class="org.springframework.retry.policy.TimeoutRetryPolicy">
<property name="timeout" value="20000"/>
</bean>
<bean class="CustomMongoDBRetryPolicy">
<constructor-arg value="100"/>
</bean>
</set>
</property>
</bean>
</property>
<property name="listeners">
<set>
<bean class="MyRetryListener"/>
</set>
</property>
<property name="backOffPolicy">
<bean class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
<property name="initialInterval" value="500"/>
<property name="maxInterval" value="8000"/>
<property name="multiplier" value="1.5"/>
</bean>
</property>
</bean>
</property>
</bean>
Ive tested this with various scenarios, and it seem to be handling most pretty well. But whether it will work for a particular application, needs to be answered on a case by case basis.
Initial replicaset start - regular autoreconnect handles before the servers are listening, this handles prior to primary election - all invisible to the application (bar a long lag)
Killing the primary - write operation in progress fails to the application, subsequent retry
Stepping down the primary, shutting down the primary - as killing the primary.
Full replicaset restart (if fast enough)
Hope this helps

you set socket-timeout="1500". you should not set any socket timeout. Defaults to 0 (infinite time).

Related

Spring & Hibernate - Make native queries run in the same transaction as #Transactional

Is it possible to create native queries that make use of an existing transaction created via #Transactional?
Most of the questions here seem to be about making native queries at all. Also, answers such as the one from Spring + Hibernate Transaction -- Native SQL rollback failure suggest it might not be possible.
What I do to test the isolation is to run some deletes and add a breakpoint to investigate the database.
Here is what I tried so far:
#Transactional(value = "someManager")
public class SpringJpaSomeDao implements SomeDao {
#PersistenceContext(unitName = "someUnit")
#Qualifier("entityManagerFactorySome")
private EntityManager em;
#Resource
#Qualifier("someManager")
private PlatformTransactionManager transactionManager;
#Override
#Transactional(value = "someManager", propagation = Propagation.SUPPORTS)
public void runNative(String sql){
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
#Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
em.createNativeQuery(sql).executeUpdate();
}
});
}
Some part of the persistence.xml
<persistence-unit name="someUnit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<non-jta-data-source>java:comp/env/jdbc/someDS</non-jta-data-source>
<!-- some classes here -->
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<!-- some dialect -->
</properties>
</persistence-unit>
The code is invoked from some controller which also has #Transactional annotations giving the deletes to the Dao.
#Transactional(value = "someManager", propagation = Propagation.SUPPORTS)
public void deleteEntireDatabase() {
List<String> deletions = new ArrayList<>();
deletions.add("DELETE FROM something;");
for (String currentDeletion : deletions) {
someDao.runNative(currentDeletion);
}
}
#Override
#Transactional(value = "someManager", propagation = Propagation.REQUIRES_NEW, rollbackFor = {Exception.class})
public void deleteAndFill(JobExecutionProgress progress) {
deleteEntireDatabase();
// more code
}
Excerpt from spring-dao.xml:
<tx:annotation-driven />
<bean id="entityManagerFactorySome" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="someDataSource" />
<property name="persistenceUnitName" value="someUnit" />
<property name="jpaProperties">
<props>
<!-- some dialect -->
</props>
</property>
</bean>
<bean id="someDao" class="xyz.model.controller.SpringJpaSomeDao"/>
<bean id="someManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactorySome" />
<qualifier value="someManager"></qualifier>
</bean>
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
Of course, I also tried some variations such as different Propagations, and using the entityManager obtained from elsewhere:
EntityManagerFactory emf = ((JpaTransactionManager) transactionManager).getEntityManagerFactory();
EntityManager em2 = EntityManagerFactoryUtils.getTransactionalEntityManager(emf);
Now, what I will do if everything else fails is manual transaction management.
Is this something that has worked for one of your applications or is it unknown if this might be a setup problem?
Actually, it turns out the alter table statements mentioned in the comment seem to have been the problem. Not sure how resetting the auto_increment can empty the table, but that seemed to be the case.

cutom ehcache evict policy with spring

If we want to custom evict policy besides LRU LFU FIFO, the way docs recommanded is to implement interface Policy then set MemoryStoreEvictionPolicy like:
manager = new CacheManager(EHCACHE_CONFIG_LOCATION);
cache = manager.getCache(CACHE_NAME);
cache.setMemoryStoreEvictionPolicy(new MyPolicy());
but if I used spring, use #cacheable and xml files like
<bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:ehcache.xml" ></property>
</bean>
<!-- cacheManager -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="cacheManagerFactory" />
</bean>
how can I inject my own policy in spring way?
thank you all
You may be best to implement your own class that sets the eviction policy on the cache when Spring initializes.
For example:
public class MyEvictionPolicySetter implements InitializingBean {
public static final String CACHE_NAME = "my_cache";
private CacheManager manager;
private Policy evictionPolicy;
#Override
public void afterPropertiesSet() {
Cache cache = manager.getCache(CACHE_NAME);
cache.setMemoryStoreEvictionPolicy(evictionPolicy);
}
public void setCacheManager(CacheManager manager) {
this.manager = manager;
}
public void setEvictionPolicy(Policy evictionPolicy) {
this.evictionPolicy = evictionPolicy;
}
}
And then in your Spring config:
<bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:ehcache.xml" ></property>
</bean>
<!-- Specify your eviction policy as a Spring bean -->
<bean id="evictionPolicy" class="MyPolicy"/>
<!-- This will set the eviction policy when Spring starts up -->
<bean id="evictionPolicySetter" class="EvictionPolicySetter">
<property name="cacheManager" ref="cacheManagerFactory"/>
<property name="evictionPolicy" ref="evictionPolicy"/>
</bean>

Spring 3.1 JPA not inserting data while running in tomcat

I have spent three days trying to find the solution to this problem to no avail. I am desperate to figure this out. I have a simple spring app, running in servlet 2.5 with jstl tags 1.2, running in tomcat with spring 3.1, using hibernate and the hibernate jpa implementation.
I can list data from a page, but I cannot complete an insert. The record goes back, it appears to fire through with no problems. Yet no insert takes place. I know there are other posts similar but I have looked through them all and have not been able to find a solution anywhere.
If I run the exact same code via a MAIN class, the insert works fine. It just does not work when running as a web-app in tomcat.
I have tried running this via the main which works, inside the controller I have skipped calling the service layer, trying to go directly to the interface, when that didnt work, I tried going directly to the implementing DAO class, and that didnt work. It appears via the spring logs, that the entity manager is getting created, and shut down before a transaction takes place.
Please help me.
Here is my App-context
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
default-autowire="byName">
<context:component-scan base-package="com.naturalbornliar.site"/>
<tx:annotation-driven />
<!-- Bean declarations go here-->
<bean id="duke" class="com.naturalbornliar.site.entity.Admin">
<constructor-arg name="admin_id" type="Long" value="15" />
<constructor-arg name="admin_login" type="String" value="testUser" />
<constructor-arg name="admin_pwd" type="String" value="testPwd" />
<constructor-arg name="email_id" type="int" value="15" />
<constructor-arg name="quote" type="String" value="Something to say here" />
</bean>
<!-- <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> -->
<!-- <property name="driverClassName" value="com.mysql.jdbc.Driver"/> -->
<!-- <property name="url" value="jdbc:mysql://localhost:3306/nbl_db"/> -->
<!-- <property name="username" value="web_user"/> -->
<!-- <property name="password" value="web_pwd"/> -->
<!-- <property name="initialSize" value="5"/> -->
<!-- <property name="maxActive" value="10"/> -->
<!-- </bean> -->
<bean id="simpledataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/nbl_db"/>
<property name="username" value="web_user"/>
<property name="password" value="web_pwd"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="simpledataSource"/>
</bean>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="nblPersistenceUnit"/>
<property name="dataSource" ref="simpledataSource"/>
<property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
</bean>
<!-- <bean id="emf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean"> -->
<!-- <property name="persistenceUnitName" value="nblPersistenceUnit"/> -->
<!-- <property name="dataSource" ref="simpledataSource"/>-->
<!-- <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/> -->
<!-- </bean> -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="emf"/>
<property name="jpaDialect" ref="jpaDialect"/>
</bean>
<bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="MYSQL"/>
<property name="showSql" value="true"/>
<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
</bean>
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:advisor pointcut="execution(* *..CategoryDaoImpl.*(..))" advice-ref="txAdvice"/>
</aop:config>
</beans>
Here is my controller:
package com.naturalbornliar.site.mvc;
import javax.inject.Inject;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.naturalbornliar.site.entity.Category;
import com.naturalbornliar.site.entity.Link;
import com.naturalbornliar.site.service.CategoryService;
#Controller
#RequestMapping("/categories")
public class CategoryController {
protected final Logger logger = Logger.getLogger(CategoryController.class);
private final CategoryService categoryService;
#Inject
public CategoryController(CategoryService categoryService){
this.categoryService = categoryService;
}
#RequestMapping(value="/listCategories")
public String listLinks(Model model){
model.addAttribute("categories", categoryService.getAllCategories());
return "categories";
}
#RequestMapping(method=RequestMethod.GET, params="new")
public String showCreateCategoryForm(Model model){
model.addAttribute(new Category());
return "addcategory";
}
#RequestMapping(method=RequestMethod.POST)
public String addCategoryFromForm(Category category, BindingResult bindingResult){
if(bindingResult.hasErrors()){
return"addcategory";
}
categoryService.addCategory(category);
return "redirect:/categories/listCategories";
}
}
Here is my service called from the controller:
package com.naturalbornliar.site.service;
import java.util.Collection;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
import com.naturalbornliar.site.entity.Category;
import com.naturalbornliar.site.i.ICategoryDao;
#Service
public class CategoryService {
private ICategoryDao iCategoryDao;
#Inject
public CategoryService(ICategoryDao iCategoryDao){
this.iCategoryDao = iCategoryDao;
}
public Collection<Category> getAllCategories(){
return iCategoryDao.getAllCategories();
}
public Collection<Category> getCategoriesByType(String type) {
return iCategoryDao.getCategoriesByType(type);
}
public Category getCategoryById(Long id) {
throw new UnsupportedOperationException();
}
public void deleteCategory(Category category) {
throw new UnsupportedOperationException();
}
public void updateCategory(Category category) {
throw new UnsupportedOperationException();
}
public void inactivateCategory(Category category){
throw new UnsupportedOperationException();
}
public void addCategory(Category category){
iCategoryDao.addCategory(category);
}
}
Here is my implementing DAO:
package com.naturalbornliar.site.dao;
import java.util.Collection;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.naturalbornliar.site.entity.Content;
import com.naturalbornliar.site.i.IContentDao;
#Transactional
#Repository
public class ContentDaoImpl implements IContentDao {
protected final Logger logger = Logger.getLogger(ContentDaoImpl.class);
#PersistenceContext
private EntityManager em;
public EntityManager getEm() {
return em;
}
public void setEm(EntityManager em) {
this.em = em;
}
#Override
public void addContent(Content content) {
// TODO Auto-generated method stub
em.persist(content);
}
#Override
public void deleteContent(Content content) {
// TODO Auto-generated method stub
em.remove(content);
}
#Override
public void inactivateContent(Content content) {
// TODO Auto-generated method stub
}
#Override
public Content getContentById(Long id) {
// TODO Auto-generated method stub
return null;
}
#Override
public Content getContentByName(String name) {
// TODO Auto-generated method stub
return null;
}
#Override
public Collection<Content> getAllObjects() {
List<Content> resultList = em.createQuery("FROM Content", Content.class).getResultList();
return resultList;
}
}
Here is the interface (just in case)
package com.naturalbornliar.site.i;
import java.util.Collection;
import com.naturalbornliar.site.entity.Content;
public interface IContentDao {
public void addContent(Content content);
public void deleteContent(Content content);
public void inactivateContent(Content content);
public Content getContentById(Long id);
public Content getContentByName(String name);
public Collection<Content> getAllObjects();
}
Jeremy, thay may be many problems with that and I had similar problem.
In my case I used tomcat and I needed to add spring weaver to tomcat (this problem is described here: http://asrijaffar.blogspot.com/2007/02/spring-jpa-tomcat.html).
In my case I needed to have:
<tx:annotation-driven proxy-target-class="true" />
In DispatcherServlet config.
Additionally in db-context config:
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="jpatest" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
Well I actually solved the problem. Here is the solution, there was another stackoverflow issue that solved it:
Spring #Transaction not starting transactions
Basically all that was needed for transactions to fire correctly was for me to add:
<tx:annotation-driven proxy-target-class="true"/>
to my servlet xml. I guess if you just have it in the application config.xml it only works when running under the main class (like in a standalone) if you need to run it in a container you have to declare transaction annotations in the servlet as well.
I also spent several hours trying to figure out where the problem is although my previous application with the same stack is working fine and I wasn't able to understand the difference.
And.... the error was in tag in spring-servlet.xml - it was defined to scan the root package with all the web controller, repository classes, etc.
After changing it to make scanning of the package with web controllers only, the problem was gone..
Just for you (and for me) in case you might meet the same problem, just an additional hint
I also had this same problem, spent couple nights searching - alex solutions saved me - in servlet xml I changed context:component-scan to scan only package with web controller.
Example from this page should look like
<context:component-scan base-package="com.naturalbornliar.site.mvc"/>

Shiro Authorization Permission check using Annotation not working

Platform: Shiro 1.1.0, Spring 3.0.5
I'm trying to secure the MVC Controller methods using Shiro annotation. However something is wrong with annotations. Regular calls are just working OK. There is nothing specific in Shiro debug also.
My shiro configuration:
<!-- Security Manager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="sessionMode" value="native" />
<property name="realm" ref="jdbcRealm" />
<property name="cacheManager" ref="cacheManager"/>
</bean>
<!-- Caching -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManager" ref="ehCacheManager" />
</bean>
<bean id="ehCacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" />
<bean id="sessionDAO"
class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO" />
<bean id="sessionManager"
class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionDAO" ref="sessionDAO" />
</bean>
<!-- JDBC Realm Settings -->
<bean id="jdbcRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm">
<property name="name" value="jdbcRealm" />
<property name="dataSource" ref="dataSource" />
<property name="authenticationQuery"
value="SELECT password FROM system_user_accounts WHERE username=? and status=1" />
<property name="userRolesQuery"
value="SELECT role_name FROM system_roles r, system_user_accounts u, system_user_roles ur WHERE u.user_id=ur.user_id AND r.role_id=ur.role_id AND u.username=?" />
<property name="permissionsQuery"
value="SELECT permission_name FROM system_roles r, system_permissions p, system_role_permission rp WHERE r.role_id=rp.role_id AND p.permission_id=rp.permission_id AND r.role_name=?" />
<property name="permissionsLookupEnabled" value="true"></property>
</bean>
<!-- Spring Integration -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<!-- Enable Shiro Annotations for Spring-configured beans. Only run after
the lifecycleBeanProcessor has run: -->
<bean id="annotationProxy"
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor" />
<bean
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
<!-- Secure Spring remoting: Ensure any Spring Remoting method invocations
can be associated with a Subject for security checks. -->
<bean id="secureRemoteInvocationExecutor"
class="org.apache.shiro.spring.remoting.SecureRemoteInvocationExecutor">
<property name="securityManager" ref="securityManager" />
</bean>
<!-- Shiro filter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login" />
<property name="successUrl" value="/dashboard" />
<property name="unauthorizedUrl" value="/error" />
<property name="filterChainDefinitions">
<value>
<!-- !!! Order matters !!! -->
/authenticate = anon
/login = anon
/logout = anon
/error = anon
/** = authc
</value>
</property>
</bean>
I can get the following working correctly:
#RequestMapping(value="/form")
public String viewPatientForm(Model model, #RequestParam(value="patientId", required=false) Long patientId){
if (!SecurityUtils.getSubject().isPermitted("hc:viewPatient")){
logger.error("Operation not permitted");
throw new AuthorizationException("No Permission");
}
}
But the below doesn't work:
#RequiresPermissions("hc:patientView")
#RequestMapping(value="/form")
public String viewPatientForm(Model model, #RequestParam(value="patientId", required=false) Long patientId){
Am I missing something? Please help.
You were absolutely right. After seeing your comment, I started giving it a thought. Well then I found out that it was NOT an implementation problem with Shiro, but the jar dependecies were not properly configured. Shiro's pom.xml should have dependency for cglib2 too.
So the below changes worked for me :
Include all these four jar files.
aspectjrt-1.6.11.jar,
aspectjweaver-1.6.12.jar,
cglib-2.2.2.jar,
asm-3.3.1.jar,
If you are using maven then :
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.11</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.12</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
And finally placing the aop:aspectj-autoproxy in the webApplicationContext.xml
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- Annotation, so that it's easier to search controllers/components -->
<context:component-scan base-package="com.pepsey.soft.web.controller"/>
Note : The above two configuration should be placed together in the same spring-webApplicationContext.xml. Otherwise it won’t work. Moreover remove context:annotation-config if you have used it in your config. context:component-scan already scans all annotations.
Once you start testing , set your log4j to debug or (better) trace mode. Whenever you are starting your server you will find somewhere the following entry in your logs :
08:16:24,684 DEBUG AnnotationAwareAspectJAutoProxyCreator:537 -
Creating implicit proxy for bean 'userController' with 0 common
interceptor and 1 specific interceptors
Guess Shiro was built when Spring 2.0 was in place. Shiro’s annotations (RequiresRoles etc…) works well for the spring container managed beans (service layer), but it does not work with #Controller annotation. This is due to the fact that #Controller is being component scanned by spring framework. I used AOP to resolve the issue. Below is the solution which worked for me.
For the below solution to work you have to include the below four jars:
aspectjrt-1.6.11.jar
aspectjweaver-1.6.12.jar
cglib-2.2.2.jar
asm-3.3.1.jar
If you are using maven then below configuration would be helpful.
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.11</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.12</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
Below is a controller class
import org.apache.shiro.authz.annotation.RequiresRoles;
#Controller
public class PatientController {
#RequiresRoles(“admin,warden”)
#RequestMapping(value="/form")
public String viewPatientForm(Model model, #RequestParam(value="patientId", required=false) Long patientId){
return “somePatientFormJsp”;
}
}
Create the below Aspect for the annotation (RequiresRoles). You can use the same principle to create pointcuts for RequiresPermission.
import java.util.Arrays;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class WebAuthorizationAspect {
#Before("#target(org.springframework.stereotype.Controller) && #annotation(requiresRoles)")
public void assertAuthorized(JoinPoint jp, RequiresRoles requiresRoles) {
SecurityUtils.getSubject().checkRoles(Arrays.asList(requiresRoles.value()));
}
}
In your spring-webApplicationContext.xml wherever you have mentioned
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- Annotation, so that it's easier to search controllers/components -->
<context:component-scan base-package="com.example.controller"/>
Note : The above two configuration should be placed together in the same spring-webApplicationContext.xml. Otherwise it won’t work. Moreover remove context:annotation-config if you have used it in your config. context:component-scan already scans all annotations.
If you're avoiding Spring XML and using primarily Java and annotation configuration, the easiest way to fix this is to add
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
to all your #Controller classes. You need cglib on the classpath.
I have only used spring-hibernate example from sample. To use annotations like #RequiresPermissions and others I tried configuration from shiro manual, configuration from this post, but I was either unsuccessful to compile or run the valid urls. So I only commented all the #RequiresPermissions from ManageUserController and started to use it in service implementation. E.g In DefaultUserService in getAllUsers method I added the annotation #RequiresPermissions("user:manage"). Magically now the application works as expected. Whenever the url manageUsers is called it displays the list page if the user has role user:manage and throws the user to /unauthorized if the user don't have that permission.
I have even configured the application to use mysql instead. To make the permissions independent of roles according to new RBAC(http://www.stormpath.com/blog/new-rbac-resource-based-access-control) I have created a new class called Permission as
#Entity
#Table(name = "permissions")
#Cache(usage= CacheConcurrencyStrategy.READ_WRITE)
public class Permission {
#Id
#GeneratedValue
private Long id;
private String element;
private String description;
// setter and getter
Now Role class is configured as
#CollectionOfElements
#JoinTable(name="roles_permissions")
#Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public Set<Permission> getPermissions() {
return permissions;
}
And finally SampleRealm as
for (Role role : user.getRoles()) {
info.addRole(role.getName());
System.out.println("Roles " + role.getName());
// Get permissions first
Set<Permission> permissions = role.getPermissions();
Set<String> permissionsStrings = new HashSet<String>();
for (Permission permission : permissions) {
permissionsStrings.add(permission.getelement());
System.out
.println("Permissions " + permission.getelement());
}
info.addStringPermissions(permissionsStrings);
}
It creates five tables as
| permissions |
| roles |
| roles_permissions |
| users |
| users_roles |
And permissions is independent of any other. According to new RBAC you have both ways (explicit and implicit) way of authorising resources.
You need to write the AuthorizationAttributeSourceAdvisor to enable Shiro's annotations bean as per the Shiro documentation
If you have written ShiroConfiguration class, make sure you include this:
#Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
#Bean
#ConditionalOnMissingBean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultSecurityManager securityManager) {
// This is to enable Shiro's security annotations
AuthorizationAttributeSourceAdvisor sourceAdvisor = new AuthorizationAttributeSourceAdvisor();
sourceAdvisor.setSecurityManager(securityManager);
return sourceAdvisor;
}
#ConditionalOnMissingBean
#Bean(name = "defaultAdvisorAutoProxyCreator")
#DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}
Example ShiroConfiguration on Github
I had the same problem. My fix was changing my jersey version from 2.2 to 2.22.2 and all #RequiresPermissions worked on my controllers.

Sharing Spring Security Configuration Between Applications

I'm brand spanking new at Spring and have gotten a majority of the knowledge I do have from the Spring Recipes book from Apress.
I've got LDAP authentication working with Spring Security within one webapp. I would like to rip out my application context beans and properties files from this one webapp, however, and somehow externalize them so that all of our webapps can reference the same beans. So when we need to change something (like the ldapuser or the ldap urls), we change it in one place and the rest of the apps just know.
UPDATE
I've implemented Reloadable Spring Properties which is reloading properties when the files they come from are touched. I am using encrypted properties, however, so below is class I created on top of the Reloadable Spring Properties ones.
ReloadingEncryptablePropertyPlaceholderConfigurer.java
package;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.lang.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasypt.encryption.StringEncryptor;
import org.jasypt.util.text.TextEncryptor;
import org.jasypt.properties.PropertyValueEncryptionUtils;
import org.springframework.beans.factory.BeanDefinitionStoreException;
public class ReloadingEncryptablePropertyPlaceholderConfigurer extends ReloadingPropertyPlaceholderConfigurer {
protected final Log logger = LogFactory.getLog(getClass());
private final StringEncryptor stringEncryptor;
private final TextEncryptor textEncryptor;
public ReloadingEncryptablePropertyPlaceholderConfigurer(TextEncryptor textEncryptor) {
super();
logger.info("Creating configurer with TextEncryptor");
Validate.notNull(textEncryptor, "Encryptor cannot be null");
this.stringEncryptor = null;
this.textEncryptor = textEncryptor;
}
public ReloadingEncryptablePropertyPlaceholderConfigurer(StringEncryptor stringEncryptor) {
super();
logger.info("Creating configurer with StringEncryptor");
Validate.notNull(stringEncryptor, "Encryptor cannot be null");
this.stringEncryptor = stringEncryptor;
this.textEncryptor = null;
}
#Override
protected String convertPropertyValue(String originalValue) {
if (!PropertyValueEncryptionUtils.isEncryptedValue(originalValue)) {
return originalValue;
}
if (this.stringEncryptor != null) {
return PropertyValueEncryptionUtils.decrypt(originalValue, this.stringEncryptor);
}
return PropertyValueEncryptionUtils.decrypt(originalValue, this.textEncryptor);
}
#Override
protected String parseStringValue(String strVal, Properties props, Set visitedPlaceholders) throws BeanDefinitionStoreException {
return convertPropertyValue(super.parseStringValue(strVal, props, visitedPlaceholders));
}
}
And here's how I use it in my securityContext.xml:
<bean id="securityContextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<constructor-arg value="ldaps://ldapserver" />
<property name="urls" value="#{ldap.urls}" />
</bean>
<bean id="timer" class="org.springframework.scheduling.timer.TimerFactoryBean">
<property name="scheduledTimerTasks">
<bean id="reloadProperties" class="org.springframework.scheduling.timer.ScheduledTimerTask">
<property name="period" value="1000"/>
<property name="runnable">
<bean class="ReloadConfiguration">
<property name="reconfigurableBeans">
<list>
<ref bean="configproperties"/>
</list>
</property>
</bean>
</property>
</bean>
</property>
</bean>
<bean id="configproperties" class="ReloadablePropertiesFactoryBean">
<property name="location" value="classpath:ldap.properties"/>
</bean>
<bean id="ldapPropertyConfigurer" class="ReloadingEncryptablePropertyPlaceholderConfigurer">
<constructor-arg ref="configurationEncryptor" />
<property name="ignoreUnresolvablePlaceholders" value="true" />
<property name="properties" ref="configproperties"/>
</bean>
<bean id="jasyptConfig" class="org.jasypt.encryption.pbe.config.SimpleStringPBEConfig">
<property name="algorithm" value="PBEWithMD5AndTripleDES" />
<property name="password" value="########" />
</bean>
<bean id="configurationEncryptor" class="org.jasypt.encryption.pbe.StandardPBEStringEncryptor">
<property name="config" ref="jasyptConfig" />
</bean>
How about:
Writing a method that returns a list
of LDAP servers - reading from a
database table or property files
expose this wethod via jndi and use it to inject a list of the servers into your spring config
If you need the ldap servers to be refreshed dynamically you could have a job poll for changes periodically or else have an admin webpage or jmx bean to trigger the update. Be careful of concurrency isses for both these methods (something reading the list while you are updating)
Wouldn't that be Spring Security? It can deal with LDAPs. And if you make it one security service that everyone uses, wouldn't that be the way to manage it?

Resources