0
votes

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

1 Answers

1
votes

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);
}