在jsp web项目中实现透明的权限控制

03-09-24 wuliang
现在想到的是在Filter中做……

目标:通过配置来实现权限管理,在业务代码中无需再考虑权限问题

参考:类似tomcat的realm,在进到项目前,已经由容器判断了权限。权限管理通过配置来实现,在业务代码中无需再考虑权限问题。不足是,servlet规范中用URL Pattern配置访问级别,不够灵活

办法:在Filter中做权限判断?
我做了一个接口

boolean isUserHasPerm(String user, String url)

MyFilter调用接口
  if (!isUserHasPerm(...))
	response.sendError(403, "Forbidden");
<p class="indent">

isUserHasPerm接口已经初步实现,???现在的问题是Filter好像不是放权限判断的地方???,因为签名是这样的:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)

其中request 是ServletRequest, 不是HttpServletRequest,
response是ServletResponse,不是HttpServletResponse
这样就没有request.getSession().getAttribute("userinfo"),//得到权限判断的who要素
没有response.sendError() //这样没有权限,该如何处理?

???当然可以强行转换,tomcat下也不会cast错误,但始终不是个办法……???

-----------------------------------------------
另外,说明一下isUserHasPerm的实现
相关联的对象图



核心是ACL表,实际操作给出role,function,resource三要素,如果ACL表中有记录,说明可以访问,因为三要素唯一确定一条ACL记录(function通过resoure type和operate type确定)

优化后,剩三个表,User,Role,ACL(当然,因为User,Role是多对多,还要一个关联表)

ACL表包含四个字段:
Role, Component, Instance, OP(参考了PostNuke的权限设置)

Component就是resource type,
Instance就是resource
OP就是function

合并进Component和OP,而不是functionID的好处是,可以通过通配符来设置权限,比如设置一个全局的管理权限,只需要一条记录

role=admins, component=*, instance=*, OP=*

另外,OP字段还可以等于DENY,有优先权,如果找到一条匹配的DENY记录,直接就是没有权限了。有了DENY,如果需要赋大部分权限,排除一小部分,也比较容易配置

isUserHasPerm的伪码
(1)
select * from ACL
where Role in (user.getRoles())
  and Component in (url.getComponent, *)
  and Instance in (url.getInst, *)
  and OP = DENY

(2)
if (结果集>=0)
  return false;

(3)
将(1)的OP条件改为
  and OP in (url.getOP, *)

(4)
if (结果集>0)
  return true;
else
  return false;
<p class="indent">


*注:从url中得到权限判断的要素,当然需要一些规则了,比如
category.do?op=add&id=1
表示component是category
op是add
instance是1
role当然是从session中拿

1
zingers
2003-09-24 15:44
稍微看了一下,如果我的意见和事实不符合请见谅。

我觉得用filter来执行权限判断是不可能的,因为基于数据库应用的URL经常是一致的,只是后面的参数不一样。有些参数还是表单发送的。
所以,用它来执行中文化是不错的,但是来实现ACL功能好象不行,tomcat可以是因为它的各部分是不同的URL吧。

我觉得windows2000的许可和角色和树状继承很不错,不过在应用中实现还是有难度。

banq
2003-09-24 16:31
filter概念实际就是Proxy模式实现。

ACL是肯定要用Proxy模式实现,关键是如何实现比较巧妙,象Jive那样,为每个数据Model建立Proxy(filter)的做法是不可取的,动态Proxy好像是比较好的方法,根据操作方法名称,如果get,表示读取操作,update表示更新,缺点是,要求对EJB或Service操作方法实现统一命名限制。

通过EJB容器的安全机制,在ejb-jar.xml中配置ACL也是目前比较好的办法,缺点是需要管理员修改ejb-jar.xml,那么在此基础上,编制管理程序操作修改ejb-jar.xml可以达到方便ACL设置吧。

wuliang
2003-09-25 09:27
>只是后面的参数不一样。有些参数还是表单发送的。
其实在filter中传入的参数是request,参数可以通过getRequestURI,getParameter来获取,

问题是如何通过request提取出
role,component,instance,op, 这个规则不太好定:
能要求URI就是component?
instance一定要用名为id的parameter来确定?
要显式的给出op参数?

而且,定了这样的规则,方案就不透明了

wuliang
2003-09-25 13:33
下面给出isUserHasPerm的初步实现

一、数据库,mssqlserver

drop table hive_acl;
drop table hive_account_role;
drop table hive_account;
drop table hive_role;

