java动态代理实现Authorization(授权)
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
动态代理实现Authorization(授权)
*
ng.reflect包中的 Proxy和InvocationHandler接口提供了创建(指定类[接口更准确些]的)动态代理类的能力。
我们知道,对象是类的实例,一般使用内存来模拟对象,对象是依据类为模板来创建的,创建时使用new来分配一块内存区(其布局
参考相应类的内存布局),为变量做一些赋值便是对象的初始化了。
我们知道通常类是设计时的产物,在设计时我们编写对象的模板(即——类),运行时
产生类的实例。
类所处的文件是 .java文件——源文件,之后编译为jvm-----java虚拟机可解释执行的.class字节码文件,在类解析
过程中这些.class文件由类加载器加载到虚拟机(可实现自己的类加载器来加载处于特定路径下的类,或加载用某种加密算法加密过的类文件---这样
便于进行安全控制——具体描述参考(Core java ——Java核心卷二))。
在遇到创建对象的指令时使用加载的类来创建对象内存
空间。
动态代理是不用创建类文件(当然也不用创建java源文件),就能在虚拟机中构造出类文件区域来(相当于使用Proxy类来
创建一块类内存区域,该区域中的内容相当于加载某个.class文件产生的区域;比如我们在使用DOM技术时,从一个XML文件构造一个
DOM内存表示,它是XML文件的内存表示,但我们也可以直接使用DOM API在内存中构建一个dom树,最终结果就是一个内存DOM树,你不用关心
这个dom树是来自于xmL文件还是直接的运行时构造)。
*
**
关于代理设计模式,这里不再敷述。
代理和被代理类(目标类)实现共同的接口。
我们在调用时不需区别它是否是真正的目标对象。
代理会转发请求到目标对象的。
比如互联网上的代理服务器,我们不必关心它是不是代理,就当它不存在一样。
对客户调用端
他是不关心具体是谁来提供这个服务功能;在服务端选择使用代理的原因可能是:安全,日志,防火墙等。
就是说代理可提供一些
非功能性功能,比如缓存功能____来加速服务的响应的速度。
在面向方面技术(AOP)中这些非功能性需求被称作“方面”他们横切于功能类
的实现中,比如:某个SubServerImpl(子服务实现)中的某个服务方法:
subServer.doService(){
//在日志输出中常有类似的代码出现于多个类实现中
loger.log("<enter 该方法被调用");
//doSomeThing();
loger.log("方法调用结束 end>");
}
如果已经存在了一个服务实现,你还需要加入日志功能,而又不想改动既有的实现,也不想太多的改动客户端,我们可以引入
一个代理类:
class SubServerProxy extends ISubServer{
ISubServerProxy target;
public SubServerProxy(ISubServerProxy target){
//代理依赖于具体的子服务实现,至于设值依赖的接口这里就不给出了
//这里只提供构造子依赖注入的接口。
this.target=target;
}
public void doService(){//实现接口
loger.log("<enter 该方法被调用");
this.target.doService();//这里先记录日志,在将具体的操作委托给目标对象。
loger.log("方法调用结束 end>");
}
}
对于客户代码,以前可能是这样:
class Client{
public doSomeThing(){
//你或许处于网络环境中,可能使用了名字服务,那么改这里的new操作为Lookup即可;
ISubServer server= new SubServerImpl();
}
}
引入了代理后只需要改为: ISubServer server= new SubServerProxy();即可,之后的代码不需要改动
依据是 Liskov替代原理(请参考Robert Martin的《Design Principles and Design Patterns》___google直接搜索该文章)。
由于我们在客户端依赖了抽象(即直接使用了继承层次树中的根——ISubService)。
**
***
上面给出了代理类的一些常用的用途,可以看出这样的非功能性需求会出现在很多代码中,我们不得不在多个类的多个方法中
加入这些讨厌的重复代码,并且可能实现多个代理类(代理本身仍旧可以选择再使用其他的代理,每个代理完成不同的方面,随后我
会使用动态代理来展示这点的)。
这样代理类的数目急剧膨胀,加大了维护和开发投入。
借助动态代理类我们不需要为每个意欲附加
非功能性需求的类来在运行时生成一个动态代理。
并且你还可以在以后重用InvacationHandler实现。
以下讨论本篇的重点。
:-)
***
****
关于 Authorization(授权):
许多软件系统中,我们都需要授权访问功能,允许某个特定用户访问特定的功能集。
将整个系统对用户提供的功能,看作一个集合,
这些集合中其中一部分对该用户可以访问,另一些对该用户不可以访问。
最完美的访问控制技术,当然是实现访问控制矩阵。
替代的ACL访问控制列表也可以实现。
访问控制矩阵概述:矩阵分两个轴,随便一个轴列出所有的主体(如可以是userName),另一个轴给出系统提供的所有客体(功能单元)
,在矩阵交叉的单元是 1,或0;1表示主体可以访问该客体,0表示主体不可以访问该客体。
ACL访问控制列表概述:由于访问矩阵中0出现的很多,在使用矩阵结构时造成大量的空间浪费,即稀疏矩阵。
我们只记载访问控制矩阵
中是1 (当然也可以是0)的主体和客体,这样我们在一个轴向上使用一个列表来存放所有的主体或客体,每个列表中的单元上
还有一个列表,其中给出了该主体(或客体)能够访问的客体(或可访问该客体的主体)。
伪码实现:
class AccessRelationEntry{
//该类表示一个访问关系的入口
User user; //表示主体
List<Object> objectives;//这是一个客体列表
public AccessRelationEntry(User user){
er=user;
this.objectives=new List<Object>();
}
//该方法可将一个客体加入到某个主体可访问的列表中去
public void addObjective(Object objective ){
this.objectives.add(objective);
}
public User getUser(){
return er;
}
public boolean hasPermission(Object objective){
return this.objectives.contain(objective);
}
}
以上只是在一个轴的实现,另一个轴如下:
//该类其实可以实现为一个全局共享的静态类,方法全为static
class ACLManager{
List<AccessRelationEntry> acl=new List<AccessRelationEntry>();
public void addAccessRelationEntry(AccessRelationEntry aRE){
this.acl.add(aRE);
}
public boolean hasPermission(User user,Object objective){
//看某个特定用户和对象是否出现访问控制列表中
//出现则表示有权限,否则无权限,当然也可反逻辑实现。
//遍历访问控制列表
for(AccessRelationEntry are:acl){
if(are.getUser().getName().equals(user.getName())){
return are.hasPermission(objective);
}
}//遍历完成,如果函数仍未返回表明,没有指定的用户,处理略...。
}
}
****
*****
Authorization设计模式(详见POSA卷四):
对于系统中子系统提供的功能,并非每个客户端都能够访问,对于一些敏感的信息(如:工资,医疗信息等),
仅授权用户才可访问。
对于提供这样服务的模块(或称为组件,子系统等),将“trusted”client硬编码到
组件中是很笨拙且高维护代价的一种实现。
(即将每个可访问该客体的主体信息硬编码到该组件的实现中)。
所以:
将访问权限赋予可访问安全敏感的子系统的用户,并在在子系统上执行任何请求之前检查用户的权限。
由高权限用户来为普通用户授权;在每个组件(或称子系统)的每个方法调用之前执行权限检查;
伪码如:
class SubSysImpl{
public void doServiceA(){
/* <start 权限检查*/
User user=AppContext.get("currentUser");//获取当前的用户
if( ACLManager.hasPermission(user,"SubSysImpl.doServiceA"){
//查询访问控制列表管理器,该用户对该方法是否有访问
//权限,如果有则继续其他的正常操作,否则抛出访问拒绝异常(自定义的)
.....Execute normal action
}else{
throw new AccessDenyException("你无权访问"+this.getClass().toString); }
/* 权限检查 end> */
}
}
授权设计模式的主要优点是,仅有授权的用户才可访问该子系统,这对安全敏感的应用系统很重要,另一个优点检查特定的访问权限(或policies)可对客户和组件透明。
对授权基础结构实现的通用形式是ROLE-BASED ACCESS CONTROL(基于角色的访问控制)。
不是把单个访问权限(AccessRights)赋给每个用户,在系统中定义了一些角色集(如:administrator,power user,guest等),用户(User)可与某个角色关联,每个角色对应了一些访问权限集。
REFERENCE MONITOR补充了授权体系结构,所有的用户请求都会被一个中央引用监视器“拦截”
,拦截后检查用户的访问权限是否与应用的授权规则一致。
这种设计集中化了访问权限的检查
对每一个客体都需要(必要时)先通过monitor的检查,然后才可访问。
可使用多个引用监视器
来最小化性能或减小瓶颈,减小单点失败的可能。
*****
******
权限的粒度:对一个主体,和客体。
访问粒度(或深度)可以是很细的,比如到方法级别,也可以
到对象级别,在上面的授权模式中doServiceA就是一个方法级别的权限检查。
本篇的主题是,动态代理来实现授权设计模式。
权限的粒度可以到方法级别,
篇首给出了动态代理的简要描述,动态代理中关键点是那个Invocationhandler接口,他在AOP中角色
相当于interceptor(请参考《j2ee development without EJB》,或其他AOP技术资料),各种方面的
“织入”也发生在这里。
请看下面的关系图:(使用Enterprise Architect绘制)
其中生成的动态代理类名是以$Proxy开头的。
关于动态代理知识请参考相关资料(孙卫琴的<<java
网络编程精解>>相当不错,中文java书籍中我看过的书籍中她的书籍是最多的).
实现一个InvocationHandler接口,便可将该实现应用到多个类上来产生动态代理类:
以下是我的一个小练习:
package proxyTest;
import ng.reflect.*;
public class ProxyTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("<< Enter the main");
//newProxyInstance方法的返回结果是一个动态代理
//它实现IService接口(参数二),并使用
//LogInterceptor来织入日志功能
IService server=(IService)Proxy.newProxyInstance(
ServiceImpl.class.getClassLoader(),
new Class[]{IService.class},
new LogInterceptor(new ServiceImpl()));
server.doService();
//以下产生另一套类层次的动态代理
ISubSystem subSys=(ISubSystem)Proxy.newProxyInstance(
SubSystemImpl.class.getClassLoader(),
new Class[]{ISubSystem.class},
new LogInterceptor(new SubSystemImpl()));
System.out.println(subSys.doRequest());
System.out.println("end the main >>");
}
}
//定义了一个接口
interface IService{
public void doService();
}
//接口的实现
class ServiceImpl implements IService{
//实现接口的方法
@Override
public void doService() {
// TODO Auto-generated method stub
System.out.println("该方法实现仅打印出一行字符串");
System.out.println("come from ServiceImpl.doService.");
}
}
//该拦截器实现InvocationHandler接口,用来处理日志输入
class LogInterceptor implements InvocationHandler{
//为了通用这里使用Object类型
private Object target;//被代理类,这里其实可以使用IService接口了类型
public LogInterceptor(Object target){
this.target=target;
}
@Override
public Object invoke(Object arg0, Method arg1, Object[] arg2)
throws Throwable {
// TODO Auto-generated method stub
/*
* 该方法的参数意义:参一,是代理类对象,
* 参二,是方法对象,表示被调用的方法如
* 上面的doService方法
* 参三,方法的参数列表
× 下面将日志输入到控制台
*/
try{
System.out.println("<< enter method:"+arg1.getName());
return arg1.invoke(target, arg2);//将调用委托给目标对象。
}finally{
System.out.println("end method "+arg1.getName()+">>");
}
}
}
//*********************************************
/**
* 另一套类层次
*/
interface ISubSystem{
public String doRequest();
}
class SubSystemImpl implements ISubSystem{
@Override
public String doRequest() {
// TODO Auto-generated method stub
return "this rslt com from SubSystemImpl.doService!";
}
}
//*********************************************
******
*******
使用动态代理来实现验证:
参考上面的Authorization设计模式,关键是确定权限的粒度,我们可以到方法级别另一比较重要的是如何建立访问控制列表控制系统,你可以借助于数据库表来实现
,比如对所有的系统用户都对应于表
//该表不一定合理,你自己调整下,你可能
//会加入password字段来实现验证功能,甚至错误,我的sql知识较差,见笑,//不过用工具建表应该不会出错的!
tbl_users(Id int auto_incrementation primarykey,name varchar(15));
//该表存放所有的客体名称,可以是类名,或类名加方法名
所有的客体表:tbl_objectives(Id int primary key,name varchar(50));
//users_rights该表存放特定用户是否可访问某个客体
//如果用户和客体名称同时出现于该表的一行,表示该主体可以访问客体
tbl_users_rights(userId int,objId int);
数据库支持大致就是上面这些,自己调整一下啊!
然后来是现InvocationHandler接口;
class AuthorizationInterceptor implements
InvocationHandler{
private Object target;
public AuthorizationInterceptor(Object target){
this.target=target;//这是授权访问的客体
}
@Override
public Object invoke(Object arg0, Method arg1, Object[] arg2)
throws Throwable {
// TODO Auto-generated method stub
//方法调用前进行拦截,验证
User user=(User)AppContext.get("userName");
ACLManager aclMngr=new ACLManager();
if(aclMngr.hasPermission(user ,arg1.getClass().toString())){
//拥有访问权限时
//正常处理
return arg1.invoke(this.target,arg2);
}else{
throw new AccessDenyException("你没有访问权限!");
}
}
}
以上判断一个用户是否有权访问某个客体类,并没有操作数据库,实际应用中
可以通过查询数据库来返回结果,由于篇幅有限,大家自己实现吧!
在实现了InvocationHandler之后,你可以将系统中提供服务的组件分为两类:需要授权访问的;和不需要授权访问的
为需要授权访问的控件生成代理类如上例:
IService server=(IService)Proxy.newProxyInstance(
ServiceImpl.class.getClassLoader(),
new Class[]{IService.class},
new LogInterceptor(new ServiceImpl()));
之后正常使用server对象,注意我们在授权访问的拦截器处理中,无权访问时抛出的是异常,你根据实际情况可改为,弹出一个对话框后不做任何事,或
向控制台输出信息。
关于拦截器形成链:
可以将拦截器链起来,形成链,这些拦截器各司其职,比如日志,验证等
即方面间正交性。
如定义了一个装饰调用的拦截器:
//该拦截器实现InvocationHandler接口,用来装饰方法调用
class DecorationInterceptor implements InvocationHandler{
//为了通用这里使用Object类型
private Object target;//被代理类,这里其实可以使用IService接口了类型
public DecorationInterceptor(Object target){
this.target=target;
}
public Object invoke(Object arg0, Method arg1, Object[] arg2)
throws Throwable {
try{
System.out.println(" 《header********这是装饰*******herder》 "); return arg1.invoke(target, arg2);//将调用委托给目标对象。
}catch (Exception e){
throw e;//重抛异常
}
finally{
System.out.println(" 《footer********这是装饰*******footer》 "); }
}
}
在上面main()方法中加入以下行
IService decorativeServer=(IService)Proxy.newProxyInstance(
ServiceImpl.class.getClassLoader(),
new Class[]{IService.class},
new DecorationInterceptor(server));
decorativeServer.doService();
运行代码看看效果,这里只是为了说明,链式拦截器的思想是可以使用动态代理实现的
,
*******
最后给出大致的实现,我这篇文章大致就是我对授权系统实现的思想,不过最终的实现可能还有些费劲,以后有机会在细化了,如下的全部代码:
//*********************************************
/*
* 在GUI应用中我们常常是根据用户的角色(当然
* 我这里不打算使用基于角色的的访问控制系统
* 更改也是比较容易的。
思路类似!)
* 来决定用户是否有权使用某个按钮或菜单。
所以
* 我们为按钮或菜单类来生成动态代理类,其共有的
* 接口是java.awt.ItemSelectable(或是AbstractButton)
* 我是这样设计的但在真正实现时才知不是太可行
* 动态代理只能为接口产生子类,不能为已有的
* 抽象类和具体类产生代理,
* 所以,还是要改设计,在以后的文章中
* 我再给出设计思路吧。
* 以下给出验证拦截器实现:
*/
class AuthorizationInterceptor implements
InvocationHandler{
private java.awt.ItemSelectable abstractBt;
public AuthorizationInterceptor(java.awt.ItemSelectable uiActionButton){
this.abstractBt=uiActionButton;//这是授权访问的客体
}
@Override
public Object invoke(Object arg0, Method arg1, Object[] arg2)
throws Throwable {
// TODO Auto-generated method stub
//方法调用前进行拦截,验证
User user=(User)AppContext.get("userName");
ACLManager aclMngr=new ACLManager();
if(aclMngr.hasPermission(user ,arg1.getClass().toString())){
//拥有访问权限时
//正常处理
//注意任何方法调用都可能抛出异常
//关于异常如何处理需仔细考虑
//这里并没有处理arg1.invoke方法可能抛出的异常。
return arg1.invoke(this.abstractBt,arg2);
}else{
throw new AccessDenyException("你没有访问权限!");
}
}
}
//可以配合数据库访问
class User {
private String name;
public User(String name){
=name;
}
public void setName(String name){
=name;
}
public String getName(){
return ;
}
}
class AccessRelationEntry{
//该类表示一个访问关系的入口
User user; //表示主体
List<Object> objectives;//这是一个客体列表
public AccessRelationEntry(User user){
er=user;
this.objectives=new java.util.LinkedList<Object>();
}
//该方法可将一个客体加入到某个主体可访问的列表中去
public void addObjective(Object objective ){
this.objectives.add(objective);
}
public User getUser(){
return er;
}
//该方法可以更换为查询数据库来返回结果。
public boolean hasPermission(Object objective){
return this.objectives.contains(objective);
}
}
//方法hasPermission可以通过查询数据库来返回结果
class ACLManager{
List<AccessRelationEntry> acl=
new java.util.LinkedList<AccessRelationEntry>();
public void addAccessRelationEntry(AccessRelationEntry aRE){
this.acl.add(aRE);
}
public boolean hasPermission(User user,Object objective){
//看某个特定用户和对象是否出现访问控制列表中
//出现则表示有权限,否则无权限,当然也可反逻辑实现。
boolean rslt=false;
//遍历访问控制列表
for(AccessRelationEntry are:acl){
if(are.getUser().getName().equals(user.getName())){
rslt=are.hasPermission(objective);
}
}//遍历完成,如果函数仍未返回表明,没有指定的用户,处理略...。
return rslt;
}
}
//该类用来存放一些应用程序级别的对象供全局共享
class AppContext{
//该数据结构用来存放全局共享的一些数据
private static java.util.concurrent.ConcurrentHashMap<String, Object>
AppDatas=new java.util.concurrent.ConcurrentHashMap<String, Object>();
public static boolean set(String name,Object value){
if(!AppDatas.containsKey(name)){
//如果未以name键值存放过数据
//存放数据
AppDatas.put(name, value);
return true;//存放成功
}else{
//该键值已经被存放过
return false;
}
}
public static Object get(String name){
if(AppDatas.containsKey(name)){
//存在该键值时
return AppDatas.get(name);
}else{
return null;//注意找不到给定键值对应的对象是返回null
//除非你把 null作为一个value插入到集合中了
//不知道能否插入。
}
}
}
class AccessDenyException extends Exception{
public AccessDenyException(String msg){
super(msg);
}
}
大致就这样了,我还是要继续我的艰苦学习啊!希望和大家共同进步。
最近又搞上了PHP啊,java学习总是断断续续的,哎,太不专心了。
to be continue.................实现权限访问控制2。