It's also possible to add in SpringSecurity. However, since I've been using Apache Shiro for some time in other projects, and didn't particularly want to learn a new security library, I wanted to see if I could get it set up with a Spring Boot application.
Basic setup
My environment is Java 7, Spring 4.0.5, using Shiro 1.2, deploying to a Servlet 3 container.Differences between configuration described in current Shiro 1.2 documentation and Spring Boot.
Boot encourages pure Java configuration, with no Spring XML files or even a web.xml file. So we need to declare all the beans needed for Shiro in a class annotated with Spring's @Configuration annotation.Here is my class to set up Shiro:
@Configuration
public class SecurityConfig {
@Bean()
public ShiroFilterFactoryBean shiroFilter (){
ShiroFilterFactoryBean factory = new ShiroFilterFactoryBean ();
factory.setSecurityManager(securityManager());
factory.setLoginUrl("/ui/login");
factory.setSuccessUrl("/ui/listView");
factory.setUnauthorizedUrl("/ui/login");
factory.setFilterChainDefinitions(
"/assets/scripts/**=anon\n"+
"/license/**=anon\n"+
"/manage/health/=anon\n"+
"/assets/static/*=authc\n"+
"/manage/metrics/**=authc\n"+
"/manage/beans/**=authc\n"+
"/manage/trace/**=authc\n"+
"/manage/mappings/**=authc\n"+
"/manage/dump/**=authc\n"+
"/manage/autoconfig/**=authc\n"+
"/manage/env/**=authc\n"+
"/manage/info/**=authc");
return factory;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager rc = new DefaultWebSecurityManager();
rc.setRealm(realm());
return rc;
}
@Bean public AuthorizingRealm realm() {
AuthorizingRealm realm = new AuthorizingRealm() {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
return new SimpleAuthenticationInfo("user", "password", "login");
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// TODO Auto-generated method stub
return null;
}
};
realm.setName("login");
return realm;
}
}
As you can see, you just need to set up 3 beans as a minimum, constructing a ShiroFilterFactoryBean, a SecurityManager and a Realm. In this example I've created a trivial Realm implementation, in practice you'll probably want to connect to a backend database to verify credentials. If you'r econfiguring a Realm that needs initialization, or want to add in any Spring Bean that implements the Initializable interface, you'll need to add in one more definition, e.g.,:
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public TextConfigurationRealm realm() {
IniRealm realm = new IniRealm() ;
realm.setResourcePath("classpath:users.ini");
return realm;
}
Instead of XML Configuration, we can just use setters in the classes to set property values.
SpringBoot can expose a set of URLs for monitoring and health checking. By default these are '/health', '/info' etc, but by setting a property in application.properties:
management.context-path=/manage
we can set these URLs with a prefix, and configure them to be authenticated. This is important as they give away a lot of sensitive information.
Customizing Shiro
For my application, I wanted to provide a custom filter that extended FormAuthenticationFilter .So, I set a new @Bean definition to create my Filter. By giving it the name 'authc' it should replace the existing FormAuthenticationFilter with my subclass.
In order to get this to work, it was crucial to define the filter after the ShiroFilterFactoryBean in the code. If it was defined first, then it seemed to prevent the correct behaviour of the FactoryBean to produce SpringShiroFilter instances.
But, by defining after the Factory bean, everything works properly. The reason I'm stressing this point is that in the old standard XML configuration, the order didn't seem to matter.
management.context-path=/managepackage com.researchspace.licenseserver; import java.util.HashMap; import java.util.Map; import javax.servlet.Filter; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.researchspace.licenseserver.controller.ShiroFormFilterExt; @Configuration public class SecurityConfig { @Bean() public ShiroFilterFactoryBean shiroFilter (){ ShiroFilterFactoryBean factory = new ShiroFilterFactoryBean (); factory.setSecurityManager(securityManager()); factory.setLoginUrl("/ui/login"); factory.setSuccessUrl("/ui/listView"); factory.setUnauthorizedUrl("/ui/login"); // this is ordered, better to do like this. factory.setFilterChainDefinitions( "/assets/scripts/**=anon\n"+ "/license/**=anon\n"+ "/manage/health/=anon\n"+ "/assets/static/*=authc\n"+ "/manage/metrics/**=authc\n"+ "/manage/beans/**=authc\n"+ "/manage/trace/**=authc\n"+ "/manage/mappings/**=authc\n"+ "/manage/dump/**=authc\n"+ "/manage/autoconfig/**=authc\n"+ "/manage/env/**=authc\n"+ "/manage/info/**=authc"); Map<String,Filter> filters= new HashMap<>(); filters.put("authc", authc()); factory.setFilters(filters); return factory; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager rc = new DefaultWebSecurityManager(); rc.setRealm(realm()); return rc; } @
Bean
public AuthorizingRealm realm() { AuthorizingRealm realm = new AuthorizingRealm() { @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { return new SimpleAuthenticationInfo("user", "password", "login"); } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // TODO Auto-generated method stub return null; } }; realm.setName("login"); return realm; } @Bean(name="authc") // this must be AFTER the factory bean definition public ShiroFormFilterExt authc(){ return new ShiroFormFilterExt(); } }
I hope other people trying to use Shiro with SpringBoot will find this useful.