apache shiro与spring整合、动态 filterChainDefinitions、以及认证、授 权

apache shiro 是一个安全认证框架,和spring security 相比,在于他使用了比较简洁易懂的
认证和授权方式。其提供的native-session(即把用户认证后的授权信息保存在其自身提供
Session 中)机制,这样就可以和HttpSession、EJB Session Bean 的基于容器的Session
脱耦,到和客户端应用、Flex 应用、远程方法调用等都可以使用它来配置权限认证。在
exit-web-framework 里的vcs-admin 例子用到该框架,具体使用说明可以参考官方帮助文档。
在这里主要讲解如何与spring 结合、动态创建filterchaindefinitions、以及认证、授权、和
缓存处理。
apache shiro 结合spring
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:

<context-param> 
<param-name>contextConfigLocationparam-name> 
<param-value> 
classpath*:/applicationContext-shiro.xml 
param-value> 
context-param> 


 
<filter> 
 
<filter-name>shiroSecurityFilterfilter-name> 
<filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class> 
<init-param> 
<param-name>targetFilterLifecycleparam-name> 
<param-value>trueparam-value> 

init-param> 
filter> 
<filter-mapping> 
<filter-name>shiroSecurityFilterfilter-name> 
<url-pattern>/*url-pattern> 
filter-mapping> 

applicationContext-shiro.xml 文件中,定义web 支持的SecurityManager 和
“shiroSecurityFilter”bean 将会被web.xml 引用。

<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" /> 
 
<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"/> 

启用Shiro 注解
在独立应用程序和Web 应用程序中,你可能想为安全检查使用Shiro 的注释(例如,
@RequiresRoles,@RequiresPermissions 等等)。这需要Shiro 的Spring AOP 集成来
扫描合适的注解类以及执行必要的安全逻辑。以下是如何使用这些注解的。只需添加这两个
bean 定义到applicationContext-shiro.xml 中:

class="org.springframwork.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" 
depends-on="lifecycleBeanPostProcessor"/> 
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdv 
isor"> 
<property name="securityManager" ref="securityManager"/> 
 

动态创建filterchaindefinitions
有时,在某些系统想通过读取数据库来定义
org.apache.shiro.spring.web.ShiroFilterFactoryBean 的filterChainDefinitions。这样能够通
过操作界面或者维护后台来管理系统的链接。
在 shrio 与spring 集成好了以后,调试源码的高人可能已经注意到。项目启动时,shrio 通
过自己的org.apache.shiro.spring.web.ShiroFilterFactoryBean 类的
filterChainDefinitions(授权规则定义)属性转换为一个filterChainDefinitionMap,转换完成后
交给ShiroFilterFactoryBean 保管。ShiroFilterFactoryBean 根据授权(AuthorizationInfo
类)后的信息去判断哪些链接能访问哪些链接不能访问。filterChainDefinitionMap 里面的键
就是链接URL,值就是存在什么条件才能访问该链接,如perms、roles。
filterChainDefinitionMap 是一个Map,shiro 扩展出一个Map 的子类Ini.Section
现在有一张表的描述实体类,以及数据访问:

@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 即可

public class ChainDefinitionSectionMetaSource implements FactoryBean{ 
@Autowired 
private ResourceDao resourceDao; 
private String filterChainDefinitions; 
/** 
* 默认premission 字符串 
*/ 
public static final String PREMISSION_STRING="perms[\"{0}\"]"; 
public Section getObject() throws BeansException { 
//获取所有Resource 
List 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 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 文件
"chainDefinitionSectionMetaSource" 
class="org.exitsoft.showcase.vcsadmin.service.account.ChainDefinitionSectionMetaSou 
rce"> 
"filterChainDefinitions"> 
 
/login = authc 
/logout = logout 
/resource/** = anon 
 
 
 
"shiroSecurityFilter" 
class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> 
"securityManager" ref="securityManager" /> 
"loginUrl" value="/login.jsp" /> 
"successUrl" value="/index.jsp" /> 
"unauthorizedUrl" value="/unauthorized.jsp" /> 

 
"filterChainDefinitionMap" ref="chainDefinitionSectionMetaSource" 
/> 
 
"securityManager" 
class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> 
 
"lifecycleBeanPostProcessor" 
class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> 

shiro数据库认证、授权
在 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. 
* 

* 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. *

* 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 token */ 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 resourcesList = new ArrayList();
//————-Getter/Setter————-//
}
@Repository
public class UserDao extends BasicHibernateDao

@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.ChainDefinitionSectionMetaSou 
rce"> 
<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" /> 
 
<property name="filterChainDefinitionMap" ref="chainDefinitionSectionMetaSource" 
/> 
bean> 
<bean id="shiroDataBaseRealm" 
class="org.exitsoft.showcase.vcsadmin.service.account.ShiroDataBaseRealm"> 
 
<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 EHcache 与 Spring EHcache 集成
shiro CacheManager 创建并管理其他Shiro 组件使用的Cache 实例生命周期。因为Shiro
能够访问许多后台数据源,如:身份验证,授权和会话管理,缓存在框架中一直是一流的架
构功能,用来在同时使用这些数据源时提高性能。任何现代开源和/或企业的缓存产品能够
被插入到Shiro 来提供一个快速及高效的用户体验。

自从spring 3.1 问世后推出了缓存功能后,提供了对已有的 Spring 应用增加缓存的支持,
这个特性对应用本身来说是透明的,通过缓存抽象层,使得对已有代码的影响降低到最小。
该缓存机制针对于 Java 的方法,通过给定的一些参数来检查方法是否已经执行,Spring 将
对执行结果进行缓存,而无需再次执行方法。
可通过下列配置来启用缓存的支持:

"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"> 
 
"ehCacheManager" /> 
 
@Cacheable 和**@CacheEvict**来对缓存进行操作
@Cacheable:负责将方法的返回值加入到缓存中
@CacheEvict:负责清除缓存
/**声明了一个名为 persons 的缓存区域,当调用该方法时,Spring 会检查缓存中是否存在 personId 
对应的值。*/ 
@Cacheable("persons") 
public Person profile(Long personId) { ... } 
/**指定多个缓存区域。Spring 会一个个的检查,一旦某个区域存在指定值时则返回*/ 
@Cacheable({"persons", "profiles"}) 
public Person profile(Long personId) { ... } 
 
 
 
/**清空所有缓存*/ 
@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.ChainDefinitionSectionMetaSou 
rce"> 
<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" /> 
 
<property name="filterChainDefinitionMap" ref="chainDefinitionSectionMetaSource" 
/> 
bean> 
<bean id="shiroDataBaseRealm" 
class="org.exitsoft.showcase.vcsadmin.service.account.ShiroDataBaseRealm"> 
 
<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"/> 
 
<bean id="ehCacheManagerFactory" 
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> 
<property name="configLocation" value="classpath:ehcache.xml" /> 
<property name="shared" value="false" /> 
bean> 
 
<bean id="ehCacheManager" 
class="org.springframework.cache.ehcache.EhCacheCacheManager"> 
<property name="cacheManager" ref="ehCacheManagerFactory">property> 
bean> 
 
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> 
<property name="cacheManager" ref="ehCacheManagerFactory" /> 
bean> 
ehcache.xml 
 
<ehcache> 
 
<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 的使用,vcs admin 项目和vcs admin jpa 中都会有例子。可以参考
showcase/vcs_admin 或者showcase/vcs_admin_jpa 项目中的例子去理解。。

你可能感兴趣的:(shiro)