前面一篇文章,展现了Acegi的作用:
现在我将对其中的代码进行讲解:
web.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC '-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN' 'http://java.sun.com/dtd/web-app_2_3.dtd'><web-app>
<display-name>Acegi Security Tutorial Application</display-name>
<!--
- Location of the XML file that defines the root application context
- Applied by ContextLoaderListener.
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext-acegi-security.xml
</param-value>
</context-param>
<!--
FilterToBeanProxy是一个特殊的Servlet过滤器,它本身做的事并不多,
而是将自己的工作委托给Spring应用上下文中的另一个Bean来完成。
被委托的Bean和其他的Servlet过滤器一样,实现了javax.servlet.Filter接口,
但它是通过Spring配置文件而不是web.xml配置的。
-->
<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>/*</url-pattern>
</filter-mapping>
<!--
- Loads the root application context of this web app at startup.
- The application context is then available via
- WebApplicationContextUtils.getWebApplicationContext(servletContext).
-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> <welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list></web-app>
users.properties文件:
#配置用户信息
#超级用户
marissa=koala,ROLE_SUPERVISOR
#普通用户
dianne=emu,ROLE_USER
scott=wombat,ROLE_USER
#禁止用户
peter=opal,disabled,ROLE_USER
applicationContext-acegi-security.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<!-- 一个简单的Acegi配置 -->
<beans>
<!-- Acegi的过滤器链 -->
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value><![CDATA[
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
]]></value>
</property>
</bean> <!-- 设置一个安全上下文。其中HttpSessionIntegrationFilter适用于大多数情形。它将Authentication对象保存在HTTP会话中,使之能够跨越多个请求。
-->
<bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/> <bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
<constructor-arg value="/index.jsp"/> <!-- URL redirected to after logout -->
<constructor-arg>
<list>
<ref bean="rememberMeServices"/>
<bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
</list>
</constructor-arg>
</bean> <!-- AuthenticationProcessingFilter是处理基于表单身份验证的过滤器。
authenticationFailureUrl指定当身份验证失败时
defaultTargetUrl定义了当出现目标URL不在HTTP会话中的异常情况时将发生什么。
这可能发生在用户通过浏览器书签或其他方式而不是通过SecurityEnforcementFilter到达登录页面的情况下。
filterProcessesUrl告诉AuthenticationProcessingFilter应该拦截哪个URL。
这个URL与登录表单中action属性的值一样。它的默认值为/j_acegi_security_check,
但我们在这里显式定义了该值,用于说明你可以根据需要改变这个值。
-->
<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationFailureUrl" value="/acegilogin.jsp?login_error=1"/>
<property name="defaultTargetUrl" value="/"/>
<property name="filterProcessesUrl" value="/j_acegi_security_check"/>
<property name="rememberMeServices" ref="rememberMeServices"/>
</bean> <bean id="securityContextHolderAwareRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter"/>
<bean id="rememberMeProcessingFilter" class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="rememberMeServices" ref="rememberMeServices"/>
</bean> <bean id="anonymousProcessingFilter" class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
<property name="key" value="changeThis"/>
<property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
</bean> <!-- 访问出现异常时的处理 -->
<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
<!--AuthenticationProcessingFilterEntryPoint是一个提供给用户基于HTML的登录表单的认证入口点。 -->
<property name="authenticationEntryPoint">
<!-- 属性loginFromUrl配置了一个登录表单的URL。当需要用户登录时,
AuthenticationProcessingFilterEntryPoint会将用户重定向到该URL。
-->
<bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="/acegilogin.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> <!-- FilterSecurityInterceptor类负责执行安全拦截器的工作 -->
<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<!-- 认证管理器Bean的引用 -->
<property name="authenticationManager" ref="authenticationManager"/>
<!-- 访问决策(即授权)管理器Bean的引用 -->
<property name="accessDecisionManager">
<!-- Acegi的访问决策管理器如何计票
访问决策管理器 如 何 决 策
AffirmativeBased 当至少有一个投票者投允许访问票时允许访问
ConsensusBased 当所有投票者都投允许访问票时允许访问
UnanimousBased 当没有投票者投拒绝访问票时允许访问
-->
<bean class="org.acegisecurity.vote.AffirmativeBased">
<!--默认地,当全部投票者都投弃权票时,所有的访问决策管理者都将拒绝访问资源。
你可以配合为true,即建立了一个“沉默即同意”的策略。换句话说,如果所有的投票者都放弃投票,
则如同它们都投赞成票一样,访问被授权。
-->
<property name="allowIfAllAbstainDecisions" value="false"/>
<property name="decisionVoters">
<!-- decisionVoters属性为访问决策管理器提供一组投票者 -->
<list>
<!-- RoleVoter只在受保护资源有以ROLE_为前缀的配置属性才进行投票。然而,ROLE_前缀只是默认值。你可以选择通过设置rolePrefix属性来重载这个默认前缀:
<bean id="roleVoter"
class="net.sf.acegisecurity.vote.RoleVoter">
<property name="rolePrefix">
<value>GROUP_</value>
</property>
</bean>
在这里,默认的前缀被重载为GROUP_。
-->
<bean class="org.acegisecurity.vote.RoleVoter"/>
<bean class="org.acegisecurity.vote.AuthenticatedVoter"/>
</list>
</property>
</bean>
</property>
<!-- 属性objectDefinitionSource告诉安全拦截器被拦截的各种请求所需要的授权是什么.
第一行是一个指令,表明在比较请求的URL在和紧跟其后定义的模式之前必须首先正规化为小写字母。
该属性的其余几行将URL模式映射为允许用户访问这些URL时必须授予用户的权限。
PATTERN_TYPE_APACHE_ANT指令则URL模式可以采用类似与ANT,或不添加该指令,URL模式以正则表达式的形式描述,如:
<property name="objectDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
/A/admin/.*/Z=ROLE_ADMIN
/A/student/.*/Z=ROLE_STUDENT,ROLE_ALUMNI
/A/instruct/.*/Z=ROLE_INSTRUCTOR
</value>
</property>
-->
<property name="objectDefinitionSource">
<value><![CDATA[
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/secure/extreme/**=ROLE_SUPERVISOR
/secure/**=IS_AUTHENTICATED_REMEMBERED
/**=IS_AUTHENTICATED_ANONYMOUSLY
]]></value>
</property>
</bean> <bean id="rememberMeServices" class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="userDetailsService"/>
<property name="key" value="changeThis"/>
</bean> <!-- 认证管理器负责确定用户身份的
ProviderManager是认证管理器的一个实现,它将验证身份的责任委托给一个或多个认证提供者.
ProviderManager的思路是使你能够根据多个身份管理源来认证用户。
它不是依靠自己实现身份验证,而是逐一遍历一个认证提供者的集合,直到某一个认证提供者能够成功地验证该用户的身份
(或者已经尝试完了该集合中所有的认证提供者)。
-->
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<!-- 从数据库中获取用户信息,包括用户名和密码。-->
<ref local="daoAuthenticationProvider"/>
<!-- 匿名用户 -->
<bean class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
<property name="key" value="changeThis"/>
</bean>
<!-- 已有登录记录的用户 -->
<bean class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
<property name="key" value="changeThis"/>
</bean>
</list>
</property>
</bean> <!--一个DaoAuthenticationProvider是一个简单的认证提供者,它使用DAO来从数据库中获取用户信息(包括用户的密码)。 -->
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userDetailsService"/>
</bean> <!-- UserDetailsService is the most commonly frequently Acegi Security interface implemented by end users -->
<!-- Acegi提供了两个可供选择的AuthenticationDao的实例:InMemoryDaoImpl和JdbcDaoImpl。在此实例中为了方便起见,采用使用内存DAO即InMemoryDaoImpl-->
<bean id="userDetailsService" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userProperties">
<bean class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="/WEB-INF/users.properties"/>
</bean>
</property>
</bean> <!-- This bean is optional; it isn't used by any other bean as it only listens and logs 日志监听-->
<bean id="loggerListener" class="org.acegisecurity.event.authentication.LoggerListener"/></beans>
JSP页面的代码:
index.jsp:
<html>
<body>
<h1>主页</h1>
任何人可浏览此页面。<p><a href="secure/index.jsp">安全页面</a>
<p><a href="secure/extreme/index.jsp">超级安全页面</a>
</body>
</html>
acegilogin.jsp:
<%@ taglib prefix='c' uri='http://java.sun.com/jstl/core' %>
<%@ page import="org.acegisecurity.ui.AbstractProcessingFilter" %>
<%@ page import="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter" %>
<%@ page import="org.acegisecurity.AuthenticationException" %><html>
<head>
<title>登录</title>
<style type="text/css">
*{
font-size:12px
}
</style>
</head> <body>
<h1>登录</h1> <P>有效用户:
<P>
<P>用户名:<b>marissa</b>, 密码: <b>koala</b> (超级用户)
<P>用户名:<b>dianne</b>, 密码: <b>emu</b> (普通用户)
<p>用户名:<b>scott</b>, 密码: <b>wombat</b> (普通用户)
<p>用户名:<b>peter</b>, 密码: <b>opal</b> (禁止用户)
<p>
<%-- 当输入用户名或密码错误时显示提示信息--%>
<c:if test="${not empty param.login_error}">
<font color="red">
抱歉,您登录失败,请重新登录!<BR><BR>
原因: <%= ((AuthenticationException) session.getAttribute(AbstractProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY)).getMessage() %>
</font>
</c:if> <form action="<c:url value='j_acegi_security_check'/>" method="POST">
<table>
<tr><td>用户名:</td><td><input type='text' name='j_username' <c:if test="${not empty param.login_error}">value='<c:out value="${ACEGI_SECURITY_LAST_USERNAME}"/>'</c:if>></td></tr>
<tr><td>密码:</td><td><input type='password' name='j_password'></td></tr>
<tr><td><input type="checkbox" name="_acegi_security_remember_me"></td><td>2周内不用再登录</td></tr> <tr><td colspan='2'><input name="submit" type="submit" value="提交"><input name="reset" type="reset"></td></tr>
</table> </form>
</body>
</html>
accessDenied.jsp:
<%@ page import="org.acegisecurity.context.SecurityContextHolder" %>
<%@ page import="org.acegisecurity.Authentication" %>
<%@ page import="org.acegisecurity.ui.AccessDeniedHandlerImpl" %><h1>对不起,您无权访问!</h1>
<p>
<%= request.getAttribute(AccessDeniedHandlerImpl.ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY)%><p>
<% Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) { %>
Authentication object as a String: <%= auth.toString() %><BR><BR>
<% } %>
secure/index.jsp:
<html>
<body>
<h1>安全页面</h1>
这是一个受保护的页面,当您的登录信息已经被记住或您在本次会话中已认证通过。
<p><a href="../">主页</a>
<p><a href="../j_acegi_logout">退出</a>
</body>
</html>
secure/extreme/index.jsp:
<html>
<body>
<h1>非常安全页面</h1>
这是一个保护页面,只有当你是超级用户才可以浏览此页面。<p><a href="../../">主页</a>
<p><a href="../../j_acegi_logout">退出</a>
</body>
</html>