公司现有一项目需要使用acegi控制身份验证,所以这几天都在学习acegi,经过网上查找资料,个人比较推崇网友zhanjia写的教程,发现比较完善的(http://zhanjia.iteye.com/category/43399)。
acegi配置与spring security配置比较相似,现在我也把自己经过修改后的并且可以运行的项目放在此blog中,以供大家交流。
首先我们需要在web.xml中定义相关设置(Filter、Listener、contextConfigLocation)。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:/com/javaeye/sunjiesh/jeestudydemo/spring/spring-common.xml,
classpath:/com/javaeye/sunjiesh/jeestudydemo/spring/spring-hibernate.xml,
classpath:/com/javaeye/sunjiesh/jeestudydemo/spring/spring-services.xml,
classpath:/com/javaeye/sunjiesh/jeestudydemo/spring/spring-actions.xml,
classpath:/com/javaeye/sunjiesh/jeestudydemo/spring/spring-acegi2.xml
</param-value>
</context-param>
<filter>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>org.acegisecurity.util.FilterChainProxy</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<url-pattern>*.html</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<url-pattern>/j_acegi_security_check</url-pattern>
</filter-mapping>
<!-- Listener -->
<listener>
<listener-class> org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!--
Acegi
Security能够限定次数防止一个principal多次并行认证到同一个应用。许多ISV利用这一点来加强授权管理,网管也喜欢这个特性因为可以防止一个用户名被重复使用。例如,你可以限制“Batman”用户从两个不同的session登录系统。
-->
<listener>
<listener-class>org.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>
</listener>
在配置完web.xml后,接下来就需要配置acegi的配置文件了,我把很多说明直接放在了xml文件的注释中。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<!--
FilterChainProxy过滤器,需要把需要的过滤器按照顺序放入,这样filterChainProxy会按照过滤器写入的顺序调用。
-->
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,exceptionTranslationFilter,filterSecurityInterceptor,logoutFilter
</value>
</property>
</bean>
<!--
每次HttpSession开始或者结束的时候,web.xml中的HttpSessionEventPublisher都会发布一个
ApplicationEvent事件到Spring的ApplicationContext。这是至关重要的,因为这个机制允许在session结束的时候,SessionRegistryImpl会得到通知。这解释了为什么我们需要在ConcurrentSessionFilter中指向
SessionRegistryImpl的实例。
-->
<bean id="concurrentSessionFilter" class="org.acegisecurity.concurrent.ConcurrentSessionFilter">
<property name="sessionRegistry" ref="sessionRegistry"></property>
<!--
如果concurrentSessionController的exceptionIfMaximumExceeded属性设置为true,那么一旦并发HttpSession数量超过限额,将会重定向到expiredUrl指定的路径
-->
<property name="expiredUrl">
<value>/concurrentError.jsp</value>
</property>
</bean>
<!--
注意,我们的程序一般并不用直接与SessionRegistryImpl打交道,你只需在Spring的配置文件定义一个Bean就行了
-->
<bean id="sessionRegistry" class="org.acegisecurity.concurrent.SessionRegistryImpl">
</bean>
<!--
httpSessionContextIntegrationFilter过滤器, 每次request前
HttpSessionContextIntegrationFilter从Session中获取Authentication对象,
在request完后, 又把Authentication对象保存到Session中供下次request使用,
此filter必须其他Acegi filter前使用,使之能跨越多个请求。
-->
<bean id="httpSessionContextIntegrationFilter"
class="org.acegisecurity.context.HttpSessionContextIntegrationFilter">
<property name="context">
<value>org.acegisecurity.context.SecurityContextImpl</value>
</property>
</bean>
<!--
处理认证请求(通常是一个登录页面的表单请求)。
当身份验证成功时,AuthenticationProcessingFilter会在会话中放置一个Authentication对象,
并且重定向到登录成功页面 属性说明: authenticationFailureUrl:验证失败后跳转的页面
defaultTargetUrl:验证通过后默认跳转页面 filterProcessesUrl:登录页面上的action需要与此对应。
rememberMeServices:如果需要cookie,则需要填入
-->
<bean id="authenticationProcessingFilter"
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="authenticationFailureUrl">
<value>/loginerr.html</value>
</property>
<property name="defaultTargetUrl">
<value>/afterLogin.do</value>
</property>
<property name="filterProcessesUrl">
<value>/j_acegi_security_check</value>
</property>
<!-- <property name="rememberMeServices" ref="rememberMeServices" />-->
</bean>
<!-- 该Filter会在用户登录后,在本地机器上记录用户cookies信息,这样下次访问就不用再登录了。 -->
<bean id="rememberMeProcessingFilter"
class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="rememberMeServices" ref="rememberMeServices" />
</bean>
<!--
匿名过滤器。 该过滤器是用来对匿名用户的处理。
如果用户尚未登录,将生成一个匿名用户的Authentication存放到ContextHolder中。 即当不存在任何授权信息时,
自动为Authentication对象添加userAttribute中定义的匿名用户权限。
-->
<bean id="anonymousProcessingFilter"
class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
<property name="key" value="changeThis" />
<property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS" />
</bean>
<!--
exceptionTranslationFilter异常转换过滤器,
主要是处理AccessDeniedException和AuthenticationException,将给每个异常找到合适的"去向"。
在此配置中。
-->
<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="/login.jsp" />
<property name="forceHttps" value="false" />
</bean>
</property>
<property name="accessDeniedHandler">
<bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
<property name="errorPage" value="/accessDenied.jsp" />
</bean>
</property>
</bean>
<!-- 采用数据库读取URL保护配置的形式 -->
<bean id="filterSecurityInterceptor"
class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager" />
<property name="accessDecisionManager" ref="httpRequestAccessDecisionManager" />
<!-- <property name="objectDefinitionSource">-->
<!-- <value><![CDATA[-->
<!-- CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON-->
<!-- PATTERN_TYPE_APACHE_ANT-->
<!-- /secure.jsp=ROLE_SUPERVISOR-->
<!-- /admin/index.jsp=ROLE_SUPERVISOR-->
<!-- /protect*.html=ROLE_SUPERVISOR-->
<!-- ]]></value>-->
<!-- </property>-->
<property name="objectDefinitionSource" ref="urlFilterInvocationDefinitionSource"></property>
</bean>
<!-- 注销过滤器 -->
<bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
<constructor-arg value="/logoutSuccess.jsp" />
<constructor-arg>
<list>
<!-- <ref bean="rememberMeServices" />-->
<bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler" />
</list>
</constructor-arg>
</bean>
<!--
起到认证管理的作用,它将验证的功能委托给多个Provider, 并通过遍历Providers,以保证获取不同来源的身份认证,
若某个Provider能成功确认当前用户的身份,
authenticate()方法会返回一个完整的包含用户授权信息的Authentication对象,
否则会抛出一个AuthenticationException。 Acegi提供了不同的AuthenticationProvider的实现。
如: DaoAuthenticationProvider 从数据库中读取用户信息验证身份
AnonymousAuthenticationProvider 匿名用户身份认证
RememberMeAuthenticationProvider 已存cookie中的用户信息身份认证
AuthByAdapterProvider 使用容器的适配器验证身份 CasAuthenticationProvider
根据Yale中心认证服务验证身份, 用于实现单点登陆 JaasAuthenticationProvider
从JASS登陆配置中获取用户信息验证身份 RemoteAuthenticationProvider 根据远程服务验证用户身份
RunAsImplAuthenticationProvider 对身份已被管理器替换的用户进行验证
X509AuthenticationProvider 从X509认证中获取用户信息验证身份
TestingAuthenticationProvider 单元测试时使用
每个认证者会对自己指定的证明信息进行认证,如DaoAuthenticationProvider仅对UsernamePasswordAuthenticationToken这个证明信息进行认证。
-->
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="daoAuthenticationProvider" />
<ref local="anonymousAuthenticationProvider" />
<!--<ref local="rememberMeAuthenticationProvider" /> -->
</list>
</property>
<property name="sessionController" ref="concurrentSessionController">
</property>
</bean>
<bean id="rememberMeServices"
class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
<property name="parameter" value="_acegi_security_remember_me"></property>
<property name="userDetailsService" ref="userDetailsService" />
<!-- cookie中的键值, 防止保存到客户端的cookie中的加密串被恶意篡改 -->
<property name="key" value="foobar" />
<!-- cookie有效时间, 单位为秒, 这里设定为5天内不用再登陆 -->
<property name="tokenValiditySeconds" value="432000" />
</bean>
<!--
DaoAuthenticationProvider 从数据库中读取用户信息验证身份。 进行简单的基于数据库的身份验证。
DaoAuthenticationProvider获取数据库中的账号密码并进行匹配,
若成功则在通过用户身份的同时返回一个包含授权信息的Authentication对象,
否则身份验证失败,抛出一个AuthenticatiionException。
-->
<bean id="daoAuthenticationProvider"
class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userDetailsService" />
<!-- <property name="passwordEncoder" ref="passwordEncoder" />-->
<property name="userCache">
<ref local="userCache" />
</property>
</bean>
<!-- 密名验证Provider -->
<bean id="anonymousAuthenticationProvider"
class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
<property name="key" value="changeThis" />
</bean>
<bean id="rememberMeAuthenticationProvider"
class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
<property name="key" value="foobar" />
<!--
key必须和rememberMeServices中的key一致
-->
</bean>
<!--
exceptionIfMaximumExceeded一般设置为false. 为true时, 如果已有一个该用户登录了,
那么在另一个地方登录该用户将抛出异常 如果设置为false, 那么, 如果已有一个该用户登录了系统, 那么在另一个地方也可以登录,
登录后前者会被逼退出系统
-->
<bean id="concurrentSessionController"
class="org.acegisecurity.concurrent.ConcurrentSessionControllerImpl">
<property name="maximumSessions" value="1"></property>
<property name="sessionRegistry" ref="sessionRegistry"></property>
<property name="exceptionIfMaximumExceeded" value="true"></property>
</bean>
<bean id="userDetailsService"
class="com.javaeye.sunjiesh.jeestudydemo.service.UserDetailServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
<!--
httpRequestAccessDecisionManager(投票通过策略管理器)用于管理投票通过策略。
Acegi提供三种投票通过策略的实现: AffirmativeBased(至少一个投票者同意方可通过),
ConsensusBased(多数投票者同意方可通过), UnanimousBased(所有投票者同意方可通过)。
本程序采用AffirmativeBased策略,并且禁止“没人反对就通过”的投票策略。
-->
<bean id="httpRequestAccessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions" value="false" />
<property name="decisionVoters">
<list>
<bean class="org.acegisecurity.vote.RoleVoter" />
</list>
</property>
</bean>
<!-- FilterInvocationDefinitionSource的自定义实现类,通过数据库读取Web资源保护配置的形式 -->
<bean id="urlFilterInvocationDefinitionSource"
class="com.javaeye.sunjiesh.jeestudydemo.acegi.UrlFilterInvocationDefinitionSource">
<property name="urlInvocationDefinition" ref="urlInvocationDefinition"></property>
</bean>
<bean id="urlInvocationDefinition"
class="com.javaeye.sunjiesh.jeestudydemo.acegi.UrlSecuredUrlDefinition">
<property name="urlService" ref="urlService"></property>
</bean>
<!--
EhCacheBasedUserCache是EhCache的一个缓存实现,提供了向缓存中放入、取得和删除用户明细信息的功能,Acegi需要用它来管理缓存。
-->
<bean id="userCache"
class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
<property name="cache" ref="userCacheBackend" />
</bean>
<!-- EhCacheFactoryBean是用于维护Cache实例的工厂Bean,Cache需要依赖于CacheManager而存在 -->
<bean id="userCacheBackend" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager" ref="cacheManager" />
<property name="cacheName" value="userCache" />
</bean>
<!-- 缓存管理器,一个CacheManager能够创建和维护多个Cache实例 -->
<bean id="cacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" />
</beans>
接下来就是在配置文件中所提及的几个Java类了。
package com.javaeye.sunjiesh.jeestudydemo.acegi;
import java.io.Serializable;
import org.acegisecurity.ConfigAttributeDefinition;
/**
* 存放URL与对应角色集合的实体类
*
* @author sunjie
* @since 2009-6-9
*
*/
public class UrlEntryHolder implements Serializable {
private static final long serialVersionUID = 2317309106087370323L;
/**
* 保护的URL模式
*/
private String url;
/**
* 要求的角色集合
*/
private ConfigAttributeDefinition cad;
public String getUrl() {
return url;
}
public ConfigAttributeDefinition getCad() {
return cad;
}
public void setUrl(String url) {
this.url = url;
}
public void setCad(ConfigAttributeDefinition cad) {
this.cad = cad;
}
}
package com.javaeye.sunjiesh.jeestudydemo.acegi;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.acegisecurity.ConfigAttributeDefinition;
import org.acegisecurity.SecurityConfig;
import com.javaeye.sunjiesh.jeestudydemo.po.Authority;
import com.javaeye.sunjiesh.jeestudydemo.po.Url;
import com.javaeye.sunjiesh.jeestudydemo.service.iface.IUrlService;
/**
*
* @author sunjie
* @since 2009-6-9
*
*/
public class UrlSecuredUrlDefinition {
private IUrlService urlService;
/**
* 得到所有URL资源保护配置。
*
* @return
*/
public List<UrlEntryHolder> getAllUrlEntryHolder() {
List<UrlEntryHolder> urlEHs = new LinkedList<UrlEntryHolder>();
List<Url> urls = urlService.getAllUrls();
for (int i = 0; i < urls.size(); i++) {
Url url = urls.get(i);
UrlEntryHolder urlEH = new UrlEntryHolder();
urlEH.setUrl(url.getUrl());
System.out.println("受保护的资源为"+url.getUrl());
ConfigAttributeDefinition cad = new ConfigAttributeDefinition();
Set<Authority> auths = url.getAuths();
for (Authority auth : auths) {
cad.addConfigAttribute(new SecurityConfig(auth.getName()));
}
urlEH.setCad(cad);
urlEHs.add(urlEH);
}
return urlEHs;
}
public IUrlService getUrlService() {
return urlService;
}
public void setUrlService(IUrlService urlService) {
this.urlService = urlService;
}
}
package com.javaeye.sunjiesh.jeestudydemo.acegi;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import net.sf.ehcache.Ehcache;
import org.acegisecurity.ConfigAttributeDefinition;
import org.acegisecurity.intercept.web.FilterInvocation;
import org.acegisecurity.intercept.web.FilterInvocationDefinitionSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
/**
* FilterInvocationDefinitionSource实现类
*
* @author sunjie
* @since 2009-6-9
*
*/
public class UrlFilterInvocationDefinitionSource implements
FilterInvocationDefinitionSource {
protected static final Log logger = LogFactory
.getLog(UrlFilterInvocationDefinitionSource.class);
private UrlSecuredUrlDefinition urlInvocationDefinition;
private PathMatcher pathMatcher = new AntPathMatcher();
@SuppressWarnings("unchecked")
public ConfigAttributeDefinition getAttributes(Object object)
throws IllegalArgumentException {
if ((object == null) || !this.supports(object.getClass())) {
throw new IllegalArgumentException("抱歉,目标对象不是FilterInvocation类型");
}
// TODO 抽取出待请求的URL
String url = ((FilterInvocation) object).getRequestUrl();
// TODO 获取所有UrlEntryHolder列表(url与角色集合对应列表),如果没有,则返回null。
List<UrlEntryHolder> urlEHs = getEntryHolderList();
if (urlEHs == null || urlEHs.size() == 0) {
return null;
}
// TODO 去掉待请求url参数信息,数据库中的url信息不带有参数
int firstQuestionMarkIndex = url.indexOf("?");
if (firstQuestionMarkIndex != -1) {
url = url.substring(0, firstQuestionMarkIndex);
}
Iterator iter = urlEHs.iterator();
// TODO 循环判断用户是否有权限访问当前url,
// 有则返回ConfigAttributeDefinition(角色集合)
while (iter.hasNext()) {
UrlEntryHolder entryHolder = (UrlEntryHolder) iter.next();
// TODO 判断当前访问的url是否符合entryHolder.getUrl()模式,
// 即判断用户是否有权限访问当前url,如url="/secure/index.jsp",
// entryHolder.getUrl()="/secure/**", 则有权限访问
boolean matched = pathMatcher.match(entryHolder.getUrl(), url);
if (logger.isDebugEnabled()) {
logger.debug("匹配到如下URL: '" + url + ";模式为 "
+ entryHolder.getUrl() + ";是否被匹配:" + matched);
}
// TODO 如果在用户所有被授权的URL中能找到匹配的,
// 则返回该ConfigAttributeDefinition(角色集合)
if (matched) {
return entryHolder.getCad();
}
}
return null;
}
@SuppressWarnings("unchecked")
public Iterator getConfigAttributeDefinitions() {
Set set = new HashSet();
Iterator iter = this.getEntryHolderList().iterator();
while (iter.hasNext()) {
UrlEntryHolder entryHolder = (UrlEntryHolder) iter.next();
set.add(entryHolder.getCad());
}
return set.iterator();
}
public boolean supports(Class clazz) {
if (FilterInvocation.class.isAssignableFrom(clazz)) {
return true;
} else {
return false;
}
}
public List<UrlEntryHolder> getEntryHolderList() {
//urlInvocationDefinition = new UrlSecuredUrlDefinition();
return urlInvocationDefinition.getAllUrlEntryHolder();
}
public UrlSecuredUrlDefinition getUrlInvocationDefinition() {
return urlInvocationDefinition;
}
public void setUrlInvocationDefinition(
UrlSecuredUrlDefinition urlInvocationDefinition) {
this.urlInvocationDefinition = urlInvocationDefinition;
}
}
package com.javaeye.sunjiesh.jeestudydemo.service;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UserDetailsService;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.apache.log4j.Logger;
import org.springframework.dao.DataAccessException;
import com.javaeye.sunjiesh.jeestudydemo.dao.iface.IUserDAO;
import com.javaeye.sunjiesh.jeestudydemo.po.Authority;
import com.javaeye.sunjiesh.jeestudydemo.po.Role;
import com.javaeye.sunjiesh.jeestudydemo.po.User;
/**
* 实现SpringSecurity的UserDetailsService接口,获取用户Detail信息
*
* @author sunjie
*/
public class UserDetailServiceImpl implements UserDetailsService {
private static Logger LOG = Logger.getLogger(UserDetailServiceImpl.class);
private IUserDAO userDao;
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
if (username == null) {
LOG.error("username is null");
}
if (userDao == null) {
LOG.error("userDao is null");
}
User user = userDao.getUserByUserName(username);
// TOTO 判断用户存在,如果不存在,则抛出异常。
if (user == null) {
LOG.error(username + " is not exist",
new UsernameNotFoundException(username + " is not exist"));
}
List<GrantedAuthority> authsList = new ArrayList<GrantedAuthority>();
Set<Role> roles=user.getRoles();
System.out.println("user.getRoles().size()=" + user.getRoles().size());
for (Role role : user.getRoles()) {
for (Authority authority : role.getAuths()) {
authsList.add(new GrantedAuthorityImpl(authority.getName()));
}
}
// TODO
org.acegisecurity.userdetails.User userdetail = new org.acegisecurity.userdetails.User(
user.getLoginName(), user.getPassword(), true, true, true,
true, authsList.toArray(new GrantedAuthority[authsList.size()]));
return userdetail;
}
public void setUserDao(IUserDAO userDao) {
this.userDao = userDao;
}
public IUserDAO getUserDao() {
return userDao;
}
}
备注: 其它像DAO类及Service相对简单,所以不在此占用空间,可以再附件中查看。
本文详细介绍了如何使用 Acegi 进行 Web 应用的安全配置,包括配置 web.xml 文件、定义过滤器和拦截器,以及实现用户权限管理。
340

被折叠的 条评论
为什么被折叠?