create table hive_account (
    id int identity,
    account varchar(50) unique not null,
    password varchar(16) not null,
    primary key (id),
);

create table hive_role (
    id int identity,
    role varchar(50) unique not null,
    primary key (id),
);

create table hive_account_role (
    account_id int,
    role_id int,
    primary key (account_id, role_id),
    foreign key (account_id) references hive_account(id),
    foreign key (role_id) references hive_role(id),
);

create table hive_acl (
    role_id int,
    component varchar(50),
    instance varchar(50),
    op varchar(50),
    primary key (role_id, component, instance, op),
    foreign key (role_id) references hive_role(id),
);

insert into hive_account (account, password) values ('admin', 'abc');
insert into hive_account (account, password) values ('amen', 'abc');
insert into hive_account (account, password) values ('anonymous', '');

insert into hive_role (role) values ('admins');
insert into hive_role (role) values ('users');
insert into hive_role (role) values ('guests');

insert into hive_account_role (account_id, role_id) values (1,1);
insert into hive_account_role (account_id, role_id) values (1,2);
insert into hive_account_role (account_id, role_id) values (2,2);
insert into hive_account_role (account_id, role_id) values (3,3);

insert into hive_acl (role_id, component, instance, op) values (1, '*', '*', '*');
insert into hive_acl (role_id, component, instance, op) values (2, 'category', 'manager', 'none');
insert into hive_acl (role_id, component, instance, op) values (2, 'category', '*', 'read');
insert into hive_acl (role_id, component, instance, op) values (2, 'category', 'public', 'write');
insert into hive_acl (role_id, component, instance, op) values (3, 'category', 'public', 'read');
<p class="indent">

wuliang
2003-09-25 13:40
测试权限的核心语句,其实后面的java方法只是另一种实现
1) 查询相应的op=none的记录,如果记录数大于则没有权限
2) 查询匹配记录,如果记录数等于0(没有acl记录)则没有权限

-- 判断是否有op=none的acl记录
declare @account varchar(50);
declare @component varchar(50);
declare @instance varchar(50);
declare @op varchar(50);

select @account = 'anonymous';
select @component = 'category';
select @instance = 'manager';
select @op = 'write';

select * from hive_acl
where role_id in (
        select role_id
        from hive_account_role inner join hive_account
            on hive_account_role.account_id = hive_account.id
        where account = @account
    )
    and component in (@component, '*')
    and instance in (@instance, '*')
    and op ='none'
;


-- 判断是否有匹配的acl记录
declare @account varchar(50);
declare @component varchar(50);
declare @instance varchar(50);
declare @op varchar(50);

select @account = 'amen';
select @component = 'category';
select @instance = 'manager';
select @op = 'write';

select * from hive_acl
where role_id in (
        select role_id
        from hive_account_role inner join hive_account
            on hive_account_role.account_id = hive_account.id
        where account = @account
    )
    and component in (@component, '*')
    and instance in (@instance, '*')
    and op in (@op, '*')
;
<p class="indent">

wuliang
2003-09-25 13:42
将由filter调用

public interface PermissionManager {

    public boolean isUserHasPerm(HttpServletRequest request);

}
<p class="indent">

wuliang
2003-09-25 13:45

public class PermissionFilter implements Filter {
    public void doFilter(
        ServletRequest request,
        ServletResponse response,
        FilterChain filterChain)
        throws IOException, ServletException {

        try {
            HttpServletRequest req = (HttpServletRequest)request;
            if (!HiveFactory.getDefaultHive().getPermissionManager().isUserHasPerm(req)) {
            	HttpServletResponse rep = (HttpServletResponse)response;
            	rep.sendError(403);
            }

        } catch (Exception e) {
        }

        try {

            filterChain.doFilter(request, response);
        } catch (Exception e) {
        }
    }
}
<p class="indent">

wuliang
2003-09-25 13:47

public class PermissionManagerImpl implements PermissionManager {

    public boolean isUserHasPerm(HttpServletRequest request) {
        HttpSession session = request.getSession();
        Account user = (Account)session.getAttribute("userinfo");
        if (user == null)
            user =
                HiveFactory
                    .getDefaultHive()
                    .getUserManager()
                    .getAnonymousUser();

        String component, instance, op;
        // get component from URI
        {
            String uri = request.getRequestURI();
            int i, j;
            i = uri.lastIndexOf("/");
            j = uri.indexOf(".do", i);
            component = uri.substring(i+1, j).toLowerCase();
        }
        // get instance
        instance = request.getParameter("id");
        // get op
        op = request.getParameter("op");
        return isUserHasPerm(user.getAccount(), component, instance, op);
    }


