- 浏览: 29041 次
- 性别:
- 来自: 广州
文章分类
最新评论
-
zhuhuixiao:
lucene的跳跃表是有层次的
(转) lucene索引结构改进-支持单机十亿级别的索引的检索 -
hydex:
谢谢作者
我是直接使用eclipse3.6 ,没有使用myec ...
如何给MyEclipse8.5安装插件 VSS
摘要
Acegi提供了多种身份验证方式(表单验证,CAS等),但只允许一种用户登录,而就个人了解,有一些系统是需要多种用户登录的。比如企业的员工需要登录并使用系统,企业也允许客户登录系统并使用有限的功能。以下尝试剖析Acegi的表单验证过程,并给出一种允许多种用户登录的方案。本方案基本达到“能用”的目的,但不一定是最佳方案。希望这篇文章能起到抛砖引玉的作用,给各位朋友一点参考,也希望各位提出有益的建议。
Acegi的表单验证方式简要分析
一个使用Acegi的表单验证的登录页面通常需要在表单提交时request的j_username和j_password参数赋值,即用户名和密码,而表单则提交到Acegi设定到验证地址。例如:
<input type="text" name="j_username" id="j_username" />
<input type="password" name="j_password" id="j_password" />
<input type="submit" name="login" value="Login" />
</form>
服务器的Servlet容器收到请求后会传递给Acegi的FilterToBeanProxy,这需要在web.xml中进行配置。例如:
<filter-name>securityFilter</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>securityFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
FilterToBeanProxy基本上只起到调用转发的作用。在它的doFilter方法中会找到类型为FilterChainProxy的bean,调用后者的doFilter方法,同时把request、response会chain参数都传递过去。代码如下:
throws IOException, ServletException {
if (!initialized) {
doInit();
}
delegate.doFilter(request, response, chain);
}
上面的代码中的delegate就是找到的类型FilterChainProxy的bean。FilterChainProxy的典型配置如下:
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,
</value>
</property>
</bean>
对于上面的配置,引用一段Acegi联机帮助中的说明来帮助理解:
Internally Acegi Security will use a PropertyEditor to convert the string presented in the above XML fragment into a FilterInvocationDefinitionSource object. What's important to note at this stage is that a series of filters will be run - in the order specified by the declaration - and each of those filters are actually the <bean id> of another bean inside the application context.
实际上,FilterChainProxy的doFilter方法会执行如下处理:
1.读取配置,如果配置为空,则直接调用chain.doFilter,返回
2.如果配置不为空,则根据配置找到各个bean,放入Filter数组中。如果配置中没有配置任何bean,则直接调用chain.doFilter,返回
3.FilterChainProxy创建一个VirtualFilterChain对象,并将chain封装为一个FilterInvocation对象,将它和Filter数组一起传递给VirtualFilterChain的构造函数。VirtualFilterChain的构造函数初始化了一个指针currentPosition,指向Filter数组的第一个元素additionalFilters[0]
4.FilterChainProxy调用VirtualFilterChain的doFilter方法,在该方法中将指针currentPosition前移,调用additionalFilters[0]的doFilter方法。注意这里VirtualFilterChain把自身作为参数传递给additionalFilters[0]的doFilter方法,这样additionalFilters[0]的doFilter方法最后会调用VirtualFilterChain的doFilter方法,这样控制就又回到了VirtualFilterChain!于是VirtualFilterChain又将currentPosition前移,调用additionalFilters[1]的doFilter方法......
5.当additionalFilters中所有元素的doFilter都执行完毕,VirtualFilterChain执行fi.getChain().doFilter,而fi.getChain()的值就是FilterChainProxy的doFilter方法中的参数chain的值。这样我们就理解了FilterChainProxy是怎样让调用兜了个圈,又传递出去的。
重新回到FilterChainProxy的配置,看到它调用了authenticationProcessingFilter这个Filter。让我们看看它的配置:
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationFailureUrl" value="/login.jsp?error=true"/>
<property name="defaultTargetUrl" value="/"/>
<property name="filterProcessesUrl" value="/j_security_check"/>
<property name="rememberMeServices" ref="rememberMeServices"/>
</bean>
authenticationProcessingFilter的其中一个作用就是获取客户端提交的用户名和密码,将它们封装为一个Token,传递给authenticationManager的authenticate方法,由后者负责验证。
看看authenticationManager的配置:
<property name="providers">
<list>
<ref local="daoAuthenticationProvider"/>
<ref local="anonymousAuthenticationProvider"/>
<ref local="rememberMeAuthenticationProvider"/>
</list>
</property>
</bean>
authenticationManager依次调用每个provider的authenticate方法。如果某个provider验证成功则返回;如果所有的验证都不成功,则抛出异常。
让我们看看daoAuthenticationProvider的配置:
<property name="userDetailsService" ref="userDao"/>
<property name="passwordEncoder" ref="passwordEncoder"/>
</bean>
daoAuthenticationProvider在authenticate方法中调用retrieveUser方法取得用户信息,执行基本的验证,然后调用additionalAuthenticationChecks执行附加的验证(比如验证密码是否正确)。在retrieveUser方法中调用userDetailsService的loadUserByUsername方法取得用户信息,而userDetailsService是一个名为userDao的bean。让我们看看userDao的配置:
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
userDao实现了Acegi的UserDetailsService接口,该接口只有loadUserByUsername方法。loadUserByUsername方法根据传入的username取得相应的Employee对象(Employee实现了UserDetails接口),该对象返回给daoAuthenticationProvider,由它和authenticationManager联合完成验证的任务。
以上对Acegi对表单验证过程进行了简单对分析,限于篇幅,无法深入分析源码。但从配置可以画出验证过程的对象图如下:
从图中可以看出,尽管Acegi调用了多个Filter来完成验证过程,关键点却在三处:
1.在客户端输入身份验证信息,包括用户名和密码
2.AuthenticationProcessingFilter取出用户名和密码,封装为一个Token往后传递
3.DaoAuthenticationProvider从系统中找出用户资料,并和ProviderManager一起执行验证
实现多种用户登录
很明显,要让系统识别不同种类的用户,必须设立一个用户类型标志。问题就转化为:
1.用户在客户端输入身份信息时系统就必须设立相应的标志
2.该标志如何传递到DaoAuthenticationProvider
3.DaoAuthenticationProvider如何识别该标志,并从相应类型的用户中找到指定用户
我不打算改动Acegi的源码,只打算扩展出我需要的功能。
首先在登录页面中加入用户类型标志j_userkind。在登录页面中加入如下代码:
其中0代码员工,1代码客户。可以考虑在登录页面中增加一个选项,如果用户要以员工身份登录,则把j_userkind置为0;如果用户要以客户身份登录,则把j_userkind置为1。也可以提供两个登录页面,其中一个员工专用(j_userkind被强制置为0),另一个客户专用(j_userkind被强制置为1)
系统如何根据收到的用户类型标志去读取指定的用户呢?如果在代码中写死(比如当用户类型标志=0时,读取员工;当用户类型标志=1时,读取客户)非常不好,还是通过配置来确定比较灵活。首先编写UserKindComparisonAware接口:
public interface UserKindComparisonAware {
public void setExpectedUserKind(String expectedUserKind);
public void setCurrentUserKind(String currentUserKind);
}
该接口说明实现类需要实现两个方法,setExpectedUserKind用于接受一个期望的用户类型标志(通常该标志通过配置来设置),setCurrentUserKind用于接受当前登录用户的用户类型标志(系统在运行时捕获,并传递给实现类)
编写MKUDaoAuthenticationProvider类:
import cn.net.cogent.summer.extension.acegisecurity.BadUserKindException;
import cn.net.cogent.summer.extension.acegisecurity.providers.UserKindComparisonAware;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.providers.dao.DaoAuthenticationProvider;
import org.acegisecurity.userdetails.UserDetails;
import cn.net.cogent.summer.util.LoggerUtil;
public class MKUDaoAuthenticationProvider extends DaoAuthenticationProvider implements
UserKindComparisonAware {
private String expectedUserKind;
private String currentUserKind;
public String getExpectedUserKind() {
return expectedUserKind;
}
public void setExpectedUserKind(String expectedUserKind) {
this.expectedUserKind = expectedUserKind;
}
public String getCurrentUserKind() {
return currentUserKind;
}
public void setCurrentUserKind(String currentUserKind) {
this.currentUserKind = currentUserKind;
}
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
LoggerUtil.getLogger().debug("expectedUserKind = '" + expectedUserKind + "', currentUserKind = '" + currentUserKind + "'");
if (currentUserKind.equals(expectedUserKind))
super.additionalAuthenticationChecks(userDetails, authentication);
else
throw new BadUserKindException(
"Flag UserKind does not match");
}
}
该类继承自DaoAuthenticationProvider并实现UserKindComparisonAware接口,在additionalAuthenticationChecks方法中判断当前登录用户的用户类型标志与期望的用户类型标志是否一致,如果一致则执行父类的additionalAuthenticationChecks,完成验证;否则抛出一个BadUserKindException异常,表明验证失败。BadUserKindException继承自org.acegisecurity.AuthenticationException,具体的代码略
在applicationContext.xml中删除daoAuthenticationProvider相关的配置,增加如下配置:
<property name="userDetailsService" ref="customerDao"/>
<property name="passwordEncoder" ref="passwordEncoder"/>
<property name="expectedUserKind" value="1"/>
</bean>
<bean id="userDaoAuthenticationProvider" class="cn.net.cogent.summer.extension.acegisecurity.providers.dao.MKUDaoAuthenticationProvider">
<property name="userDetailsService" ref="userDao"/>
<property name="passwordEncoder" ref="passwordEncoder"/>
<property name="expectedUserKind" value="0"/>
</bean>
可以看出customerDaoAuthenticationProvider仅用于验证客户(其expectedUserKind被指定为1),而userDaoAuthenticationProvider仅用于验证员工(其expectedUserKind被指定为0)。customerDao的配置如下:
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
CustomerDaoHibernate的代码如下:
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UserDetailsService;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import cn.net.cogent.summer.model.Customer;
import org.appfuse.dao.hibernate.GenericDaoHibernate;
import org.springframework.dao.DataAccessException;
import java.util.List;
public class CustomerDaoHibernate extends GenericDaoHibernate<Customer, Long> implements UserDetailsService {
public CustomerDaoHibernate() {
super(Customer.class);
}
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
List<Customer> users = getHibernateTemplate().find("from Customer where username=?", username);
if (users == null || users.isEmpty()) {
throw new UsernameNotFoundException("Customer '" + username + "' not found");
} else {
return (UserDetails) users.get(0);
}
}
}
可以看出CustomerDaoHibernate是取得一个Customer对象(实现了UserDetails接口),而不是Employee。
修改authenticationManager的配置如下:
<property name="providers">
<list>
<ref local="customerDaoAuthenticationProvider"/>
<ref local="userDaoAuthenticationProvider"/>
<ref local="anonymousAuthenticationProvider"/>
<ref local="rememberMeAuthenticationProvider"/>
</list>
</property>
</bean>
在哪里捕获当前登录用户的用户类型标志,并传递给MKUDaoAuthenticationProvider呢?我决定增加一个名为PreAuthenticationProcessingFilter的Filter,放在AuthenticationProcessingFilter之前,代码如下:
import cn.net.cogent.summer.extension.acegisecurity.providers.UserKindComparisonAware;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
public class PreAuthenticationProcessingFilter implements Filter, ApplicationContextAware {
public static final String ACEGI_SECURITY_FORM_USERKIND = "j_userkind";
private FilterConfig filterConfig;
private boolean initialized = false;
private Map targetBeans;
private String targetClass;
private ApplicationContext applicationContext;
public String getTargetClass() {
return targetClass;
}
public void setTargetClass(String targetClass) {
this.targetClass = targetClass;
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void destroy() {
}
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
if (!(request instanceof HttpServletRequest)) {
throw new ServletException("Can only process HttpServletRequest");
}
if (!initialized) {
doInit();
}
String userKind = obtainUserKind((HttpServletRequest)request);
for (Iterator it = targetBeans.values().iterator(); it.hasNext();) {
UserKindComparisonAware comparison = (UserKindComparisonAware)it.next();
comparison.setCurrentUserKind(userKind);
}
chain.doFilter(request, response);
}
private synchronized void doInit() throws ServletException {
if ((targetClass == null) || "".equals(targetClass)) {
throw new ServletException("targetClass must be specified");
}
Class _targetClass;
try {
_targetClass = Thread.currentThread().getContextClassLoader().loadClass(targetClass);
} catch (ClassNotFoundException ex) {
throw new ServletException("Class of type " + targetClass + " not found in classloader");
}
targetBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, _targetClass, true, true);
if (targetBeans.size() == 0) {
throw new ServletException("Bean context must contain at least one bean of type " + targetClass);
}
for (Iterator it = targetBeans.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry)it.next();
if (!(entry.getValue() instanceof UserKindComparisonAware)) {
throw new ServletException("Bean '" + entry.getKey() +
"' does not implement cn.net.cogent.summer.extension.acegisecurity.providers.UserKindComparisonAware");
}
}
// Set initialized to true at the end of the synchronized method, so
// that invocations of doFilter() before this method has completed will not
// cause NullPointerException
initialized = true;
}
protected String obtainUserKind(HttpServletRequest request) {
return request.getParameter(ACEGI_SECURITY_FORM_USERKIND);
}
}
PreAuthenticationProcessingFilter需要在初始化参数中指定targetClass,该参数的值是一个类,该类实现了UserKindComparisonAware接口。PreAuthenticationProcessingFilter找到容器中所有该类的实例,并把捕获的当前登录用户的用户类型标志赋值给它们。PreAuthenticationProcessingFilter的配置如下:
class="cn.net.cogent.summer.extension.acegisecurity.ui.webapp.PreAuthenticationProcessingFilter">
<property name="targetClass"
value="cn.net.cogent.summer.extension.acegisecurity.providers.dao.MKUDaoAuthenticationProvider"/>
</bean>
还需要把preAuthenticationProcessingFilter加入到filterChainProxy的配置中:
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=,preAuthenticationProcessingFilter,authenticationProcessingFilter,
</value>
</property>
</bean>
注意把它放在authenticationProcessingFilter的前面
至此我们初步实现了使用Acegi实现多种用户登录
发表评论
-
【MongoDB for Java】Java操作MongoDB
2013-09-04 11:23 508开发环境: System:Win ... -
Java 性能分析工具
2013-08-30 10:54 710如何利用 JConsole观察分析Java程序的运行,进行排错 ... -
java自带的jvm分析工具
2013-08-14 10:14 746这段时间觉得很有必要对java的内存分析工具进行熟悉,这样以 ... -
Java冒泡排序
2015-05-04 16:47 442冒泡排序(Bubble Sort)是一种简单的排序算法。它 ... -
Java线程的5种状态及切换(透彻讲解)
2013-08-13 09:08 434Java中的线程的生命周期大体可分为5种状态。 ①NEW: ... -
Java基础笔记 – 线程同步问题 解决同步问题的方法 synchronized方法 同步代码块
2013-08-13 07:20 5361、多线程的同步: 1.1、同步机制: 在多线程中,可 ... -
Java线程同步 (synchronized wait notify)
2013-08-12 23:50 612注:wait notify 都是Object的方法 ... -
查看 jvm gc情况,使用JDK自带jstat
2013-05-30 00:35 770需要查询当前JAVA的PID windows环境,任务管理 ... -
DB QL CODE AND SQL State
2012-08-14 14:56 992操作数据库过程中,遇 ... -
如何给MyEclipse8.5安装插件 VSS
2010-12-06 11:17 2865一、下载VSS插件 可以去官方网站下载,也可以在这里http: ... -
Struts2 json jar 冲突
2010-10-21 16:59 1203今天做一个项目(ssh)的时候遇到“java.lang.NoS ... -
Struts2 使用<s:radio 标签的默认值的方法
2010-10-18 10:10 1001写法如下 <s:radio key="bi ... -
介绍ExtremeTable的下一代Jmesa
2010-09-18 23:13 989说到ExtremeTable(以下简称 ... -
appfuse2.0 FCKEditor整合
2010-08-20 00:43 774appfuse2.0使用的是SpringMVC好些默认的设置没 ... -
JavaSE6脚本引擎(java中javascript脚本)
2010-06-25 15:15 906在默认情况下,Java SE ... -
Java正则表达式入门
2010-06-25 14:45 414< type="text/javascript ... -
java版本的escape和unescape函数
2010-06-24 16:05 596class EscapeUnescape{ public s ...
相关推荐
这篇博客将深入解析一个配置了Acegi Security的`applicationContext-acegi-security.xml`文件,帮助我们理解如何将LDAP与Acegi集成以实现更安全的Web应用。 **LDAP基础** LDAP是一种标准的网络协议,用于存储和...
CAS 是一个基于 Web 的身份验证协议,它的主要目标是简化用户登录流程,让用户只需在一个地方登录即可访问所有支持 CAS 的应用。CAS 服务器负责验证用户的身份,而客户端应用则依赖于 CAS 服务器的认证结果,无需...
Acegi是Spring框架早期的一个安全模块,主要用于身份验证和授权管理。在2009年,Acegi被Spring Security所吸收,成为其前身,因此理解Acegi有助于我们深入理解Spring Security的安全机制。以下是对Acegi的详细介绍和...
在"acegi-security-1.0.7"这个版本中,包含了完整的ACEGI安全框架的所有包文件,使得开发者能够方便地集成和使用这一强大的安全工具。 ### 1. 框架概述 ACEGI Security(后被Spring Security替代)主要目的是解决...
这个"acegi-security-tiger-1.0.0-RC2.jar.zip"压缩包包含的是Acegi Security的一个早期版本——1.0.0 Release Candidate 2(RC2),专门针对Tiger(Java SE 5.0)版本的Java开发环境。 Acegi Security的主要功能...
在`acegi-lib2`这个压缩包中,可能包含了Acegi的库文件和其他相关资源,比如示例代码或配置文件。为了进一步学习和实践Acegi Security,你可以研究这些文件,了解如何在实际项目中集成和配置Acegi。 总之,Acegi ...
总结,Grails Acegi 0.5插件是Grails框架下的一种重要安全解决方案,它引入了Spring Security的经典功能,为Grails开发者提供了强大的身份验证和授权手段。虽然随着时间的推移,新的安全框架和插件不断涌现,但了解...
这个压缩包中的"acegi-sample"部分可能包含了一个示例项目,展示了如何在实际应用中配置和使用Acegi Security。而"spring-1.2.4.jar"则是Spring框架的一个较旧版本,表明Acegi Security是在Spring 1.x时代设计的,那...
AceGI提供了多种内置的AuthenticationProvider,如DaoAuthenticationProvider,它可以从数据库中验证用户信息。 授权方面,AceGI支持角色和权限的概念。角色是预定义的一组权限集合,用户可以被分配一个或多个角色...
包含acegi-security-1.0.7.jar,acegi-security-1.0.7-sources.jar,acegi-security-cas-1.0.7.jar,acegi-security-cas-1.0.7-sources.jar,acegi-security-catalina-1.0.7.jar,acegi-security-catalina-1.0.7-...
acegi-security 1.0.2.jar
Acegi 是一个在Java开发领域,特别是Spring框架中曾经广泛使用的安全组件,全称为Acegi Security。这个系统为Spring应用程序提供了全面的安全管理解决方案,包括身份验证、授权、会话管理以及安全事件处理等功能。...
Acegi Security提供了一系列安全服务,如RememberMe服务,可以记住用户的登录状态,使用户在一段时间内无需重新登录。另外,还包括了密码加密服务,确保密码在存储和传输过程中的安全性。 五、与其他Spring组件的...
acegi-security-0.8.3驱动程序