apache shiro是一个安全认证框架,和spring security相比,在于他使用了比较简洁易懂的认证和受权方式。其提供的native-session(即把用户认证后的受权信息保存在其自身提供Session中)机制,这样就能够和HttpSession、EJB Session Bean的基于容器的Session脱耦,到和客户端应用、Flex应用、远程方法调用等均可以使用它来配置权限认证。 在exit-web-framework里的vcs-admin例子用到该框架,具体使用说明能够参考官方帮助文档。在这里主要讲解如何与spring结合、动态建立filterchaindefinitions、以及认证、受权、和缓存处理。html
Shiro 拥有对Spring Web 应用程序的一流支持。在Web 应用程序中,全部Shiro 可访问的万恶不请求必须经过一个主要的Shiro 过滤器。该过滤器自己是极为强大的,容许临时的自定义过滤器链基于任何URL 路径表达式执行。 在Shiro 1.0 以前,你不得不在Spring web 应用程序中使用一个混合的方式,来定义Shiro 过滤器及全部它在web.xml中的配置属性,但在Spring XML 中定义SecurityManager。这有些使人沮丧,因为你不能把你的配置固定在一个地方,以及利用更为先进的Spring 功能的配置能力,如PropertyPlaceholderConfigurer 或抽象bean 来固定通用配置。如今在Shiro 1.0 及之后版本中,全部Shiro 配置都是在Spring XML 中完成的,用来提供更为强健的Spring 配置机制。如下是如何在基于Spring web 应用程序中配置Shiro: web.xml:java
<!-- Spring ApplicationContext配置文件的路径,可以使用通配符,多个路径用,号分隔 此参数用于后面的Spring Context Loader --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath*:/applicationContext-shiro.xml </param-value> </context-param> <!-- shiro security filter --> <filter> <!-- 这里的filter-name要和spring的applicationContext-shiro.xml里的 org.apache.shiro.spring.web.ShiroFilterFactoryBean的bean name相同 --> <filter-name>shiroSecurityFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroSecurityFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
applicationContext-shiro.xml文件中,定义web支持的SecurityManager和"shiroSecurityFilter"bean将会被web.xml 引用。git
<bean id="shiroSecurityFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- shiro的核心安全接口 --> <property name="securityManager" ref="securityManager" /> <!-- 要求登陆时的连接 --> <property name="loginUrl" value="/login.jsp" /> <!-- 登录成功后要跳转的链接 --> <property name="successUrl" value="/index.jsp" /> <!-- 未受权时要跳转的链接 --> <property name="unauthorizedUrl" value="/unauthorized.jsp" /> <!-- shiro链接约束配置 --> <propery name="filterChainDefinitions"> <value> /login = authc /logout = logout /resource/** = anon </value> </property> </bean> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> </bean> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
在独立应用程序和Web应用程序中,你可能想为安全检查使用Shiro 的注释(例如,@RequiresRoles,@RequiresPermissions 等等)。这须要Shiro 的Spring AOP 集成来扫描合适的注解类以及执行必要的安全逻辑。如下是如何使用这些注解的。只需添加这两个bean:github
<bean class="org.springframwork.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean>
有时,在某些系统想经过读取数据库来定义org.apache.shiro.spring.web.ShiroFilterFactoryBean的filterChainDefinitions。这样可以经过操做界面或者维护后台来管理系统的连接。web
在shrio与spring集成好了之后,调试源码的高人可能已经注意到。项目启动时,shrio经过本身的org.apache.shiro.spring.web.ShiroFilterFactoryBean类的filterChainDefinitions(受权规则定义)属性转换为一个filterChainDefinitionMap,转换完成后交给ShiroFilterFactoryBean保管。ShiroFilterFactoryBean根据受权(AuthorizationInfo类)后的信息去判断哪些连接能访问哪些连接不能访问。filterChainDefinitionMap里面的键就是连接URL,值就是存在什么条件才能访问该连接,如perms、roles。filterChainDefinitionMap是一个Map,shiro扩展出一个Map的子类Ini.Sectionspring
如今有一张表的描述实体类,以及数据访问:数据库
@Entity @Table(name="TB_RESOURCE") public class Resource implements Serializable { //主键id @Id private String id; //action url private String value; //shiro permission; private String permission; //------------------Getter/Setter---------------------// }
@Repository public class ResourceDao extends BasicHibernateDao<Resource, String> { }
经过该类能够知道permission字段和value就是filterChainDefinitionMap的键/值,用spring FactoryBean接口的实现getObject()返回Section给filterChainDefinitionMap便可apache
public class ChainDefinitionSectionMetaSource implements FactoryBean<Ini.Section>{ @Autowired private ResourceDao resourceDao; private String filterChainDefinitions; /** * 默认premission字符串 */ public static final String PREMISSION_STRING="perms[\"{0}\"]"; public Section getObject() throws BeansException { //获取全部Resource List<Resource> list = resourceDao.getAll(); Ini ini = new Ini(); //加载默认的url ini.load(filterChainDefinitions); Ini.Section section = ini.getSection(Ini.DEFAULT_SECTION_NAME); //循环Resource的url,逐个添加到section中。section就是filterChainDefinitionMap, //里面的键就是连接URL,值就是存在什么条件才能访问该连接 for (Iterator<Resource> it = list.iterator(); it.hasNext();) { Resource resource = it.next(); //若是不为空值添加到section中 if(StringUtils.isNotEmpty(resource.getValue()) && StringUtils.isNotEmpty(resource.getPermission())) { section.put(resource.getValue(), MessageFormat.format(PREMISSION_STRING,resource.getPermission())); } } return section; } /** * 经过filterChainDefinitions对默认的url过滤定义 * * @param filterChainDefinitions 默认的url过滤定义 */ public void setFilterChainDefinitions(String filterChainDefinitions) { this.filterChainDefinitions = filterChainDefinitions; } public Class<?> getObjectType() { return this.getClass(); } public boolean isSingleton() { return false; } }
定义好了chainDefinitionSectionMetaSource后修改applicationContext-shiro.xml文件缓存
<bean id="chainDefinitionSectionMetaSource" class="org.exitsoft.showcase.vcsadmin.service.account.ChainDefinitionSectionMetaSource"> <property name="filterChainDefinitions"> <value> /login = authc /logout = logout /resource/** = anon </value> </property> </bean> <bean id="shiroSecurityFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/login.jsp" /> <property name="successUrl" value="/index.jsp" /> <property name="unauthorizedUrl" value="/unauthorized.jsp" /> <!-- shiro链接约束配置,在这里使用自定义的动态获取资源类 --> <property name="filterChainDefinitionMap" ref="chainDefinitionSectionMetaSource" /> </bean> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> </bean> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
在shiro认证和受权主要是两个类,就是org.apache.shiro.authc.AuthenticationInfo和org.apache.shiro.authz.AuthorizationInfo。该两个类的处理在org.apache.shiro.realm.AuthorizingRealm中已经给出了两个抽象方法。就是:安全
/** * Retrieves the AuthorizationInfo for the given principals from the underlying data store. When returning * an instance from this method, you might want to consider using an instance of * {@link org.apache.shiro.authz.SimpleAuthorizationInfo SimpleAuthorizationInfo}, as it is suitable in most cases. * * @param principals the primary identifying principals of the AuthorizationInfo that should be retrieved. * @return the AuthorizationInfo associated with this principals. * @see org.apache.shiro.authz.SimpleAuthorizationInfo */ protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals); /** * Retrieves authentication data from an implementation-specific datasource (RDBMS, LDAP, etc) for the given * authentication token. * <p/> * For most datasources, this means just 'pulling' authentication data for an associated subject/user and nothing * more and letting Shiro do the rest. But in some systems, this method could actually perform EIS specific * log-in logic in addition to just retrieving data - it is up to the Realm implementation. * <p/> * A {@code null} return value means that no account could be associated with the specified token. * * @param token the authentication token containing the user's principal and credentials. * @return an {@link AuthenticationInfo} object containing account data resulting from the * authentication ONLY if the lookup is successful (i.e. account exists and is valid, etc.) * @throws AuthenticationException if there is an error acquiring data or performing * realm-specific authentication logic for the specified <tt>token</tt> */ protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
doGetAuthenticationInfo(AuthenticationToken token)(认证/登陆方法)会返回一个AuthenticationInfo,就是认证信息。是一个对执行及对用户的身份验证(登陆)尝试负责的方法。当一个用户尝试登陆时,该逻辑被认证器执行。认证器知道如何与一个或多个Realm协调来存储相关的用户/账户信息。从这些Realm中得到的数据被用来验证用户的身份来保证用户确实是他们所说的他们是谁。
doGetAuthorizationInfo(PrincipalCollection principals)是负责在应用程序中决定用户的访问控制的方法。它是一种最终断定用户是否被容许作某件事的机制。与doGetAuthenticationInfo(AuthenticationToken token)类似,doGetAuthorizationInfo(PrincipalCollection principals) 也知道如何协调多个后台数据源来访问角色恶化权限信息和准确地决定用户是否被容许执行给定的动做。
简单的一个用户和一个资源实体:
@Entity @Table(name="TB_RESOURCE") public class Resource implements Serializable { //主键id @Id private String id; //action url private String value; //shiro permission; private String permission; //------------------Getter/Setter---------------------// }
@Entity @Table(name="TB_USER") @SuppressWarnings("serial") public class User implements Serializable { //主键id @Id private String id; //登陆名称 private String username; //登陆密码 private String password; //拥有能访问的资源/连接() private List<Resource> resourcesList = new ArrayList<Resource>(); //-------------Getter/Setter-------------// }
@Repository public class UserDao extends BasicHibernateDao<User, String> { public User getUserByUsername(String username) { return findUniqueByProperty("username", username); } }
实现org.apache.shiro.realm.AuthorizingRealm中已经给出了两个抽象方法:
public class ShiroDataBaseRealm extends AuthorizingRealm{ @Autowired private UserDao userDao; /** * * 当用户进行访问连接时的受权方法 * */ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { if (principals == null) { throw new AuthorizationException("Principal对象不能为空"); } User user = (User) principals.fromRealm(getName()).iterator().next(); //获取用户响应的permission List<String> permissions = CollectionUtils.extractToList(user.getResourcesList(), "permission",true); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermissions(permissions); return info; } /** * 用户登陆的认证方法 * */ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token; String username = usernamePasswordToken.getUsername(); if (username == null) { throw new AccountException("用户名不能为空"); } User user = userDao.getUserByUsername(username); if (user == null) { throw new UnknownAccountException("用户不存在"); } return new SimpleAuthenticationInfo(user,user.getPassword(),getName()); } }
定义好了ShiroDataBaseRealm后修改applicationContext-shiro.xml文件
<bean id="chainDefinitionSectionMetaSource" class="org.exitsoft.showcase.vcsadmin.service.account.ChainDefinitionSectionMetaSource"> <property name="filterChainDefinitions"> <value> /login = authc /logout = logout /resource/** = anon </value> </property> </bean> <bean id="shiroSecurityFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/login.jsp" /> <property name="successUrl" value="/index.jsp" /> <property name="unauthorizedUrl" value="/unauthorized.jsp" /> <!-- shiro链接约束配置,在这里使用自定义的动态获取资源类 --> <property name="filterChainDefinitionMap" ref="chainDefinitionSectionMetaSource" /> </bean> <bean id="shiroDataBaseRealm" class="org.exitsoft.showcase.vcsadmin.service.account.ShiroDataBaseRealm"> <!-- MD5加密 --> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="MD5" /> </bean> </property> </bean> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="shiroDataBaseRealm" /> </bean> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
shiro CacheManager建立并管理其余Shiro组件使用的Cache实例生命周期。由于Shiro可以访问许多后台数据源,如:身份验证,受权和会话管理,缓存在框架中一直是一流的架构功能,用来在同时使用这些数据源时提升性能。任何现代开源和/或企业的缓存产品可以被插入到Shiro 来提供一个快速及高效的用户体验。
自从spring 3.1问世后推出了缓存功能后,提供了对已有的 Spring 应用增长缓存的支持,这个特性对应用自己来讲是透明的,经过缓存抽象层,使得对已有代码的影响下降到最小。
该缓存机制针对于 Java 的方法,经过给定的一些参数来检查方法是否已经执行,Spring 将对执行结果进行缓存,而无需再次执行方法。
可经过下列配置来启用缓存的支持:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <!-- 使用缓存annotation 配置 --> <cache:annotation-driven cache-manager="ehCacheManager" /> </beans>
@Cacheable和@CacheEvict来对缓存进行操做
@Cacheable:负责将方法的返回值加入到缓存中
@CacheEvict:负责清除缓存
/**声明了一个名为 persons 的缓存区域,当调用该方法时,Spring 会检查缓存中是否存在 personId 对应的值。*/ @Cacheable("persons") public Person profile(Long personId) { ... } /**指定多个缓存区域。Spring 会一个个的检查,一旦某个区域存在指定值时则返回*/ @Cacheable({"persons", "profiles"}) public Person profile(Long personId) { ... } </code> </pre> <pre> <code> /**清空全部缓存*/ @CacheEvict(value="persons",allEntries=true) public Person profile(Long personId, Long groundId) { ... } /**或者根据条件决定是否缓存*/ @CacheEvict(value="persons", condition="personId > 50") public Person profile(Long personId) { ... }
在shiro里面会有受权缓存。能够经过AuthorizingRealm类中指定缓存名称。就是authorizationCacheName属性。当shiro为用户受权一次以后将会把全部受权信息都放进缓存中。如今有个需求。当在更新用户或者删除资源和更新资源的时候,要刷新一下shiro的受权缓存,给shiro从新受权一次。由于当更新用户或者资源时,颇有可能已经把用户自己已有的资源去掉。不给用户访问。因此。借助spring的缓存工厂和shiro的缓存可以很好的实现这个需求。
将applicationContext-shiro.xml文件添加缓存
<bean id="chainDefinitionSectionMetaSource" class="org.exitsoft.showcase.vcsadmin.service.account.ChainDefinitionSectionMetaSource"> <property name="filterChainDefinitions" > <value> /login = authc /logout = logout /resource/** = anon </value> </property> </bean> <bean id="shiroSecurityFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/login.jsp" /> <property name="successUrl" value="/index.jsp" /> <property name="unauthorizedUrl" value="/unauthorized.jsp" /> <!-- shiro链接约束配置,在这里使用自定义的动态获取资源类 --> <property name="filterChainDefinitionMap" ref="chainDefinitionSectionMetaSource" /> </bean> <bean id="shiroDataBaseRealm" class="org.exitsoft.showcase.vcsadmin.service.account.ShiroDataBaseRealm"> <!-- MD5加密 --> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="MD5" /> </bean> </property> <property name="authorizationCacheName" value="shiroAuthorizationCache" /> </bean> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="shiroDataBaseRealm" /> <property name="cacheManager" ref="cacheManager" /> </bean> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- 使用缓存annotation 配置 --/> <cache:annotation-driven cache-manager="ehCacheManager" /> <!-- spring对ehcache的缓存工厂支持 --> <bean id="ehCacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> <property name="configLocation" value="classpath:ehcache.xml" /> <property name="shared" value="false" /> </bean> <!-- spring对ehcache的缓存管理 --> <bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"> <property name="cacheManager" ref="ehCacheManagerFactory"></property> </bean> <!-- shiro对ehcache的缓存管理直接使用spring的缓存工厂 --> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManager" ref="ehCacheManagerFactory" /> </bean>
ehcache.xml
<?xml version="1.0" encoding="UTF-8"?> <ehcache> <!-- maxElementsInMemory为缓存对象的最大数目, eternal设置是否永远不过时, timeToIdleSeconds对象处于空闲状态的最多秒数, timeToLiveSeconds对象处于缓存状态的最多秒数 --> <diskStore path="java.io.tmpdir"/> <cache name="shiroAuthorizationCache" maxElementsInMemory="300" eternal="false" timeToLiveSeconds="600" overflowToDisk="false"/> </ehcache>
public class UserDao extends BasicHibernateDao<User, String> { public User getUserByUsername(String username) { return findUniqueByProperty("username", username); } @CacheEvict(value="shiroAuthorizationCache",allEntries=true) public void saveUser(User entity) { save(entity); } }
@Repository public class ResourceDao extends BasicHibernateDao<Resource, String> { @CacheEvict(value="shiroAuthorizationCache",allEntries=true) public void saveResource(Resource entity) { save(entity); } @CacheEvict(value="shiroAuthorizationCache",allEntries=true) public void deleteResource(Resource entity) { delete(entity); } }
当userDao或者reaourceDao调用了相应带有缓存注解的方法,都会将AuthorizingRealm类中的缓存去掉。那么就意味着 shiro在用户访问连接时要从新受权一次。
整个apache shiro的使用在basic-curd项目有例子。能够参考showcase/basic-curd项目中的例子去理解。。