    private boolean isUserHasPerm(
        String account,
        String component,
        String instance,
        String op) {
        try {
            Connection cn = HiveFactory.getDefaultHive().getConnection();
            if (cn == null)
                return false;
            String sql;
            PreparedStatement pstm;
            ResultSet rs;
            int count;

            sql =
                "select count(*) from hive_acl"
                    + " where role_id in ("
                    + "        select role_id"
                    + "        from hive_account_role inner join hive_account"
                    + "            on hive_account_role.account_id = hive_account.id"
                    + "        where account = ?"
                    + "    )"
                    + "    and component in (?, '*')"
                    + "    and instance in (?, '*')"
                    + "    and op ='none'";
            pstm = cn.prepareStatement(sql);
            pstm.setString(1, account);
            pstm.setString(2, component);
            pstm.setString(3, instance);
            rs = pstm.executeQuery();
            rs.next();
            count = rs.getInt(1);
            if (count >= 1) {
                cn.close();
                return false;
            }

            sql =
                "select count(*) from hive_acl"
                    + " where role_id in ("
                    + "        select role_id"
                    + "        from hive_account_role inner join hive_account"
                    + "            on hive_account_role.account_id = hive_account.id"
                    + "        where account = ?"
                    + "    )"
                    + "    and component in (?, '*')"
                    + "    and instance in (?, '*')"
                    + "    and op in (?, '*')";
            pstm = cn.prepareStatement(sql);
            pstm.setString(1, account);
            pstm.setString(2, component);
            pstm.setString(3, instance);
            pstm.setString(4, op);
            rs = pstm.executeQuery();
            rs.next();
            count = rs.getInt(1);

            cn.close();
            if (count == 0) {
                return false;
            }
            return true;

        } catch (SQLException e) {
            e.printStackTrace();
            return false;
        }
    }

}
<p class="indent">

wuliang
2003-09-25 13:52
在web.xml中,PermissionFilter设置过滤"/secu/*"的请求

<web-app>
  <filter>
    <filter-name>permissionfilter</filter-name>
    <filter-class>com.stalent.hive.permission.PermissionFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>permissionfilter</filter-name>
    <url-pattern>/secu/*</url-pattern>
  </filter-mapping>
<p class="indent">

wuliang
2003-09-25 13:58
前面

insert into hive_acl (role_id, component, instance, op) values (1, '*', '*', '*');
insert into hive_acl (role_id, component, instance, op) values (2, 'category', 'manager', 'none');
insert into hive_acl (role_id, component, instance, op) values (2, 'category', '*', 'read');
insert into hive_acl (role_id, component, instance, op) values (2, 'category', 'public', 'write');
insert into hive_acl (role_id, component, instance, op) values (3, 'category', 'public', 'read');
<p class="indent">

前面insert的数据表示:
假想有一种“category”的资源(可以想象成目录、论坛的板块之类)
有public,manager以及其他的一些实例
role_id=1是admins,2是users,3是guests
admins有所有权限
users可以读除manager外的所有资源,并对public可写
guests只能读public

wuliang
2003-09-25 14:06
以users登录
/hiveweb/secu/category.do?op=read&id=manager
403 Forbidden

/hiveweb/secu/category.do?op=read&id=public
400 Invalid path

……

wuliang
2003-09-25 14:24
一、没有owner的概念,比如要让用户对自己创建的资源有编辑权,怎么办?……

二、PermissionFilter,如果设为过滤"/*",又是根目录下的项目,tomcat启动时也会调用doFilter方法,这不是我想要的

三、PermissionFilter中,downcast ServletRequest到HttpServletRequest,会不会有隐患?上传文件通过mutipart form提交的request会不会转换错?还有没有其他不能cast的情况?

四、用资源类型、操作类型能唯一确定一个功能吗?

xangd
2003-09-25 14:24
可以参见 seraph
http://opensource.atlassian.com/seraph

wuliang
2003-09-25 14:52
Woo~
Seraph is a 'gate keeper' for J2EE applications - thus was named after Seraph from the Matrix Reloaded.

thumbup!;)

猜你喜欢
2Go 1 2 下一页