- 浏览: 9783 次
- 性别:
- 来自: 武汉
最近访客 更多访客>>
最新评论
-
xindianshang:
谢谢分享,很不错的知识~
总结的工作心得~希望对刚工作的coder有帮助 -
thebye85:
改为String chara = new String(byt ...
JAVA 区位码于汉字的互换 -
thebye85:
区位码转换成汉字,好像不行
JAVA 区位码于汉字的互换
目 录
前 言 3
第一章 环境搭建 4
1.1 环境配置 4
第二章 相关的JAVA类 13
2.1 接口TESTAOPINTERFACE 13
2.2 接口实现类TESTAOPINTERFACEIMPL 14
2.3 接口实现类TESTAOPINTERFACE的ADVICE类 14
2.4 DATASOURCEMETHODDEFINITIONSOURCEEDITOR类 17
第三章 测试页面 18
3.1 登录页面 18
3.2 登录后进入INDEX.JSP 19
3.3 调用业务方法的页面 20
前 言
我在公司产品升级中引入struts+spring+hibernate3的框架时,那时还没有引入Acegi安全系统框架,直到在最近的开发中发现了这样一个问题:Web层可以很容易地得到登录用户的userId,但业务逻辑层的接口如何获得UserId来进行方法调用的安全控制?一开始的接口设计中,有不少方法的第一个参数都是一个String类型的userId变量,但总不能每个方法都带一个userId做为参数吧?那当然不能。
那么如何将userId传给业务逻辑层的接口,或者传给业务逻辑接口的前置通知类(MethodBeforeAdvice)?在Advice类获得userId后,就可以根据userId控制方法的执行权限。既然不能使用业务方法中带userId参数的笨拙办法,那么有什么其他的方法可以传递userId呢?我最初考虑由前端传递一个Session,后来又考虑开发一个Listener,但我还没来得及去尝试这些方法的时候,偶尔翻了翻买来的《Spring in Action中文版》中介绍的acegi安全系统,觉得这个安全框架既然能够实现方法级的权限控制,就一定可以解决我现在遇到的问题:我要在业务接口的Advice类中获得登录用户的身份!《Spring in Action中文版》介绍的内容比较基础,没有直接给出一个现成的范例,于是到网上狂找一气,在google上用acegi关键字搜索了10页,找到几篇好文章,最有帮助的应该是网名为“一餐三碗”的先生的一个示例,不过在看他的例子是用acegi0.8.3做的,而我那时手头已经下载了acegi1.0,于是结合acegi1.0下载包和“一餐三碗”老兄的Demo,花了一整天的时间,终于调通了一个基于acegi1.0的例子,这个例子配置了MD5加密,后台的口令验证是根据登录表单的口令进行MD5加密后,与数据库用户表中此用户的加密口令进行比较。
我想大家一定很着急先知道我们如何实现在业务方法或Advice类中获得用户身份,所以我先介绍这个例子的具体实现,然后再介绍一下Acegi。
第一章 环境搭建
1.1 环境配置
1、下载acegi:
http://nchc.dl.sourceforge.net/sourceforge/acegisecurity/acegi-security-1.0.0-src.zip
http://prdownloads.sourceforge.net/acegisecurity/acegi-security-1.0.0.zip?download
下载后需要将zip文件中的jar包解压出来,放到Web应用可以找到的lib目录下。
2、配置MySQL库
当然也可以使用其他数据库,这里以MySql库为例,MySql安装好以后需要建几个表,大家可以从这个地址下载“一餐三碗”先生(从名字看应该是男士)提供的例子,里面有数据库表的sql文件: http://www.blogjava.net/Files/youlq/Acegi.zip。
3、配置 web.xml
〈?xml version="1.0" encoding="GB2312"?〉
〈!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〉acegi10〈/display-name〉
〈context-param〉
〈param-name〉contextConfigLocation〈/param-name〉
〈param-value〉/WEB-INF/classes/applicationContext-acegi-security.xml
/WEB-INF/classes/applicationContext.xml
〈/param-value〉
〈/context-param〉
〈context-param〉
〈param-name〉log4jConfigLocation〈/param-name〉
〈param-value〉/WEB-INF/classes/log4j.properties〈/param-value〉
〈/context-param〉
〈servlet〉
〈servlet-name〉SpringContextServlet〈/servlet-name〉
〈servlet-class〉
org.springframework.web.context.ContextLoaderServlet
〈/servlet-class〉
〈load-on-startup〉1〈/load-on-startup〉
〈/servlet〉
〈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〉
〈listener〉
〈listener-class〉org.springframework.web.util.Log4jConfigListener〈/listener-class〉
〈/listener〉
〈!--
The HttpSessionEventPublisher will publish
HttpSessionCreatedEvent and HttpSessionDestroyedEvent
to the WebApplicationContext
--〉
〈listener〉
〈listener-class〉org.acegisecurity.ui.session.HttpSessionEventPublisher〈/listener-class〉
〈/listener〉
〈/web-app〉
4、WEB-INF\classes下的applicationContext.xml文件:
〈?xml version="1.0" encoding="UTF-8"?〉
〈!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"〉
〈beans〉
〈bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"〉
〈property name="driverClass"〉
〈value〉com.mysql.jdbc.Driver〈/value〉
〈/property〉
〈property name="jdbcUrl"〉
〈value〉jdbc:mysql://localhost:3308/acegidb〈/value〉
〈/property〉
〈property name="user"〉
〈value〉root〈/value〉
〈/property〉
〈property name="password"〉
〈value〉mysql〈/value〉
〈/property〉
〈property name="initialPoolSize"〉
〈value〉10〈/value〉
〈/property〉
〈property name="minPoolSize"〉
〈value〉10〈/value〉
〈/property〉
〈property name="maxPoolSize"〉
〈value〉50〈/value〉
〈/property〉
〈property name="checkoutTimeout"〉
〈value〉50000〈/value〉
〈/property〉
〈property name="maxIdleTime"〉
〈value〉1800〈/value〉
〈/property〉
〈property name="idleConnectionTestPeriod"〉
〈value〉3000〈/value〉
〈/property〉
〈property name="acquireIncrement"〉
〈value〉5〈/value〉
〈/property〉
〈/bean〉
〈bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"〉
〈property name="dataSource"〉
〈ref local="dataSource"/〉
〈/property〉
〈/bean〉
〈bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"〉
〈property name="dataSource"〉
〈ref bean="dataSource"/〉
〈/property〉
〈/bean〉
〈bean id="businessAccessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased"〉
〈property name="allowIfAllAbstainDecisions"〉〈value〉false〈/value〉〈/property〉
〈property name="decisionVoters"〉
〈list〉
〈ref bean="roleVoter"/〉
〈/list〉
〈/property〉
〈/bean〉
〈bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer"〉
〈property name="customEditors"〉
〈map〉
〈entry key="org.acegisecurity.intercept.method.MethodDefinitionSource"〉
〈bean class="com.mysoft.pmi.security.DataSourceMethodDefinitionSourceEditor"〉
〈property name="jdbcTemplate"〉 〈ref bean="jdbcTemplate"/〉 〈/property〉
〈/bean〉
〈/entry〉
〈/map〉
〈/property〉
〈/bean〉
〈bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"〉
〈property name="transactionManager"〉〈ref bean="transactionManager"/〉〈/property〉
〈property name="transactionAttributeSource"〉
〈value〉
com.mysoft.pmi.testcase.aop.TestAOPInterfaceImpl.*=PROPAGATION_REQUIRED
〈/value〉
〈/property〉
〈/bean〉
〈bean id="contactManagerTarget" class="com.mysoft.pmi.testcase.aop.TestAOPInterfaceImpl" /〉
〈bean id="contactManagerSecurity" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor"〉
〈property name="authenticationManager"〉〈ref bean="authenticationManager"/〉〈/property〉
〈property name="accessDecisionManager"〉〈ref bean="businessAccessDecisionManager"/〉〈/property〉
〈property name="objectDefinitionSource"〉
〈value〉
select authority,PROTECTED_RES from authorities where AUTH_TYPE='FUNCTION' and authority like 'AUTH_FUNC_ContactManager%'
〈/value〉
〈/property〉
〈/bean〉
〈bean id="contactManager" class="org.springframework.aop.framework.ProxyFactoryBean"〉
〈property name="proxyInterfaces"〉
〈value〉com.mysoft.pmi.testcase.aop.TestAOPInterface〈/value〉
〈/property〉
〈property name="interceptorNames"〉
〈list〉
〈idref local="transactionInterceptor"/〉
〈idref local="contactManagerSecurity"/〉
〈idref local="contactManagerTarget"/〉
〈/list〉
〈/property〉
〈/bean〉
〈/beans〉
说明:请根据自己机器的配置更改数据库连接地址、端口、数据库名、口令配置参数。com.mysoft.pmi.testcase.aop.TestAOPInterface接口是一个业务逻辑层接口,本例com.mysoft.pmi.testcase.aop.TestAOPInterfaceImpl是此接口的实现类,提供了两个非常简单的接口方法:test和testAAA。
另外注意使用“一餐三碗”的数据库导入表结构和数据后,需要修改authorities表PROTECTED_RES的值,因为本例使用的接口为com.mysoft.pmi.testcase.aop.TestAOPInterface,如果此字段使用了不存在的接口和方法,在应用服务器启动时会报错,所以请将此字段的内容改为:com.mysoft.pmi.testcase.aop.TestAOPInterface.test或com.mysoft.pmi.testcase.aop.TestAOPInterface.testAAA,如果大家有兴趣可以在此接口下多加几个方法。
5、WEB-INF\classes下的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〉
〈bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy"〉
〈property name="filterInvocationDefinitionSource"〉
〈value〉
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
〈/value〉
〈/property〉
〈/bean〉
〈bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/〉
〈bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter"〉
〈property name="authenticationManager" ref="authenticationManager"/〉
〈property name="authenticationFailureUrl" value="/index_acegi.jsp?login_error=1"/〉
〈property name="defaultTargetUrl" value="/testacegi.jsp"/〉
〈property name="filterProcessesUrl" value="/j_acegi_security_check"/〉
〈/bean〉
〈bean id="securityContextHolderAwareRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter"/〉
〈bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter"〉
〈property name="authenticationEntryPoint"〉
〈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〉
〈bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor"〉
〈property name="authenticationManager" ref="authenticationManager"/〉
〈property name="accessDecisionManager"〉
〈bean class="org.acegisecurity.vote.AffirmativeBased"〉
〈property name="allowIfAllAbstainDecisions" value="false"/〉
〈property name="decisionVoters"〉
〈list〉
〈ref bean="roleVoter"/〉
〈bean class="org.acegisecurity.vote.AuthenticatedVoter"/〉
〈/list〉
〈/property〉
〈/bean〉
〈/property〉
〈property name="objectDefinitionSource"〉
〈value〉
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/secure/extreme/**=AUTH_USER
/secure/**=IS_AUTHENTICATED_REMEMBERED
/**=IS_AUTHENTICATED_ANONYMOUSLY
〈/value〉
〈/property〉
〈/bean〉
〈bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager"〉
〈property name="providers"〉
〈list〉
〈ref local="daoAuthenticationProvider"/〉
〈ref local="anonymousAuthenticationProvider"/〉
〈/list〉
〈/property〉
〈/bean〉
〈bean id="anonymousProcessingFilter" class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter"〉
〈property name="key"〉〈value〉foobar〈/value〉〈/property〉
〈property name="userAttribute"〉〈value〉anonymousUser,AUTH_ANONYMOUS〈/value〉〈/property〉
〈/bean〉
〈bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"〉
〈property name="userDetailsService" ref="jdbcDaoImpl"/〉
〈property name="userCache"〉〈ref local="userCache"/〉〈/property〉
〈!-- if u want encode your password with md5,use property passwordEncoder,otherwise delete row below--〉
〈!--〈property name="passwordEncoder" ref="passwordEncoder"/〉--〉
〈/bean〉
〈bean id="passwordEncoder" class="org.acegisecurity.providers.encoding.Md5PasswordEncoder"/〉
〈bean id="loggerListener" class="org.acegisecurity.event.authentication.LoggerListener"/〉
〈!-- add --〉
〈bean id="jdbcDaoImpl" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl"〉
〈property name="dataSource"〉〈ref bean= "dataSource"/〉〈/property〉
〈property name="usersByUsernameQuery"〉
〈!-- 〈value〉SELECT USERID, USERPASSWORD,IS_ENABLED FROM LTPMIUSER WHERE USERID=?〈/value〉 --〉
〈value〉SELECT USERNAME, PASSWORD,ENABLED FROM USERINFO WHERE USERNAME=?〈/value〉
〈/property〉
〈property name="authoritiesByUsernameQuery"〉
〈!--
〈value〉
SELECT userid,authority FROM ltpmiuser u, authorities a, user_auth ua
WHERE u.object_id=ua.user_oid
and a.auth_id=ua.auth_id
and u.userid = ?
〈/value〉
--〉
〈value〉
SELECT username,authority FROM userinfo u, authorities a, user_auth ua
WHERE u.user_id=ua.user_id
and a.auth_id=ua.auth_id
and u.username = ?
〈/value〉
〈/property〉
〈/bean〉
〈bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter"〉
〈property name="rolePrefix"〉〈value〉AUTH_〈/value〉〈/property〉
〈/bean〉
〈bean id="anonymousAuthenticationProvider" class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider"〉
〈property name="key"〉〈value〉foobar〈/value〉〈/property〉
〈/bean〉
〈bean id="userCacheBackend" class="org.springframework.cache.ehcache.EhCacheFactoryBean"〉
〈property name="cacheManager"〉 〈ref local="cacheManager"/〉 〈/property〉
〈property name="cacheName"〉 〈value〉userCache〈/value〉 〈/property〉
〈/bean〉
〈bean id="userCache" class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache"〉
〈property name="cache"〉〈ref local="userCacheBackend"/〉〈/property〉
〈/bean〉
〈bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/〉
〈/beans〉
说明:如果你想使用md5口令验证,请去掉〈!--〈property name="passwordEncoder" ref="passwordEncoder"/〉--〉的注释。
6、log4j.properties
放在WEB-INF\classes下:
log4j.rootLogger=info,CONSOLE
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern= %4p [%t] (%F:%L) - %m%n
7、配置业务方法的前置通知(Advice)
在业务方法的前置通知类编写获取Web层用户身份的代码,在业务方法的后置通知类编写操作日志的方法调用,在操作日志方法中可通过acegi提供的API来获取当前操作者的Authenticaton。实际上acegi提供了一个非常松散的集成机制,如果你仅需要使用acegi获得Web层的身份,只需要在Advice类中引入acegi的方法,即使我们以后不使用acegi,也使代码的修改非常有限。
业务接口:TestAOPInterface。
下面是为TestAOPInterface的Advice配置的xml文件aoptest.xml。 文件的类路径为:com.mysoft.pmi.resource。文件内容:
〈?xml version="1.0" encoding="UTF-8"?〉
〈!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"〉
〈beans〉
〈bean id="interceptTarget" class="com.mysoft.pmi.testcase.aop.TestAOPInterfaceImpl" singleton="false"/〉
〈bean id="welcomeAdvice" class="com.mysoft.pmi.testcase.aop.WelcomeAdvice"/〉
〈bean id="aopProxyFactoryBean"
class="org.springframework.aop.framework.ProxyFactoryBean"〉
〈property name="proxyInterfaces"〉
〈list〉
〈value〉com.mysoft.pmi.testcase.aop.TestAOPInterface〈/value〉
〈/list〉
〈/property〉
〈property name="interceptorNames"〉
〈list〉
〈value〉welcomeAdvice〈/value〉
〈/list〉
〈/property〉
〈property name="target"〉〈ref bean="interceptTarget"/〉
〈/property〉
〈/bean〉
〈/beans〉
第二章 相关的Java类
2.1 接口TestAOPInterface
此接口的角色为业务逻辑层接口。
package com.mysoft.pmi.testcase.aop;
public interface TestAOPInterface
{
public void test(String arg0,String arg1);
public void testAAA(String arg0,String arg1);
}
2.2 接口实现类TestAOPInterfaceImpl
package com.mysoft.pmi.testcase.aop;
public class TestAOPInterfaceImpl implements TestAOPInterface
{
public void test(String arg0, String arg1) {
System.out.println(arg0+"/"+arg1);
}
public void testAAA(String arg0, String arg1) {
System.out.println(arg0+":"+arg1);
}
}
2.3 接口实现类TestAOPInterface的Advice类
package com.mysoft.pmi.testcase.aop;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.Authentication;
//import org.acegisecurity.vote.RoleVoter;
//import org.apache.log4j.Logger;
//import org.acegisecurity.providers.ProviderManager;
//import org.acegisecurity.vote.AffirmativeBased;
//import org.acegisecurity.ui.session.HttpSessionEventPublisher;
//import org.acegisecurity.providers.anonymous.AnonymousProcessingFilter;
//import org.acegisecurity.providers.dao.DaoAuthenticationProvider;
//import org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider;
//import org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache;
//import org.acegisecurity.userdetails.jdbc.JdbcDaoImpl;
//import org.acegisecurity.userdetails.jdbc.JdbcDaoImpl;
//import org.acegisecurity.context.SecurityContextImpl;
//import org.acegisecurity.vote.AffirmativeBased;
//import org.acegisecurity.userdetails.jdbc.JdbcDaoImpl;
//import org.springframework.jdbc.datasource.DataSourceTransactionManager;
//import org.springframework.jdbc.core.JdbcTemplate;
//import org.springframework.beans.factory.config.CustomEditorConfigurer;
//import org.acegisecurity.intercept.method.MethodDefinitionSource;
//import org.springframework.transaction.interceptor.TransactionInterceptor;
//import org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor;
//import org.acegisecurity.providers.*;
public class WelcomeAdvice implements MethodBeforeAdvice
{
public void before(Method method, Object[] args, Object target) throws Throwable
{
System.out.println("hihihihihi,启动MethodBeforeAdvice ");
String userName;
SecurityContext ctx = SecurityContextHolder.getContext();
if(ctx!=null)
{
System.out.println("ctx is not null");
Authentication auth = ctx.getAuthentication();
if(auth!=null)
{
System.out.println("auth is not null!");
Object principal=auth.getPrincipal();
if(principal instanceof UserDetails)
{
//输出用户名
System.out.println( "UserName is "+((UserDetails)principal).getUsername());
//密码获得
System.out.println( "UserPassword is "+((UserDetails)principal).getPassword());
}else{
System.out.println("pricipal.toString()=="+principal.toString());
}
}
else
{
System.out.println("auth is null!");
}
}
else
{
System.out.println("ctx is null");
}
userName = SecurityContextHolder.getContext().getAuthentication().toString();
System.out.println("UserName is:");
System.out.println(userName);
System.out.println("++++++++++++++++++");
//输出被拦截的接口的名称
System.out.println(target.getClass().getName());
System.out.println(method.getName());
for(int i=0;i〈args.length;i++)
{
System.out.println("HI:"+(String)args[i]);
//输出传入的字符串参数的值
System.out.println( args[i] .getClass().toString());
}
//
//Logger song = Logger.getLogger(target.getClass()); //得到目标类的logger
//System.out.println("println:"+target.getClass().getName()+" did "+method.getName());
}
}
说明:当接口TestAOPInterface的方法被调用前,WelcomeAdvice类作为此接口的前置通知,其before方法将先于TestAOPInterface的方法(在aoptest.xml中配置了WelcomeAdvice拦截TestAOPInterface的所有方法),在before方法里实现了取得当前会话的Authentication即当前用户的身份。既然取得了用户的登录Id,就可以利用此Id做更细粒度的权限控制及添加系统操作日志(日志需要记录哪个用户调用了此方法),由于acegi的这种授权体系支持到方法级别,可以在Advice中进行更深入的开发-规则引擎的开发。例如在ERP系统中,A库管员和B库管员都可以操作入库单录入功能,但A库管员是原料库库管员,B是成品库库管员,如果成品库库管员录入了原料库物品,则提交入库单操作失败,这样的需求,只提供方法及的授权还不够,可通过开发规则引擎来支持数据级的授权。实现方式可将限制参数于传入的方法实际参数进行比较,如符合规则则继续执行。
如果大家想在业务方法里而不是advce类里获得userId,最好是参照上述代码,单独设计一个类封装acegi的调用,以免在业务方法中直接引入acegi的API造成对acegi的过度依赖。
2.4 DataSourceMethodDefinitionSourceEditor类
package com.mysoft.pmi.security;
import org.acegisecurity.intercept.method.MethodDefinitionMap;
import org.acegisecurity.ConfigAttributeEditor;
import org.acegisecurity.ConfigAttributeDefinition;
import java.util.List;
import java.util.Iterator;
import java.util.Map;
import java.beans.PropertyEditorSupport;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* 〈br〉Title:
* 〈br〉Description:
* 〈br〉Copyright: Copyright.com (c) 2003
* 〈br〉Company:
* History:
* create 2005-11-7 19:02:16
*
* @author youlq
* @version 1.0
*/
public class DataSourceMethodDefinitionSourceEditor extends PropertyEditorSupport{
protected JdbcTemplate jdbcTemplate;
//~ Methods ================================================================
public JdbcTemplate getJdbcTemplate(){
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate){
this.jdbcTemplate=jdbcTemplate;
}
public void setAsText(String s) throws IllegalArgumentException{
MethodDefinitionMap source=new MethodDefinitionMap();
List rs;
try{
rs=jdbcTemplate.queryForList(s);
} catch(Exception e){
setValue(source);
e.printStackTrace();
return;
}
if((rs==null)||rs.size()==0){
// Leave value in property editor null
} else{
// Now we have properties, process each one individually
ConfigAttributeEditor configAttribEd=new ConfigAttributeEditor();
for(Iterator iter=rs.iterator();iter.hasNext();){
Map row=(Map)iter.next();
String authority=(String)row.get("AUTHORITY");
String resource=(String)row.get("PROTECTED_RES");
if((null==authority)||(resource==null)){
continue;
}
// Convert value to series of security configuration attributes
configAttribEd.setAsText(authority);
ConfigAttributeDefinition attr=(ConfigAttributeDefinition)configAttribEd .getValue();
// Register name and attribute
source.addSecureMethod(resource, attr);
}
}
setValue(source);
}
}
第三章 测试页面
3.1 登录页面
acegilogin.jsp(将此文件放在web应用的根目录下):
〈%@ page contentType="text/html;charset=GBK" language="java" %〉
〈%@ page import="org.acegisecurity.ui.AbstractProcessingFilter" %〉
〈%@ page import="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter" %〉
〈%@ page import="org.acegisecurity.AuthenticationException" %〉
〈html〉
〈head〉
〈title〉Login〈/title〉
〈/head〉
〈body〉
〈h1〉Login〈/h1〉
〈%
String login_error=request.getParameter("login_error");
if(login_error!=null){
%〉
〈font color="red"〉
登录失败。原因: 〈%= ((AuthenticationException) session.getAttribute(AbstractProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY)).getMessage() %〉
〈/font〉
〈%
}
%〉
〈form action="j_acegi_security_check" method="POST"〉
〈table〉
〈tr〉〈td〉User:〈/td〉〈td〉〈input type='text' name='j_username'〉〈/td〉〈/tr〉
〈tr〉〈td〉Password:〈/td〉〈td〉〈input type='password' name='j_password'〉〈/td〉〈/tr〉
〈tr〉〈td colspan='2'〉〈input name="submit" type="submit"〉〈/td〉〈/tr〉
〈tr〉〈td colspan='2'〉〈input name="reset" type="reset"〉〈/td〉〈/tr〉
〈/table〉
〈/form〉
〈/body〉
〈/html〉
说明:“一餐三碗”提供的数据库表中的root用户口令为root。
3.2 登录后进入index.jsp
在web应用根目录下建立一个secure目录,此目录下件一个index.jsp文件:
〈html〉
〈body〉
〈h1〉Secure Page〈/h1〉
This is a protected page. You can get to me if you've been remembered,
or if you've authenticated this session.
〈p〉〈a href="../testacegi.jsp"〉Home〈/a〉
〈p〉〈a href="../j_acegi_logout"〉Logout〈/a〉
〈/body〉
〈/html〉
3.3 调用业务方法的页面
在web应用的根目录下建立一个testacegi.jsp文件:
〈%@ page import="org.acegisecurity.context.SecurityContextHolder" %〉
〈%@ page import="org.acegisecurity.Authentication" %〉
〈%@ page import="org.acegisecurity.ui.AccessDeniedHandlerImpl" %〉
〈%@ page import="org.springframework.aop.framework.ProxyFactoryBean"%〉
〈%@ page import="org.springframework.context.ApplicationContext"%〉
〈%@ page import="org.springframework.context.support.ClassPathXmlApplicationContext"%〉
〈%@ page import="com.mysoft.pmi.testcase.aop.TestAOPInterface"%〉
〈%
System.out.println("_______"+SecurityContextHolder.getContext().getAuthentication().toString());
ApplicationContext context1 = new ClassPathXmlApplicationContext("com/mysoft/pmi/resource/aoptest.xml");
TestAOPInterface bean1 = (TestAOPInterface)context1.getBean("aopProxyFactoryBean");
bean1.test("aa1a","ccc");
bean1.testAAA("dd1d","dwdwsd");
%〉
〈% Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) { %〉
Authentication object as a String: 〈%= auth.toString() %〉〈BR〉〈BR〉
〈% } %〉
说明:本文的例子是在Bea Weblogic8.1下测试通过。
JSP测试流程:在登录页面输入正确的用户名和口令(如果口令错误,控制台会报bad credentials的错误),登录成功后,浏览器打开了secure/index.jsp页面,点此页面的home连接,此连接调用了testacegi.jsp,此JSP调用了TestAOPInterface的接口方法,可以看到后台显示了当前的userId(见WelcomeAdvice.java)。如果需要将口令进行md5加密,然后与数据库中加密的口令进行比较,可参考applicationContext-acegi-security.xml。
前 言 3
第一章 环境搭建 4
1.1 环境配置 4
第二章 相关的JAVA类 13
2.1 接口TESTAOPINTERFACE 13
2.2 接口实现类TESTAOPINTERFACEIMPL 14
2.3 接口实现类TESTAOPINTERFACE的ADVICE类 14
2.4 DATASOURCEMETHODDEFINITIONSOURCEEDITOR类 17
第三章 测试页面 18
3.1 登录页面 18
3.2 登录后进入INDEX.JSP 19
3.3 调用业务方法的页面 20
前 言
我在公司产品升级中引入struts+spring+hibernate3的框架时,那时还没有引入Acegi安全系统框架,直到在最近的开发中发现了这样一个问题:Web层可以很容易地得到登录用户的userId,但业务逻辑层的接口如何获得UserId来进行方法调用的安全控制?一开始的接口设计中,有不少方法的第一个参数都是一个String类型的userId变量,但总不能每个方法都带一个userId做为参数吧?那当然不能。
那么如何将userId传给业务逻辑层的接口,或者传给业务逻辑接口的前置通知类(MethodBeforeAdvice)?在Advice类获得userId后,就可以根据userId控制方法的执行权限。既然不能使用业务方法中带userId参数的笨拙办法,那么有什么其他的方法可以传递userId呢?我最初考虑由前端传递一个Session,后来又考虑开发一个Listener,但我还没来得及去尝试这些方法的时候,偶尔翻了翻买来的《Spring in Action中文版》中介绍的acegi安全系统,觉得这个安全框架既然能够实现方法级的权限控制,就一定可以解决我现在遇到的问题:我要在业务接口的Advice类中获得登录用户的身份!《Spring in Action中文版》介绍的内容比较基础,没有直接给出一个现成的范例,于是到网上狂找一气,在google上用acegi关键字搜索了10页,找到几篇好文章,最有帮助的应该是网名为“一餐三碗”的先生的一个示例,不过在看他的例子是用acegi0.8.3做的,而我那时手头已经下载了acegi1.0,于是结合acegi1.0下载包和“一餐三碗”老兄的Demo,花了一整天的时间,终于调通了一个基于acegi1.0的例子,这个例子配置了MD5加密,后台的口令验证是根据登录表单的口令进行MD5加密后,与数据库用户表中此用户的加密口令进行比较。
我想大家一定很着急先知道我们如何实现在业务方法或Advice类中获得用户身份,所以我先介绍这个例子的具体实现,然后再介绍一下Acegi。
第一章 环境搭建
1.1 环境配置
1、下载acegi:
http://nchc.dl.sourceforge.net/sourceforge/acegisecurity/acegi-security-1.0.0-src.zip
http://prdownloads.sourceforge.net/acegisecurity/acegi-security-1.0.0.zip?download
下载后需要将zip文件中的jar包解压出来,放到Web应用可以找到的lib目录下。
2、配置MySQL库
当然也可以使用其他数据库,这里以MySql库为例,MySql安装好以后需要建几个表,大家可以从这个地址下载“一餐三碗”先生(从名字看应该是男士)提供的例子,里面有数据库表的sql文件: http://www.blogjava.net/Files/youlq/Acegi.zip。
3、配置 web.xml
〈?xml version="1.0" encoding="GB2312"?〉
〈!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〉acegi10〈/display-name〉
〈context-param〉
〈param-name〉contextConfigLocation〈/param-name〉
〈param-value〉/WEB-INF/classes/applicationContext-acegi-security.xml
/WEB-INF/classes/applicationContext.xml
〈/param-value〉
〈/context-param〉
〈context-param〉
〈param-name〉log4jConfigLocation〈/param-name〉
〈param-value〉/WEB-INF/classes/log4j.properties〈/param-value〉
〈/context-param〉
〈servlet〉
〈servlet-name〉SpringContextServlet〈/servlet-name〉
〈servlet-class〉
org.springframework.web.context.ContextLoaderServlet
〈/servlet-class〉
〈load-on-startup〉1〈/load-on-startup〉
〈/servlet〉
〈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〉
〈listener〉
〈listener-class〉org.springframework.web.util.Log4jConfigListener〈/listener-class〉
〈/listener〉
〈!--
The HttpSessionEventPublisher will publish
HttpSessionCreatedEvent and HttpSessionDestroyedEvent
to the WebApplicationContext
--〉
〈listener〉
〈listener-class〉org.acegisecurity.ui.session.HttpSessionEventPublisher〈/listener-class〉
〈/listener〉
〈/web-app〉
4、WEB-INF\classes下的applicationContext.xml文件:
〈?xml version="1.0" encoding="UTF-8"?〉
〈!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"〉
〈beans〉
〈bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"〉
〈property name="driverClass"〉
〈value〉com.mysql.jdbc.Driver〈/value〉
〈/property〉
〈property name="jdbcUrl"〉
〈value〉jdbc:mysql://localhost:3308/acegidb〈/value〉
〈/property〉
〈property name="user"〉
〈value〉root〈/value〉
〈/property〉
〈property name="password"〉
〈value〉mysql〈/value〉
〈/property〉
〈property name="initialPoolSize"〉
〈value〉10〈/value〉
〈/property〉
〈property name="minPoolSize"〉
〈value〉10〈/value〉
〈/property〉
〈property name="maxPoolSize"〉
〈value〉50〈/value〉
〈/property〉
〈property name="checkoutTimeout"〉
〈value〉50000〈/value〉
〈/property〉
〈property name="maxIdleTime"〉
〈value〉1800〈/value〉
〈/property〉
〈property name="idleConnectionTestPeriod"〉
〈value〉3000〈/value〉
〈/property〉
〈property name="acquireIncrement"〉
〈value〉5〈/value〉
〈/property〉
〈/bean〉
〈bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"〉
〈property name="dataSource"〉
〈ref local="dataSource"/〉
〈/property〉
〈/bean〉
〈bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"〉
〈property name="dataSource"〉
〈ref bean="dataSource"/〉
〈/property〉
〈/bean〉
〈bean id="businessAccessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased"〉
〈property name="allowIfAllAbstainDecisions"〉〈value〉false〈/value〉〈/property〉
〈property name="decisionVoters"〉
〈list〉
〈ref bean="roleVoter"/〉
〈/list〉
〈/property〉
〈/bean〉
〈bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer"〉
〈property name="customEditors"〉
〈map〉
〈entry key="org.acegisecurity.intercept.method.MethodDefinitionSource"〉
〈bean class="com.mysoft.pmi.security.DataSourceMethodDefinitionSourceEditor"〉
〈property name="jdbcTemplate"〉 〈ref bean="jdbcTemplate"/〉 〈/property〉
〈/bean〉
〈/entry〉
〈/map〉
〈/property〉
〈/bean〉
〈bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"〉
〈property name="transactionManager"〉〈ref bean="transactionManager"/〉〈/property〉
〈property name="transactionAttributeSource"〉
〈value〉
com.mysoft.pmi.testcase.aop.TestAOPInterfaceImpl.*=PROPAGATION_REQUIRED
〈/value〉
〈/property〉
〈/bean〉
〈bean id="contactManagerTarget" class="com.mysoft.pmi.testcase.aop.TestAOPInterfaceImpl" /〉
〈bean id="contactManagerSecurity" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor"〉
〈property name="authenticationManager"〉〈ref bean="authenticationManager"/〉〈/property〉
〈property name="accessDecisionManager"〉〈ref bean="businessAccessDecisionManager"/〉〈/property〉
〈property name="objectDefinitionSource"〉
〈value〉
select authority,PROTECTED_RES from authorities where AUTH_TYPE='FUNCTION' and authority like 'AUTH_FUNC_ContactManager%'
〈/value〉
〈/property〉
〈/bean〉
〈bean id="contactManager" class="org.springframework.aop.framework.ProxyFactoryBean"〉
〈property name="proxyInterfaces"〉
〈value〉com.mysoft.pmi.testcase.aop.TestAOPInterface〈/value〉
〈/property〉
〈property name="interceptorNames"〉
〈list〉
〈idref local="transactionInterceptor"/〉
〈idref local="contactManagerSecurity"/〉
〈idref local="contactManagerTarget"/〉
〈/list〉
〈/property〉
〈/bean〉
〈/beans〉
说明:请根据自己机器的配置更改数据库连接地址、端口、数据库名、口令配置参数。com.mysoft.pmi.testcase.aop.TestAOPInterface接口是一个业务逻辑层接口,本例com.mysoft.pmi.testcase.aop.TestAOPInterfaceImpl是此接口的实现类,提供了两个非常简单的接口方法:test和testAAA。
另外注意使用“一餐三碗”的数据库导入表结构和数据后,需要修改authorities表PROTECTED_RES的值,因为本例使用的接口为com.mysoft.pmi.testcase.aop.TestAOPInterface,如果此字段使用了不存在的接口和方法,在应用服务器启动时会报错,所以请将此字段的内容改为:com.mysoft.pmi.testcase.aop.TestAOPInterface.test或com.mysoft.pmi.testcase.aop.TestAOPInterface.testAAA,如果大家有兴趣可以在此接口下多加几个方法。
5、WEB-INF\classes下的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〉
〈bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy"〉
〈property name="filterInvocationDefinitionSource"〉
〈value〉
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
〈/value〉
〈/property〉
〈/bean〉
〈bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/〉
〈bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter"〉
〈property name="authenticationManager" ref="authenticationManager"/〉
〈property name="authenticationFailureUrl" value="/index_acegi.jsp?login_error=1"/〉
〈property name="defaultTargetUrl" value="/testacegi.jsp"/〉
〈property name="filterProcessesUrl" value="/j_acegi_security_check"/〉
〈/bean〉
〈bean id="securityContextHolderAwareRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter"/〉
〈bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter"〉
〈property name="authenticationEntryPoint"〉
〈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〉
〈bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor"〉
〈property name="authenticationManager" ref="authenticationManager"/〉
〈property name="accessDecisionManager"〉
〈bean class="org.acegisecurity.vote.AffirmativeBased"〉
〈property name="allowIfAllAbstainDecisions" value="false"/〉
〈property name="decisionVoters"〉
〈list〉
〈ref bean="roleVoter"/〉
〈bean class="org.acegisecurity.vote.AuthenticatedVoter"/〉
〈/list〉
〈/property〉
〈/bean〉
〈/property〉
〈property name="objectDefinitionSource"〉
〈value〉
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/secure/extreme/**=AUTH_USER
/secure/**=IS_AUTHENTICATED_REMEMBERED
/**=IS_AUTHENTICATED_ANONYMOUSLY
〈/value〉
〈/property〉
〈/bean〉
〈bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager"〉
〈property name="providers"〉
〈list〉
〈ref local="daoAuthenticationProvider"/〉
〈ref local="anonymousAuthenticationProvider"/〉
〈/list〉
〈/property〉
〈/bean〉
〈bean id="anonymousProcessingFilter" class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter"〉
〈property name="key"〉〈value〉foobar〈/value〉〈/property〉
〈property name="userAttribute"〉〈value〉anonymousUser,AUTH_ANONYMOUS〈/value〉〈/property〉
〈/bean〉
〈bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"〉
〈property name="userDetailsService" ref="jdbcDaoImpl"/〉
〈property name="userCache"〉〈ref local="userCache"/〉〈/property〉
〈!-- if u want encode your password with md5,use property passwordEncoder,otherwise delete row below--〉
〈!--〈property name="passwordEncoder" ref="passwordEncoder"/〉--〉
〈/bean〉
〈bean id="passwordEncoder" class="org.acegisecurity.providers.encoding.Md5PasswordEncoder"/〉
〈bean id="loggerListener" class="org.acegisecurity.event.authentication.LoggerListener"/〉
〈!-- add --〉
〈bean id="jdbcDaoImpl" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl"〉
〈property name="dataSource"〉〈ref bean= "dataSource"/〉〈/property〉
〈property name="usersByUsernameQuery"〉
〈!-- 〈value〉SELECT USERID, USERPASSWORD,IS_ENABLED FROM LTPMIUSER WHERE USERID=?〈/value〉 --〉
〈value〉SELECT USERNAME, PASSWORD,ENABLED FROM USERINFO WHERE USERNAME=?〈/value〉
〈/property〉
〈property name="authoritiesByUsernameQuery"〉
〈!--
〈value〉
SELECT userid,authority FROM ltpmiuser u, authorities a, user_auth ua
WHERE u.object_id=ua.user_oid
and a.auth_id=ua.auth_id
and u.userid = ?
〈/value〉
--〉
〈value〉
SELECT username,authority FROM userinfo u, authorities a, user_auth ua
WHERE u.user_id=ua.user_id
and a.auth_id=ua.auth_id
and u.username = ?
〈/value〉
〈/property〉
〈/bean〉
〈bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter"〉
〈property name="rolePrefix"〉〈value〉AUTH_〈/value〉〈/property〉
〈/bean〉
〈bean id="anonymousAuthenticationProvider" class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider"〉
〈property name="key"〉〈value〉foobar〈/value〉〈/property〉
〈/bean〉
〈bean id="userCacheBackend" class="org.springframework.cache.ehcache.EhCacheFactoryBean"〉
〈property name="cacheManager"〉 〈ref local="cacheManager"/〉 〈/property〉
〈property name="cacheName"〉 〈value〉userCache〈/value〉 〈/property〉
〈/bean〉
〈bean id="userCache" class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache"〉
〈property name="cache"〉〈ref local="userCacheBackend"/〉〈/property〉
〈/bean〉
〈bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/〉
〈/beans〉
说明:如果你想使用md5口令验证,请去掉〈!--〈property name="passwordEncoder" ref="passwordEncoder"/〉--〉的注释。
6、log4j.properties
放在WEB-INF\classes下:
log4j.rootLogger=info,CONSOLE
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern= %4p [%t] (%F:%L) - %m%n
7、配置业务方法的前置通知(Advice)
在业务方法的前置通知类编写获取Web层用户身份的代码,在业务方法的后置通知类编写操作日志的方法调用,在操作日志方法中可通过acegi提供的API来获取当前操作者的Authenticaton。实际上acegi提供了一个非常松散的集成机制,如果你仅需要使用acegi获得Web层的身份,只需要在Advice类中引入acegi的方法,即使我们以后不使用acegi,也使代码的修改非常有限。
业务接口:TestAOPInterface。
下面是为TestAOPInterface的Advice配置的xml文件aoptest.xml。 文件的类路径为:com.mysoft.pmi.resource。文件内容:
〈?xml version="1.0" encoding="UTF-8"?〉
〈!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"〉
〈beans〉
〈bean id="interceptTarget" class="com.mysoft.pmi.testcase.aop.TestAOPInterfaceImpl" singleton="false"/〉
〈bean id="welcomeAdvice" class="com.mysoft.pmi.testcase.aop.WelcomeAdvice"/〉
〈bean id="aopProxyFactoryBean"
class="org.springframework.aop.framework.ProxyFactoryBean"〉
〈property name="proxyInterfaces"〉
〈list〉
〈value〉com.mysoft.pmi.testcase.aop.TestAOPInterface〈/value〉
〈/list〉
〈/property〉
〈property name="interceptorNames"〉
〈list〉
〈value〉welcomeAdvice〈/value〉
〈/list〉
〈/property〉
〈property name="target"〉〈ref bean="interceptTarget"/〉
〈/property〉
〈/bean〉
〈/beans〉
第二章 相关的Java类
2.1 接口TestAOPInterface
此接口的角色为业务逻辑层接口。
package com.mysoft.pmi.testcase.aop;
public interface TestAOPInterface
{
public void test(String arg0,String arg1);
public void testAAA(String arg0,String arg1);
}
2.2 接口实现类TestAOPInterfaceImpl
package com.mysoft.pmi.testcase.aop;
public class TestAOPInterfaceImpl implements TestAOPInterface
{
public void test(String arg0, String arg1) {
System.out.println(arg0+"/"+arg1);
}
public void testAAA(String arg0, String arg1) {
System.out.println(arg0+":"+arg1);
}
}
2.3 接口实现类TestAOPInterface的Advice类
package com.mysoft.pmi.testcase.aop;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.Authentication;
//import org.acegisecurity.vote.RoleVoter;
//import org.apache.log4j.Logger;
//import org.acegisecurity.providers.ProviderManager;
//import org.acegisecurity.vote.AffirmativeBased;
//import org.acegisecurity.ui.session.HttpSessionEventPublisher;
//import org.acegisecurity.providers.anonymous.AnonymousProcessingFilter;
//import org.acegisecurity.providers.dao.DaoAuthenticationProvider;
//import org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider;
//import org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache;
//import org.acegisecurity.userdetails.jdbc.JdbcDaoImpl;
//import org.acegisecurity.userdetails.jdbc.JdbcDaoImpl;
//import org.acegisecurity.context.SecurityContextImpl;
//import org.acegisecurity.vote.AffirmativeBased;
//import org.acegisecurity.userdetails.jdbc.JdbcDaoImpl;
//import org.springframework.jdbc.datasource.DataSourceTransactionManager;
//import org.springframework.jdbc.core.JdbcTemplate;
//import org.springframework.beans.factory.config.CustomEditorConfigurer;
//import org.acegisecurity.intercept.method.MethodDefinitionSource;
//import org.springframework.transaction.interceptor.TransactionInterceptor;
//import org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor;
//import org.acegisecurity.providers.*;
public class WelcomeAdvice implements MethodBeforeAdvice
{
public void before(Method method, Object[] args, Object target) throws Throwable
{
System.out.println("hihihihihi,启动MethodBeforeAdvice ");
String userName;
SecurityContext ctx = SecurityContextHolder.getContext();
if(ctx!=null)
{
System.out.println("ctx is not null");
Authentication auth = ctx.getAuthentication();
if(auth!=null)
{
System.out.println("auth is not null!");
Object principal=auth.getPrincipal();
if(principal instanceof UserDetails)
{
//输出用户名
System.out.println( "UserName is "+((UserDetails)principal).getUsername());
//密码获得
System.out.println( "UserPassword is "+((UserDetails)principal).getPassword());
}else{
System.out.println("pricipal.toString()=="+principal.toString());
}
}
else
{
System.out.println("auth is null!");
}
}
else
{
System.out.println("ctx is null");
}
userName = SecurityContextHolder.getContext().getAuthentication().toString();
System.out.println("UserName is:");
System.out.println(userName);
System.out.println("++++++++++++++++++");
//输出被拦截的接口的名称
System.out.println(target.getClass().getName());
System.out.println(method.getName());
for(int i=0;i〈args.length;i++)
{
System.out.println("HI:"+(String)args[i]);
//输出传入的字符串参数的值
System.out.println( args[i] .getClass().toString());
}
//
//Logger song = Logger.getLogger(target.getClass()); //得到目标类的logger
//System.out.println("println:"+target.getClass().getName()+" did "+method.getName());
}
}
说明:当接口TestAOPInterface的方法被调用前,WelcomeAdvice类作为此接口的前置通知,其before方法将先于TestAOPInterface的方法(在aoptest.xml中配置了WelcomeAdvice拦截TestAOPInterface的所有方法),在before方法里实现了取得当前会话的Authentication即当前用户的身份。既然取得了用户的登录Id,就可以利用此Id做更细粒度的权限控制及添加系统操作日志(日志需要记录哪个用户调用了此方法),由于acegi的这种授权体系支持到方法级别,可以在Advice中进行更深入的开发-规则引擎的开发。例如在ERP系统中,A库管员和B库管员都可以操作入库单录入功能,但A库管员是原料库库管员,B是成品库库管员,如果成品库库管员录入了原料库物品,则提交入库单操作失败,这样的需求,只提供方法及的授权还不够,可通过开发规则引擎来支持数据级的授权。实现方式可将限制参数于传入的方法实际参数进行比较,如符合规则则继续执行。
如果大家想在业务方法里而不是advce类里获得userId,最好是参照上述代码,单独设计一个类封装acegi的调用,以免在业务方法中直接引入acegi的API造成对acegi的过度依赖。
2.4 DataSourceMethodDefinitionSourceEditor类
package com.mysoft.pmi.security;
import org.acegisecurity.intercept.method.MethodDefinitionMap;
import org.acegisecurity.ConfigAttributeEditor;
import org.acegisecurity.ConfigAttributeDefinition;
import java.util.List;
import java.util.Iterator;
import java.util.Map;
import java.beans.PropertyEditorSupport;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* 〈br〉Title:
* 〈br〉Description:
* 〈br〉Copyright: Copyright.com (c) 2003
* 〈br〉Company:
* History:
* create 2005-11-7 19:02:16
*
* @author youlq
* @version 1.0
*/
public class DataSourceMethodDefinitionSourceEditor extends PropertyEditorSupport{
protected JdbcTemplate jdbcTemplate;
//~ Methods ================================================================
public JdbcTemplate getJdbcTemplate(){
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate){
this.jdbcTemplate=jdbcTemplate;
}
public void setAsText(String s) throws IllegalArgumentException{
MethodDefinitionMap source=new MethodDefinitionMap();
List rs;
try{
rs=jdbcTemplate.queryForList(s);
} catch(Exception e){
setValue(source);
e.printStackTrace();
return;
}
if((rs==null)||rs.size()==0){
// Leave value in property editor null
} else{
// Now we have properties, process each one individually
ConfigAttributeEditor configAttribEd=new ConfigAttributeEditor();
for(Iterator iter=rs.iterator();iter.hasNext();){
Map row=(Map)iter.next();
String authority=(String)row.get("AUTHORITY");
String resource=(String)row.get("PROTECTED_RES");
if((null==authority)||(resource==null)){
continue;
}
// Convert value to series of security configuration attributes
configAttribEd.setAsText(authority);
ConfigAttributeDefinition attr=(ConfigAttributeDefinition)configAttribEd .getValue();
// Register name and attribute
source.addSecureMethod(resource, attr);
}
}
setValue(source);
}
}
第三章 测试页面
3.1 登录页面
acegilogin.jsp(将此文件放在web应用的根目录下):
〈%@ page contentType="text/html;charset=GBK" language="java" %〉
〈%@ page import="org.acegisecurity.ui.AbstractProcessingFilter" %〉
〈%@ page import="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter" %〉
〈%@ page import="org.acegisecurity.AuthenticationException" %〉
〈html〉
〈head〉
〈title〉Login〈/title〉
〈/head〉
〈body〉
〈h1〉Login〈/h1〉
〈%
String login_error=request.getParameter("login_error");
if(login_error!=null){
%〉
〈font color="red"〉
登录失败。原因: 〈%= ((AuthenticationException) session.getAttribute(AbstractProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY)).getMessage() %〉
〈/font〉
〈%
}
%〉
〈form action="j_acegi_security_check" method="POST"〉
〈table〉
〈tr〉〈td〉User:〈/td〉〈td〉〈input type='text' name='j_username'〉〈/td〉〈/tr〉
〈tr〉〈td〉Password:〈/td〉〈td〉〈input type='password' name='j_password'〉〈/td〉〈/tr〉
〈tr〉〈td colspan='2'〉〈input name="submit" type="submit"〉〈/td〉〈/tr〉
〈tr〉〈td colspan='2'〉〈input name="reset" type="reset"〉〈/td〉〈/tr〉
〈/table〉
〈/form〉
〈/body〉
〈/html〉
说明:“一餐三碗”提供的数据库表中的root用户口令为root。
3.2 登录后进入index.jsp
在web应用根目录下建立一个secure目录,此目录下件一个index.jsp文件:
〈html〉
〈body〉
〈h1〉Secure Page〈/h1〉
This is a protected page. You can get to me if you've been remembered,
or if you've authenticated this session.
〈p〉〈a href="../testacegi.jsp"〉Home〈/a〉
〈p〉〈a href="../j_acegi_logout"〉Logout〈/a〉
〈/body〉
〈/html〉
3.3 调用业务方法的页面
在web应用的根目录下建立一个testacegi.jsp文件:
〈%@ page import="org.acegisecurity.context.SecurityContextHolder" %〉
〈%@ page import="org.acegisecurity.Authentication" %〉
〈%@ page import="org.acegisecurity.ui.AccessDeniedHandlerImpl" %〉
〈%@ page import="org.springframework.aop.framework.ProxyFactoryBean"%〉
〈%@ page import="org.springframework.context.ApplicationContext"%〉
〈%@ page import="org.springframework.context.support.ClassPathXmlApplicationContext"%〉
〈%@ page import="com.mysoft.pmi.testcase.aop.TestAOPInterface"%〉
〈%
System.out.println("_______"+SecurityContextHolder.getContext().getAuthentication().toString());
ApplicationContext context1 = new ClassPathXmlApplicationContext("com/mysoft/pmi/resource/aoptest.xml");
TestAOPInterface bean1 = (TestAOPInterface)context1.getBean("aopProxyFactoryBean");
bean1.test("aa1a","ccc");
bean1.testAAA("dd1d","dwdwsd");
%〉
〈% Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) { %〉
Authentication object as a String: 〈%= auth.toString() %〉〈BR〉〈BR〉
〈% } %〉
说明:本文的例子是在Bea Weblogic8.1下测试通过。
JSP测试流程:在登录页面输入正确的用户名和口令(如果口令错误,控制台会报bad credentials的错误),登录成功后,浏览器打开了secure/index.jsp页面,点此页面的home连接,此连接调用了testacegi.jsp,此JSP调用了TestAOPInterface的接口方法,可以看到后台显示了当前的userId(见WelcomeAdvice.java)。如果需要将口令进行md5加密,然后与数据库中加密的口令进行比较,可参考applicationContext-acegi-security.xml。
发表评论
-
总结的工作心得~希望对刚工作的coder有帮助
2009-12-05 18:17 1706这篇日志完全是自己经历很多事情后所悟出来的道理,希望自 ... -
我是游戏王 哎哟~这个网站不错哟!
2009-08-18 20:49 736DOTA堂刺客的入门 隋唐志红拂女后期玩法问题 伏魔英雄传 ... -
SVN常用操作详解(图解)
2009-06-19 14:38 1550将服务器上的目录和文件下载到本地 Checkout < ... -
http://www.blogjava.net/wangkai40/archive/2009/05/
2009-05-31 17:03 867基于Struts+Spring+Hibernate ... -
彻底明白Java的IO系统
2008-10-27 15:37 858彻底明白Java的IO系统2008 ... -
JAVA 区位码于汉字的互换
2008-09-20 11:05 1792import java.io.UnsupportedEncod ... -
java实现文件压缩
2008-08-29 14:28 827import java.io.File; import jav ...
相关推荐
在本文中,我们将深入探讨如何使用 Acegi 实现用户权限的管理实例。 Acegi 提供了一套全面的认证和授权机制,它允许开发者以声明式的方式定义用户的访问权限,使得权限控制更加灵活和易于维护。首先,我们需要理解 ...
<bean id="filterSecurityInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor"> **" access="ROLE_ADMIN"/> **" access="IS_AUTHENTICATED_ANONYMOUSLY"/> ``` 在这个...
在Acegi中,`AuthenticationProvider`接口负责处理身份验证逻辑,例如与数据库交互验证用户凭证。 2. **授权(Authorization)**:一旦用户被成功验证,就需要确定他们是否有权限访问特定的资源或执行操作。Acegi...
在业务逻辑层,我们可以使用Java 5注解来简化安全控制。例如,可以使用`@Secured`注解标记那些需要特定角色才能访问的方法。这样,当尝试访问这些方法时,Acegi会自动检查当前用户的权限,如果符合角色要求则允许...
4. 在Web层和业务层添加必要的安全注解或XML配置。 5. 测试和调试安全配置,确保所有资源的访问控制符合预期。 总的来说,Acegi为Spring应用提供了一套全面的安全解决方案,涵盖了从用户认证到权限控制的各个方面。...
用户角色表则有`user_id`(用户ID)和`role_id`(角色ID)字段,用于建立用户和角色之间的多对多关系。权限角色表通常包含`role_id`(角色ID)和`permission`(权限标识)字段,表示特定角色拥有执行某些操作的权限...
总的来说,Acegi通过声明式配置提供了强大的Web应用安全解决方案,使得开发者可以专注于业务逻辑,而不必过于担心安全问题。配置 Acegi 要注意过滤器链的顺序、过滤器的功能以及与Spring容器的集成,以确保安全策略...
6. **权限表达式(Permission Expressions)**:Acegi支持使用SpEL(Spring Expression Language)来表达复杂的权限逻辑。 在实际应用中,我们通常会在Spring的XML配置文件中定义这些组件。例如,为了配置认证管理...
此外,Acegi引入了一种强大的表达式语言,允许在配置中使用条件语句,如`hasRole('ROLE_ADMIN')`来检查用户角色。 **三、Web安全特性** 1. **Session Fixation Protection**:防止会话劫持,即当用户登录后,系统...
5. **后续操作**:根据用户的权限执行相应的业务逻辑。 #### 五、Acegi认证核心组件 - **AuthenticationManager**:负责处理用户的认证过程。 - **UserDetailsService**:提供获取用户详细信息的方法,例如用户名...
在用户登录时,框架会生成一个随机密钥,并结合用户ID和时间戳通过一个不可逆的哈希算法(如SHA-1)生成令牌。这样,即使令牌被截获,攻击者也无法直接获取用户信息。 另一方面,匿名登录是一种让用户在不提供完整...
虽然Spring Acegi在后来的版本中已被Spring Security取代,但其核心思想和机制在Spring Security中得到了延续和增强。Spring Security提供了更多安全特性,如支持OAuth2、JWT等现代认证协议,以及更丰富的自定义选项...
Acegi Security Framework是一款在Spring早期广泛使用的安全框架,它提供了基于角色的访问控制(RBAC)功能。在处理复杂的权限管理时,Acegi允许开发者将资源权限数据存储到数据库,而不是仅局限于配置文件中,这样...
3. **业务层**:Spring Service处理登录逻辑,可能调用Hibernate的DAO层查询数据库,对比用户输入的凭证。 4. **数据访问层**:Hibernate的DAO负责执行SQL查询,验证用户名和密码是否匹配。 5. **安全验证**:如果...
Shiro 设计目标是简化安全代码,使得开发者能专注于业务逻辑而不被安全问题困扰。 1.2.2 功能架构 Shiro 主要由认证、授权、会话管理和加密四个部分组成,提供了一个清晰的安全管理层次结构。 1.3 安全框架选择 ...
Struts2是基于Apache Jakarta Struts的下一代MVC框架,它提供了强大的Action、拦截器、结果类型等功能,使得Web应用的业务逻辑与视图层分离。Action是Struts2的核心,负责处理用户的请求并调用相应的业务逻辑。拦截...
- 实现用户、角色和权限的业务逻辑,例如用户登录、角色分配、权限变更等。 5. MySQL数据库设计: - 用户表(User Table):存储用户基本信息,如用户名、密码、状态等。 - 角色表(Role Table):记录角色信息...
7. 其他模块:如课题管理、审核管理、选题管理等,都是基于用户权限进行的操作,需要在业务逻辑中考虑权限控制。 在实际的毕业设计管理系统中,SSH整合可以帮助实现上述各个功能模块,通过合理的权限控制确保系统的...