Monday, April 18, 2016

Spring 4 MVC + Apache Shiro

Leave a Comment

I have a Spring 4 MVC application that uses @RestController annotations for all of the endpoints. The application does not use a web.xml nor an applicationContext.xml file. Everything works perfectly fine and now I am trying to get Apache Shiro working in cooperation with everything else.

What I would like to do is have Shiro throw an exception when there is an authc/authz problem accessing any of the REST services that I have built.

So far I was able to set up a basic class set up for initializing Shiro:

@Configuration public class ShiroInitializer {    @Bean(name = "realmLocal")   @DependsOn ("lifecycleBeanPostProcessor")   public Realm realmLocal() {     return new AuthorizingRealm() {       @Override       protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {         return null;       }        @Override       protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {         return null;       }     };   }    @Bean (name = "lifecycleBeanPostProcessor")   public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {     return new LifecycleBeanPostProcessor();   }    @Bean   public org.apache.shiro.mgt.SecurityManager getSecurityManager() {     DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();     securityManager.setRealm(realmLocal());     return securityManager;   }    @DependsOn("lifecycleBeanPostProcessor")   @Bean   public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {     DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();     proxyCreator.setProxyTargetClass(true);     return proxyCreator;   }    // enable shiro annotations   @Bean   public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {     AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();     advisor.setSecurityManager(getSecurityManager());     return advisor;   }    @Bean (name = "shiroFilter")   public ShiroFilterFactoryBean getShiroFilter() {     ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();     shiroFilter.setSecurityManager(getSecurityManager());      Map<String, String> filters = new ConcurrentHashMap<>();     filters.put("/**", "authcBasic");     shiroFilter.setFilterChainDefinitionMap(filters);      return shiroFilter;   } } 

When I build an endpoint like this with @RequiresUser and set breakpoints in the AuthorizingRealm in the initializer, nothing seems to picked up and the message displays without kicking off any authc/authz functionality:

@RestController @RequestMapping (value = "/") @RequiresUser public class RootEndpoint {    @RequestMapping (method = RequestMethod.GET)   public String doGet() throws Exception {     return "{ \"hello\": \"world\" }";   } } 

So here are my questions

  1. Q1 What am I missing in my configuration? Keep in mind I am trying to avoid the XML configurations if at all possible.
  2. Q2 After getting the basics, what can I use to throw exceptions instead of redirect the user to another page? Do I need a custom ShiroFilterFactoryBean of my own?

1 Answers

Answers 1

I was able to get this to work by actually switching to Spring Boot, which I was recently permitted to do by the owner of the code. I finally wound up with a configuration class for Shiro that looked like this:

@Configuration public class ShiroConfiguration {    @Bean   public ShiroFilterFactoryBean getShiroFilter() {     ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();     factoryBean.setSecurityManager(getSecurityManager());     return factoryBean;   }    @Bean (name = "securityManager")   public DefaultWebSecurityManager getSecurityManager() {     DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();     securityManager.setRealm(getLocaleRealm());     securityManager.setSessionManager(getSessionManager());     return securityManager;   }    @Bean   public DefaultWebSessionManager getSessionManager() {     final DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();     sessionManager.setGlobalSessionTimeout(43200000);     return sessionManager;   }    @Bean (name = "localRealm")   @DependsOn ("lifecycleBeanPostProcessor")   public LocalRealm getLocaleRealm() {     return new LocalRealm();   }    @Bean (name = "lifecycleBeanPostProcessor")   public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {     return new LifecycleBeanPostProcessor();   }    @Bean   public MethodInvokingFactoryBean getMethodInvokingFactoryBean() {     MethodInvokingFactoryBean factoryBean = new MethodInvokingFactoryBean();     factoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");     factoryBean.setArguments(new Object[]{getSecurityManager()});     return factoryBean;   } } 

I also added a custom handler to use exceptions and custom return data by adding a @ControllerAdvice implementation that contained:

@ExceptionHandler ({   AuthenticationException.class,   UnknownAccountException.class,   UnauthenticatedException.class,   IncorrectCredentialsException.class,   UnauthorizedException.class} ) @ResponseStatus (value = HttpStatus.UNAUTHORIZED) @ResponseBody public RestApiErrorResponse handleUnauthorized() {   return build(HttpStatus.UNAUTHORIZED); } 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment