zoukankan      html  css  js  c++  java
  • Spring AOP 实现功能权限校验功能

     
     

    实现功能权限校验的功能有多种方法,其一使用拦截器拦截请求,其二是使用AOP抛异常。 
    首先用拦截器实现未登录时跳转到登录界面的功能。注意这里没有使用AOP切入,而是用拦截器拦截,因为AOP一般切入的是service层方法,而拦截器是拦截控制器层的请求,它本身也是一个处理器,可以直接中断请求的传递并返回视图,而AOP则不可以。

    1.使用拦截器实现未登录时跳转到登录界面的功能

    1.1 拦截器SecurityInterceptor

    package com.jykj.demo.filter;
    
    import java.io.PrintWriter;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    import com.alibaba.fastjson.JSON;
    import com.jykj.demo.util.Helper;
    import com.jykj.demo.util.Result;
    
    public class SecurityInterceptor implements HandlerInterceptor{
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {
            System.out.println("SecurityInterceptor:"+request.getContextPath()+","+request.getRequestURI()+","+request.getMethod());
            HttpSession session = request.getSession();
            if (session.getAttribute(Helper.SESSION_USER) == null) {
                System.out.println("AuthorizationException:未登录!"+request.getMethod());
                if("POST".equalsIgnoreCase(request.getMethod())){
                    response.setContentType("text/html; charset=utf-8");  
                    PrintWriter out = response.getWriter();   
                    out.write(JSON.toJSONString(new Result(false,"未登录!")));
                    out.flush();
                    out.close();
                }else{
                    response.sendRedirect(request.getContextPath()+"/login"); 
                }
                return false;
            } else {
                return true;
            }
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                ModelAndView modelAndView) throws Exception {
            // TODO Auto-generated method stub
    
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
                throws Exception {
            // TODO Auto-generated method stub
    
        }
    
    }
    

    1.2.spring-mvc.xml(拦截器配置部分)

    <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
        <mvc:resources mapping="/resources/**" location="/resources/" />
    <mvc:interceptors>
            <mvc:interceptor>
                <mvc:mapping path="/*"/>  <!-- 拦截/  /test  /login  等等单层结构的请求  --> 
                <mvc:mapping path="/**/*.aspx"/><!-- 拦截后缀为.aspx的请求 -->
                <mvc:mapping path="/**/*.do"/><!-- 拦截后缀为 .do的请求 -->
                <mvc:exclude-mapping path="/login"/>
                <mvc:exclude-mapping path="/signIn"/>
                <mvc:exclude-mapping path="/register"/>
                <bean class="com.jykj.demo.filter.SecurityInterceptor">
                </bean>
            </mvc:interceptor>
        </mvc:interceptors>

    这里需要特别说明:拦截器拦截的路径最好是带有后缀名的,否则一些静态的资源文件不好控制,也就是说请求最好有一个统一的格式如 .do 等等,这样匹配与过滤速度会非常快。如果不这样,例如 用 /** 来拦截所有的请求,则页面渲染速度会非常慢,因为资源文件也被拦截了。

    2.使用AOP实现功能权限校验

    对于功能权限校验也可以类似地用拦截器来实现,只不过会拦截所有的请求,对不需要权限校验的请求没有很好的过滤功能,所以采用AOP指定拦截需要校验的方法的方式来实现之。

    2.1 切面类 PermissionAspect

    package com.jykj.demo.filter;
    
    import java.io.IOException;
    import java.lang.reflect.Method;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.beans.factory.annotation.Autowired;
    
    import com.jykj.demo.annotation.ValidatePermission;
    import com.jykj.demo.exception.AccessDeniedException;
    import com.jykj.demo.service.SysUserRolePermService;
    
    /**
     * 事件日志 切面,凡是带有 @ValidatePermission 以及@ResponseBody注解 控制器 都要进行 功能权限检查,
     * 若无权限,则抛出AccessDeniedException 异常,该异常将请求转发至一个控制器,然后将异常结果返回
     * @author Administrator
     *
     */
    public class PermissionAspect {
        @Autowired
        SysUserRolePermService sysUserRolePermService;
    
        public void doBefore(JoinPoint jp) throws IOException{
            System.out.println(
                    "log PermissionAspect Before method: " + jp.getTarget().getClass().getName() + "." + jp.getSignature().getName());
            Method soruceMethod = getSourceMethod(jp);
            if(soruceMethod!=null){
                ValidatePermission oper = soruceMethod.getAnnotation(ValidatePermission.class);
                if (oper != null) {
                    int fIdx = oper.idx();
                    Object[] args = jp.getArgs();
                    if (fIdx>= 0 &&fIdx<args.length){
                        int functionId = (Integer) args[fIdx];
                        String rs = sysUserRolePermService.permissionValidate(functionId);
                        System.out.println("permissionValidate:"+rs);
                        if(rs.trim().isEmpty()){
                            return ;//正常
                        }
                    }
                }
            }
            throw new AccessDeniedException("您无权操作!");
        }
        private Method getSourceMethod(JoinPoint jp){
            Method proxyMethod = ((MethodSignature) jp.getSignature()).getMethod();
            try {
                return jp.getTarget().getClass().getMethod(proxyMethod.getName(), proxyMethod.getParameterTypes());
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (SecurityException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    

    2.2自定义注解ValidatePermission

    package com.jykj.demo.annotation;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @Descrption该注解是标签型注解,被此注解标注的方法需要进行权限校验
     */
    @Target(value = ElementType.METHOD)
    @Retention(value = RetentionPolicy.RUNTIME)
    @Documented
    public @interface ValidatePermission {
        /**
         * @Description功能Id的参数索引位置  默认为0,表示功能id在第一个参数的位置上,-1则表示未提供,无法进行校验
         */
        int idx() default 0;
    }
    

    说明: AOP切入的是方法,不是某个控制器请求,所以不能直接返回视图来中断该方法的请求,但可以通过抛异常的方式达到中断方法执行的目的,所以在before通知中,如果通过验证直接return返回继续执行连接点方法,否则抛出一个自定义异常AccessDeniedException来中断连接点方法的执行。该异常的捕获可以通过系统的异常处理器(可以看做控制器)来捕获并跳转到一个视图或者一个请求。这样就达到拦截请求的目的。所以需要配置异常处理器。

    2.3 spring-mvc.xml(异常处理器配置,以及aop配置)

    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
            <!-- <property name="defaultErrorView" value="rediret:/error"></property>   -->
            <property name="exceptionMappings">
                <props>
                    <!--<prop key="com.jykj.demo.exception.AuthorizationException">redirect:/login</prop>-->
                    <prop key="com.jykj.demo.exception.AccessDeniedException">forward:/accessDenied</prop>
                </props>
            </property>
        </bean>
    <bean id="aspectPermission" class="com.jykj.demo.filter.PermissionAspect" />
        <!-- 对带有@ValidatePermission和ResponseBody注解的controller包及其子包所有方法执行功能权限校验  --> 
        <aop:config proxy-target-class="true">  
            <aop:aspect ref="aspectPermission">  
                <aop:pointcut id="pc"  
                    expression="@annotation(com.jykj.demo.annotation.ValidatePermission) 
                    and @annotation(org.springframework.web.bind.annotation.ResponseBody) 
                    and execution(* com.jykj.demo.controller..*.*(..)) " />  
                <aop:before pointcut-ref="pc" method="doBefore"/>  
            </aop:aspect>  
        </aop:config>

    2.5 异常处理器将请求转发到的控制器请求 forward:/accessDenied

    @RequestMapping(value = "/accessDenied",produces = "text/html;charset=UTF-8")
    @ResponseBody
    public String accessDenied(){
        return JSON.toJSONString(new Result(false,"您没有权限对此进行操作!"));
    }

    2.6 请求校验不通过时 由上述的控制器返回 结果本身

    如下所示:

    {"info":"您没有权限对此进行操作!","success":false}
    • 1

    2.7 功能校验service 示例

    /**
         * 校验当前用户在某个模块的某个功能的权限
         * @param functionId
         * @return 空字符串表示 有权限 ,否则是错误信息
         * @throws Exception 
         */
        public String permissionValidate(int functionId){
            Object o =  request.getSession().getAttribute(Helper.SESSION_USER);
            //if(o==null)  throw new AuthorizationException(); 
            SysUser loginUser= (SysUser)o;
            if(loginUser.getUserid() == 1) return "";
            try{
                return mapper.permissionValidate(loginUser.getUserid(),functionId);
            }catch(Exception ex){
                ex.printStackTrace();
                return "数据库操作出现异常!";
            }
        }

    说明: 这里仅仅是对带有@ValidatePermission和@ResponseBody注解的controller包及其子包所有方法进行切入,这样肯定是不够通用的,应该是对带有@ValidatePermission的方法进行切入,在切面类中通过判断该方法是否有@ResponseBody注解来抛出不一样的异常,若带有@ResponseBody注解则抛出上述的异常返回json字符串, 
    否则,应该抛出另一个自定义异常然后将请求重定向到一个合法的视图如error.jsp .

    通过客户端发送 /moduleAccess.do 请求,该请求对应的方法同时具有@ValidatePermission和@ResponseBody,并且有功能Id参数fid,这样AOP可以切入该方法,执行doBefore通知,通过功能参数fid,对它结合用户id进行权限校验,若校验通过直接返回,程序继续执行,否则抛出自定义异常AccessDeniedException,该异常由系统捕获(需要配置异常处理器)并发出请求 forward:/accessDenied ,然后对应的控制器 /accessDenied 处理该请求返回一个包含校验失败信息的json给客户端。这样发送 /moduleAccess.do 请求,如果校验失败,转发到了/accessDenied请求,否则正常执行。绕了这么一个大圈子才实现它。

     
     
  • 相关阅读:
    SPOJ 4110 Fast Maximum Flow (最大流模板)
    CF 277E Binary Tree on Plane (拆点 + 费用流) (KM也可做)
    POJ 2289 Jamie's Contact Groups (二分+最大流)
    POJ 1723 SOLDIERS (中位数)
    TRI 解题报告
    POJ 2455 Secret Milking Machine (二分 + 最大流)
    POJ 2112 Optimal Milking (二分 + 最大流)
    POJ 2195 Going Home / HDU 1533(最小费用最大流模板)
    POJ 2135 Farm Tour (最小费用最大流模板)
    运维自动化
  • 原文地址:https://www.cnblogs.com/yhtboke/p/5749063.html
Copyright © 2011-2022 走看看