粗-细颗粒权限控制

来源:互联网 发布:ds数据精灵注册机 编辑:程序博客网 时间:2024/04/30 10:35

1 粗颗粒度权限控制(使用过滤器完成)

分析:

精确到Session的权限控制(判断Session是否存在)

使用过滤器完成粗颗粒的权限控制,如果Session不存在就跳转到首页,如果存在可以通过URL链接访问到对应的操作。

第一步:定义一个过滤器:

public class SystemFilter implements Filter {

    /**web容器启动的时候,执行的方法*/

    //存放没有Session之前,需要放行的连接

    List<String> list = new ArrayList<String>();

    public void init(FilterConfig config) throws ServletException {

        list.add("/index.jsp");

        list.add("/image.jsp");

        list.add("/system/elecMenuAction_menuHome.do");

    }

   

    /**每次访问URL连接的时候,先执行过滤器的doFilter的方法*/

    public void doFilter(ServletRequest req, ServletResponse res,

            FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;

        HttpServletResponse response = (HttpServletResponse) res;

        //获取访问的连接地址

        String path = request.getServletPath();

        //在访问首页index.jsp页面之前,先从Cookie中获取name,password的值,并显示在页面上(完成记住我)

        this.forwordIndexPage(path,request);

        //如果访问的路径path包含在放行的List的存放的连接的时候,此时需要放行

        if(list.contains(path)){

            chain.doFilter(request, response);

            return;

        }

        //获取用户登录的Session

        ElecUser elecUser = (ElecUser)request.getSession().getAttribute("globle_user");

        //放行

        if(elecUser!=null){

            chain.doFilter(request, response);

            return;

        }

        //重定向到登录页面

        response.sendRedirect(request.getContextPath()+"/index.jsp");

    }

   

    /**销毁*/

    public void destroy() {

 

    }

   

    /**在访问首页index.jsp页面之前,先从Cookie中获取name,password的值,并显示在页面上(完成记住我)*/

    private void forwordIndexPage(String path, HttpServletRequest request) {

        if(path!=null && path.equals("/index.jsp")){

            String name = "";

            String password = "";

            String checked = "";

            Cookie [] cookies = request.getCookies();

            if(cookies!=null && cookies.length>0){

                for(Cookie cookie:cookies){

                    if(cookie.getName().equals("name")){

                        name = cookie.getValue();

                        /**

                         * 如果name出现中文,对中文进行解码

                         */

                        try {

                            name = URLDecoder.decode(name, "UTF-8");

                        } catch (UnsupportedEncodingException e) {

                            e.printStackTrace();

                        }

                        checked = "checked";

                    }

                    if(cookie.getName().equals("password")){

                        password = cookie.getValue();

                    }

                }

            }

            request.setAttribute("name", name);

            request.setAttribute("password", password);

            request.setAttribute("checked", checked);

        }

    }

}

 

第二步:在web容器中添加对应的过滤器:

<!-- 自定义过滤器,要求添加到struts2过滤器的前面 -->

    <filter>

        <filter-name>SystemFilter</filter-name>

        <filter-class>cn.itcast.elec.util.SystemFilter</filter-class>

    </filter>

    <filter-mapping>

        <filter-name>SystemFilter</filter-name>

        <url-pattern>*.do</url-pattern>

        <url-pattern>*.jsp</url-pattern>

    </filter-mapping>

 

问题:不够友好,访问链接,最好提示【非法操作,系统将会5秒后跳转到登录页面】

修改过滤器类中的操作:

(1)在过滤器中的init方法中添加2个放行的连接:

list.add("/error.jsp");

list.add("/system/elecMenuAction_logout.do");

(2)在doFilter的方法重定向到error.jsp

将:

//重定向到登录页面

response.sendRedirect(request.getContextPath()+"/index.jsp");

修改成:

//重定向到error.jsp(5秒跳转到登录名页面)

response.sendRedirect(request.getContextPath()+"/error.jsp");

(3)error.jsp的内容:

<script>

var i=6;

var t;

function showTimer(){

 if(i==0){//如果秒数为0的话,清除t,防止一直调用函数,对于反应慢的机器可能实现不了跳转到的效果,所以要清除掉 setInterval()

  parent.location.href="${pageContext.request.contextPath }/system/elecMenuAction_logout.do";

  window.clearInterval(t);

 

  }else{

  i = i - 1 ;

  // 秒数减少并插入 timer 层中

  document.getElementById("timer").innerHTML= i+"秒";

  }

}

// 每隔一秒钟调用一次函数 showTimer()

t = window.setInterval(showTimer,1000);

</script>

 

注意:Session不应该在服务器一直不清空,如果Session过多,会导致Session压力大,系统变慢,于是要求10分钟如果不操作系统,将Session自动清空。在web.xml中配置

<session-config>

    <session-timeout>10</session-timeout>

</session-config> 

 

粗颗粒的权限控制的面试:

使用过滤器

在过滤器中定义放行的连接,因为不是每个操作都会存在Session

在过滤器中获取登录后存放的Session,如果Session不为空,则放行,即可以操作定义的业务功能,如果Session为空,则跳转到登录页面。

控制访问的系统必须要存在Session

2  细颗粒权限控制(使用struts2的拦截器)

/**
* 自定义注解
*/
//被这个注解修饰的注解,利用反射,将其他的注解读取出来
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationLimit {
String mid();//权限的code
String pid();//父级权限的code
}

public class ErrorAndLimitInterceptor extends MethodFilterInterceptor {

/**拦截器*/
@Override
protected String doIntercept(ActionInvocation actioninvocation) throws Exception {
//把自定义错误信息 放置到request中
HttpServletRequest request = (HttpServletRequest) actioninvocation
.getInvocationContext().get(StrutsStatics.HTTP_REQUEST);
try {
//获取请求的action对象
Object action = actioninvocation.getAction();
//获取请求的方法的名称
String methodName = actioninvocation.getProxy().getMethod();
//获取action中的方法的封装类(action中的方法没有参数)
Method method = action.getClass().getMethod(methodName, null);
// Action的返回值 
String result = null; 

//在完成跳转Action之前完成细颗粒权限控制,控制Action的每个方法
//检查注解,是否可以操作权限的URL
//boolean flag = isCheckLimit(request,method);
if(true){
// 运行被拦截的Action,期间如果发生异常会被catch住 
result = actioninvocation.invoke();
}
else{
request.setAttribute("errorMsg", "对不起!您没有权限操作此功能!");
return "errorMsg";
}
return result;
} catch (Exception e) {
/** 
* 处理异常 
*/
String errorMsg = "出现错误信息,请查看日志!";
//通过instanceof判断到底是什么异常类型 
if (e instanceof RuntimeException) {
//未知的运行时异常 
RuntimeException re = (RuntimeException) e;
//re.printStackTrace();
errorMsg = re.getMessage().trim();
}
/** 
* 发送错误消息到页面 
*/
request.setAttribute("errorMsg", errorMsg);

/** 
* log4j记录日志 
*/
Log log = LogFactory
.getLog(actioninvocation.getAction().getClass());
log.error(errorMsg, e);
return "errorMsg";
}// ...end of catch 
}


/**验证细颗粒权限控制*/
public boolean isCheckLimit(HttpServletRequest request, Method method) {
if(method == null){
return false;
}
//获取当前的登陆用户
ElecUser elecUser = (ElecUser)request.getSession().getAttribute("globle_user");
if(elecUser == null){
return false;
}

//获取当前登陆用户的角色(一个用户可以对应多个角色)
Hashtable<String, String> ht = (Hashtable)request.getSession().getAttribute("globle_role");
if(ht == null){
return false;
}
//处理注解,判断方法上是否存在注解(注解的名称为:AnnotationLimit)
/*
* 例如:
* @AnnotationLimit(mid="aa",pid="0")
public String home(){
*/
boolean isAnnotationPresent = method.isAnnotationPresent(AnnotationLimit.class);

//不存在注解(此时不能操作该方法)
if(!isAnnotationPresent){
return false;
}

//存在注解(调用注解)
AnnotationLimit limit = method.getAnnotation(AnnotationLimit.class);

//获取注解上的值
String mid = limit.mid(); //权限子模块名称
String pid = limit.pid(); //权限父操作名称

/**
* 如果登陆用户的角色id+注解上的@AnnotationLimit(mid="aa",pid="0")
* * 在elec_role_popedom表中存在 flag=true,此时可以访问Action的方法;
* * 在elec_role_popedom表中不存在 flag=false,此时不能访问Action的方法;
*/
boolean flag = false;
//拦截器中加载spring容器,从而获取Service类,使用Service类查询对应的用户信息
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext());
IElecRoleService elecRoleService = (IElecRoleService)wac.getBean(IElecRoleService.SERVICE_NAME);
//遍历角色ID
if(ht!=null && ht.size()>0){
for(Iterator<Entry<String, String>> ite = ht.entrySet().iterator();ite.hasNext();){
Entry<String, String> entry = ite.next();
//获取角色ID
String roleID = entry.getKey();
flag = elecRoleService.findRolePopedomByID(roleID, mid, pid);
if(flag){
break;
}
}
}
return flag;
}

问题:为什么在struts2的拦截器中都要使用roleID,mid,pid去查询一遍数据库,而为什么不从Session中获取mid的值和注解上定义的mid的值进行比较呢?

回答:此时不安全,如果盗用账户,登录系统(一定有Session),即可以操作每一个执行的方法。但是由于挂失后,数据库存放的数据发生变化,操作每一个功能之前都会先查询权限,这样查询才能保证数据安全。

 

细颗粒的权限控制的面试:

  使用struts2的拦截器

  定义一个注解(mid和pid),对应权限code和父级权限的code,将注解添加到Action类中方法的上面

  每个Action类的方法上添加注解(mid=””,pid=””),表示方法的惟一标识(即该方法所具有的权限)

  在struts2的拦截器中,从Session中获取角色ID,获取Action类方法上的注解(mid和pid),使用角色ID,mid和pid查询角色权限表,判断当前用户是否可以操作该方法。