Acegi在SSH(Struts+Spring+Hibernate)初级应用



上周研究了一下Acegi安全框架在SSH中的应用,一开始就因为Acegi配置文件的加载方式困惑了几天,到底是加在以插件的形式在struts配置文件struts-config.xml里声明还是在部署描述符web.xml里声明?最后得出答案,在web.xml里声明。正如Acegi宣称的那样,它将低耦合地与你已有应用系统做无缝结合。下面就它的初级应用做个介绍。

其中,需要用到的jar包有: acegi-security-1.0.3.jar acegi-security-cas-1.0.3.jar commons-codec-1.3.jar 1. 首先是部署描述符web.xml <?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.4" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<!-- 容器启动参数 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext-acegi-security.xml</param-value>
</context-param>

<!-- 字符编码过滤器 -->
<filter>
<filter-name>Set Character Encoding</filter-name>
<filter-class>com.xps.util.SetCharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Set Character Encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!-- Acegi过滤器 -->
<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>

<!-- struts控制器初始化配置 -->
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>3</param-value>
</init-param>
<init-param>
<param-name>detail</param-name>
<param-value>3</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>

<!-- 用于Acegi的context装载的监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- 系统默认主页 -->
<welcome-file-list>
<welcome-file>/jsp/index.jsp</welcome-file>
</welcome-file-list>
</web-app> 红色部分是应用Acegi需要添加的,首先context-param将Acegi的配置文件作为容器启动参数装载进去,而FilterToBeanProxy正如它名字表示的一样,是一个过滤器代理,它负责将不同的资源对应到各个过滤器中去,ContextLoaderListener用于context装载情况的监听。 2. 其次是比较复杂的Acegi配置文件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">
<beans>

<!-- 为Acegi数据库验证准备的数据源配置 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost:3306/xgame</value>
</property>
<property name="username">
<value>root</value>
</property>
<property name="password">
<value>123456</value>
</property>
</bean>

<!-- 过滤器链代理 -->
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,logoutFilter,
authenticationProcessingFilter,securityContextHolderAwareRequestFilter,
rememberMeProcessingFilter,anonymousProcessingFilter,
exceptionTranslationFilter,filterInvocationInterceptor
</value>
</property>
</bean>

<!-- 会话的context整合过滤器 -->
<bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>

<!-- 注销过滤器 -->
<bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
<constructor-arg value="/jsp/index.jsp"/> <!-- URL redirected to after logout -->
<constructor-arg>
<list>
<ref bean="rememberMeServices"/>
<bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
</list>
</constructor-arg>
</bean>

<!-- 认证处理过滤器 -->
<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationFailureUrl" value="/jsp/login.jsp"/>
<property name="defaultTargetUrl" value="/login.do"/>
<property name="filterProcessesUrl" value="/j_acegi_security_check"/>
<property name="rememberMeServices" ref="rememberMeServices"/>
</bean>

<bean id="securityContextHolderAwareRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter"/>

<!-- Cookie登录的过滤器 -->
<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,AUTH_ANONYMOUS"/>
</bean>

<!-- 异常处理过滤器 -->
<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="/jsp/login.jsp"/>
<property name="forceHttps" value="false"/>
</bean>
</property>
<property name="accessDeniedHandler">
<bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
<property name="errorPage" value="/jsp/noGrant.jsp"/>
</bean>
</property>
</bean>

<!-- 过滤拦截器 -->
<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="accessDecisionManager"><ref local="httpRequestAccessDecisionManager"/></property>
<property name="objectDefinitionSource">
<value>
PATTERN_TYPE_APACHE_ANT

<!-- 所有用户的权限 -->
/searchUser.do=AUTH_USER,AUTH_ROOT,AUTH_RELEASER,AUTH_AUDITOR
/updateInformation.do=AUTH_USER,AUTH_ROOT,AUTH_RELEASER,AUTH_AUDITOR

<!-- 普通用户的权限 -->
/preOrderAd.do=AUTH_USER
/orderAd.do=AUTH_USER
/myAdList.do=AUTH_USER
/findReOrderAd.do=AUTH_USER
/ReOrder.do=AUTH_USER

<!--==== 超级管理员的权限 ====-->

<!-- 审核员的权限 -->
/noaudit.do=AUTH_AUDITOR,AUTH_ROOT
/allAuditAdNoPass.do=AUTH_AUDITOR,AUTH_ROOT
/auditedAd.do=AUTH_AUDITOR
/audited.do=AUTH_AUDITOR,AUTH_ROOT
/notify.do=AUTH_AUDITOR,AUTH_ROOT
/editAuditedEmail.do=AUTH_AUDITOR,AUTH_ROOT

<!-- 发布员的权限 -->
/unpaymentOrderList.do=AUTH_RELEASER,AUTH_ROOT
/paymentOrderList.do=AUTH_RELEASER,AUTH_ROOT

<!-- 超级管理员的权限 -->
/manageAdpos.do=AUTH_ROOT
/manageUser.do=AUTH_ROOT
/addNewAdpos.do=AUTH_ROOT


</value>
</property>
</bean>

<!-- 请求获取仲裁管理器 -->
<bean id="httpRequestAccessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions"><value>false</value></property>
<property name="decisionVoters">
<list>
<ref bean="roleVoter"/>
</list>
</property>
</bean>

<!-- Cookie服务器 -->
<bean id="rememberMeServices" class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="jdbcDaoImpl"/>
<property name="key" value="changeThis"/>
</bean>

<!-- 认证管理器 -->
<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>

<!-- 进行简单的基于数据库的身份验证 -->
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="jdbcDaoImpl"/>
<property name="userCache">
<bean class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
<property name="cache">
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager">
<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
</property>
<property name="cacheName" value="userCache"/>
</bean>
</property>
</bean>
</property>
</bean>

<!-- 用户名密码匹配验证并返回UserDetails -->
<bean id="jdbcDaoImpl" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource" ref="dataSource"/>
<property name="usersByUsernameQuery">
<value>select username, password, enabled from user where username = ? and enabled = 1</value>
</property>
<property name="authoritiesByUsernameQuery">
<value>
select u.username, a.authority
from user u, authorities a, user_auth ua
where u.id=ua.user_id and a.id=ua.auth_id and u.username=?
</value>
</property>
</bean>

<!-- 登录日志监听器,只用于记录日志,(可选) -->
<bean id="loggerListener" class="org.acegisecurity.event.authentication.LoggerListener"/>

<!-- 权限投票器,主要用于权限前缀的设定 -->
<bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter">
<property name="rolePrefix" value="AUTH_"/>
</bean>
</beans>

一般在用户登录以后需要用session来记录用户信息以便需要的地方调用,因此你可以在登录成功以后转向LoginAction,在这个Action里面你可以将用户名存在seesion里,然后再mapping到显示登录成功的页面,代码如下:

package com.xps.controller.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.userdetails.UserDetails;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;


public class LoginAction extends Action {


public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {

String username = null;
//从Acegi的Context中获取用户信息
Object obj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

if(obj instanceof UserDetails){
username = ((UserDetails)obj).getUsername();
}else{
username = obj.toString();
}

HttpSession session = request.getSession(true);
session.setAttribute("username", username);
return mapping.findForward("success");
}
}

上面Acegi配置文件红色部分根据自己的需要修改。对于Acegi配置文件的说明,已经有很多朋友讲得比较清楚了,我也就不用多赘述,在下篇文章中蓝色字体是引用一位网友的(它用的Acegi版本是acegi-security-0.8.3.jar,所以有些包可能有不同),如果上面没看懂的可以结合下边的解释理解。注:蓝色字段为引用部分,由于internet上转载太多,未能找到原出处,请见谅

在applicationContext-acegi-security.xml中

FILTER CHAIN

  FilterChainProxy会按顺序来调用这些filter,使这些filter能享用Spring ioc的功能, CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON定义了url比较前先转为小写, PATTERN_TYPE_APACHE_ANT定义了使用Apache ant的匹配模式

基础认证

1) authenticationManager
起到认证管理的作用,它将验证的功能委托给多个Provider,并通过遍历Providers, 以保证获取不同来源的身份认证,若某个Provider能成功确认当前用户的身份,authenticate()方法会返回一个完整的包含用户授权信息的Authentication对象,否则会抛出一个AuthenticationException。
Acegi提供了不同的AuthenticationProvider的实现,如:
DaoAuthenticationProvider 从数据库中读取用户信息验证身份
AnonymousAuthenticationProvider 匿名用户身份认证
RememberMeAuthenticationProvider 已存cookie中的用户信息身份认证
AuthByAdapterProvider 使用容器的适配器验证身份
CasAuthenticationProvider 根据Yale中心认证服务验证身份, 用于实现单点登陆
JaasAuthenticationProvider 从JASS登陆配置中获取用户信息验证身份
RemoteAuthenticationProvider 根据远程服务验证用户身份
RunAsImplAuthenticationProvider 对身份已被管理器替换的用户进行验证
X509AuthenticationProvider 从X509认证中获取用户信息验证身份
TestingAuthenticationProvider 单元测试时使用

每个认证者会对自己指定的证明信息进行认证,如DaoAuthenticationProvider仅对UsernamePasswordAuthenticationToken这个证明信息进行认证。


2) daoAuthenticationProvider
进行简单的基于数据库的身份验证。DaoAuthenticationProvider获取数据库中的账号密码并进行匹配,若成功则在通过用户身份的同时返回一个包含授权信息的Authentication对象,否则身份验证失败,抛出一个AuthenticatiionException。


3) passwordEncoder
使用加密器对用户输入的明文进行加密。Acegi提供了三种加密器:
PlaintextPasswordEncoder—默认,不加密,返回明文.
ShaPasswordEncoder—哈希算法(SHA)加密
Md5PasswordEncoder—消息摘要(MD5)加密

4) jdbcDaoImpl
用于在数据中获取用户信息。 acegi提供了用户及授权的表结构,但是您也可以自己来实现。通过usersByUsernameQuery这个SQL得到你的(用户ID,密码,状态信息);通过authoritiesByUsernameQuery这个SQL得到你的(用户ID,授权信息)

5) userCache & resourceCache
缓存用户和资源相对应的权限信息。每当请求一个受保护资源时,daoAuthenticationProvider就会被调用以获取用户授权信息。如果每次都从数据库获取的话,那代价很高,对于不常改变的用户和资源信息来说,最好是把相关授权信息缓存起来。(详见 2.6.3 资源权限定义扩展 )
userCache提供了两种实现: NullUserCache和EhCacheBasedUserCache, NullUserCache实际上就是不进行任何缓存,EhCacheBasedUserCache是使用Ehcache来实现缓功能。

6) basicProcessingFilter
用于处理HTTP头的认证信息,如从Spring远程协议(如Hessian和Burlap)或普通的浏览器如IE,Navigator的HTTP头中获取用户信息,将他们转交给通过authenticationManager属性装配的认证管理器。如果认证成功,会将一个Authentication对象放到会话中,否则,如果认证失败,会将控制转交给认证入口点(通过authenticationEntryPoint属性装配)

7) basicProcessingFilterEntryPoint
通过向浏览器发送一个HTTP401(未授权)消息,提示用户登录。
处理基于HTTP的授权过程,在当验证过程出现异常后的"去向",通常实现转向、在response里加入error信息等功能。

8) authenticationProcessingFilterEntryPoint
当抛出AccessDeniedException时,将用户重定向到登录界面。属性loginFormUrl配置了一个登录表单的URL,当需要用户登录时,authenticationProcessingFilterEntryPoint会将用户重定向到该URL

HTTP安全请求

1) httpSessionContextIntegrationFilter
每次request前 HttpSessionContextIntegrationFilter从Session中获取Authentication对象,在request完后, 又把Authentication对象保存到Session中供下次request使用,此filter必须其他Acegi filter前使用,使之能跨越多个请求。


2) httpRequestAccessDecisionManager
经过投票机制来决定是否可以访问某一资源(URL或方法)。allowIfAllAbstainDecisions为false时如果有一个或以上的decisionVoters投票通过,则授权通过。可选的决策机制有ConsensusBased和UnanimousBased


3) roleVoter
  必须是以rolePrefix设定的value开头的权限才能进行投票,如AUTH_ , ROLE_

4)exceptionTranslationFilter
异常转换过滤器,主要是处理AccessDeniedException和AuthenticationException,将给每个异常找到合适的"去向"

5) authenticationProcessingFilter
和servlet spec差不多,处理登陆请求.当身份验证成功时,AuthenticationProcessingFilter会在会话中放置一个Authentication对象,并且重定向到登录成功页面
authenticationFailureUrl定义登陆失败时转向的页面
defaultTargetUrl定义登陆成功时转向的页面
filterProcessesUrl定义登陆请求的页面
rememberMeServices用于在验证成功后添加cookie信息

6) filterInvocationInterceptor
在执行转向url前检查objectDefinitionSource中设定的用户权限信息。首先,objectDefinitionSource中定义了访问URL需要的属性信息(这里的属性信息仅仅是标志,告诉accessDecisionManager要用哪些voter来投票)。然后,authenticationManager掉用自己的provider来对用户的认证信息进行校验。最后,有投票者根据用户持有认证和访问url需要的属性,调用自己的voter来投票,决定是否允许访问。


7) filterDefinitionSource
自定义DBFilterInvocationDefinitionSource从数据库和cache中读取保护资源及其需要的访问权限信息

方法调用安全控制

1) methodSecurityInterceptor
在执行方法前进行拦截,检查用户权限信息
2) methodDefinitionSource
自定义MethodDefinitionSource从cache中读取权限

<bean id="methodSecurityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
<property name="objectDefinitionSource" ref="methodDefinitionSource"/>
</bean>
<bean id="methodDefinitionSource" class="org.springside.modules.security.service.acegi.DBMethodDefinitionSource">
<property name="acegiCacheManager" ref="acegiCacheManager"/>
</bean>

3. 最后是登录页面login.jsp
<html>
<head>
<title>用户登录</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body topmargin="0">
<form action="j_acegi_security_check" method="POST">
<table>
<tr><td>用户名:</td><td><input type='text' name='j_username'></td></tr>
<tr><td>密码:</td><td><input type='password' name='j_password'></td></tr>
<tr>
<td><input name="submit" type="submit" value="登录"></td>
<td><input name="reset" type="reset" value="重置"></td>
</tr>
</table>
</form>
</body>
</html>


红色部分必须用Acegi固定的命名,如果要注销,只需要加上<a href="j_acegi_logout">注销</a>,如果还需要将登录信息记录到cookie中,可以用<input type="checkbox" name="_acegi_security_remember_me">Don't ask for my password for two weeks,这样用户就在两周内不用登录也能访问到系统了。

后记,其实Acegi在认证授权方面功能是非常强大的,这无愧于它的复杂配置- -!我只是用它做了一点初级的应用,对于一些高级功能(比如方法级的访问控制等),以后用到了再研究。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值