分享

基于拦截器和注解实现页面的访问权限控制

 WindySky 2017-11-22


 

          在 web 系统中,经常需要对每个页面的访问进行权限控制。譬如,要进入 xx 公司的开放 平台, isv 需要注册成为开发者,开发者的状态有审核中、有效、冻结、拒绝、删除等状态,然后根据不同的状态,开发者可以访问不同的页面。只有有效或冻结状态可以访问只读功能的页面(即该页面的访问不会造成后台数据的变化),只有有效状态可以访问具有写功能的页面。

      如何实现该访问控制的需求呢?最直观的做法就是:在每个页面对应的 controller 里,都去调用查询开发者的服务,然后判断开发者的状态。

 

Java代码  收藏代码
  1. @Controller  
  2. @RequestMapping("/appDetail.htm")  
  3. public class AppDetailController {  
  4.     @RequestMapping(method = RequestMethod.GET)  
  5.     public String doGet(ModelMap modelMap, HttpServletRequest httpServletRequest) {  
  6.         //1. 开发者有效性判断  
  7.         Developer developer = developerManageServiceClient  
  8.             .getByCardNo(cardNo);  
  9.         if (null == developer){  
  10.             return ERROR_VM;  
  11.         }  
  12.         if (DeveloperStatus.VALID != developer.getStatus()  &&  DeveloperStatus.FREEZE != developer.getStatus()) {  
  13.             return ERROR_VM;  
  14.         }  
  15.         //2. 业务操作,此处省略  
  16.     }  
  17. }  
  18.   
  19. @Controller  
  20. @RequestMapping("/appBaseInfoEdit.htm")  
  21. public class AppBaseInfoEditController {  
  22.     @RequestMapping(method = RequestMethod.POST)  
  23.     public String modify(ModelMap modelMap, HttpServletRequest httpServletRequest,  AppBaseInfoForm appBaseInfoForm) {  
  24.         //1. 开发者有效性判断  
  25.         Developer developer = developerManageServiceClient  
  26.             .getByCardNo(cardNo);  
  27.         if (null == developer){  
  28.             return ERROR_VM;  
  29.         }  
  30.         if (DeveloperStatus.VALID !=  developer.getStatus()) {  
  31.             return ERROR_VM;  
  32.         }  
  33.         //2. 业务操作,此处省略  
  34.     }  
  35. }  

 

   appDetail.htm 对应的页面需要开发者的状态为有效或者冻结, appBaseInfoEdit.htm 对应的页面需要开发者的状态为有效。

采用这种方式有以下缺点:

  1. 多个 controller 里实现同样的代码,造成代码冗余;
  2. 对于每个 controller 的主体功能来说,对开发者状态的检查是一个横切关注点,将这种关注点掺和在主功能里,会使得主体业务逻辑不清晰。

      所以,可以基于AOP 将这种横切关注点以拦截器的方式实现,但存在的一个问题是,拦截器如何知道某个页面的访问对开发者状态的要求呢?可以基于注解实现。譬如:

 

Java代码  收藏代码
  1. @Controller  
  2. @RequestMapping("/appDetail.htm")  
  3. @Permission(permissionTypes = { PermissionEnum.DEVELOPER_VALID })  
  4. public class AppDetailController {  
  5.     @RequestMapping(method = RequestMethod.GET)  
  6.     public String doGet(ModelMap modelMap, HttpServletRequest httpServletRequest) {  
  7.         //1. 业务操作,此处省略  
  8.     }  
  9. }  
  10.   
  11. @Controller  
  12. @RequestMapping("/appBaseInfoEdit.htm")  
  13. @Permission(permissionTypes = { PermissionEnum.DEVELOPER_VALID, PermissionEnum.DEVELOPER_FREEZE })  
  14. public class AppBaseInfoEditController {  
  15.     @RequestMapping(method = RequestMethod.POST)  
  16.     public String modify(ModelMap modelMap, HttpServletRequest httpServletRequest, AppBaseInfoForm appBaseInfoForm) {  
  17.         //1. 业务操作,此处省略  
  18.     }  
  19. }  

 

  @Permission(permissionTypes = { PermissionEnum.DEVELOPER_VALID }) ,表示开发者的状态必须是有效; @Permission(permissionTypes = { PermissionEnum.DEVELOPER_VALID, PermissionEnum.DEVELOPER_FREEZE }) ,表示开发者的状态必须是有效或者冻结。这样,每个controller 的主体业务逻辑就清晰了。

    下面分析一下注解和拦截器是如何实现的:

    注解实现:

 

Java代码  收藏代码
  1. @Target(ElementType.TYPE)  
  2. @Retention(RetentionPolicy.RUNTIME)  
  3. public @interface Permission {  
  4.     /** 检查项枚举 */  
  5.     PermissionEnum[] permissionTypes() default {};  
  6.     /** 检查项关系 */  
  7.     RelationEnum relation() default RelationEnum.OR;  
  8. }  

         RelationEnum 该枚举表示各检查项( permissionTypes )之间的关系, OR 表示至少需要满足其中一个检查项, AND 表示需要满足所有检查项

 

Java代码  收藏代码
  1. /** 
  2.  * 权限检查拦截器 
  3.  *  
  4.  * @author xianwu.zhang 
  5.  * @version $Id: PermissionCheckInterceptor.java, v 0.1 2012-10-25 下午07:48:11 xianwu.zhang Exp $ 
  6.  */  
  7. public class PermissionCheckInterceptor extends HandlerInterceptorAdapter {  
  8.     /** 权限检查服务 */  
  9.     private PermissionCheckProcessor permissionCheckProcessor;  
  10.     @Override  
  11.     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
  12.         Class<?> clazz = handler.getClass();  
  13.         if (clazz.isAnnotationPresent(Permission.class)) {  
  14.             Permission permission = (Permission) clazz.getAnnotation(Permission.class);  
  15.             return permissionCheckProcessor.process(permission, request,response);  
  16.         }  
  17.         return true;  
  18.     }  
  19. }  

 

Java代码  收藏代码
  1.  * 权限检查器  
  2. *  @author xianwu.zhang  
  3.  * @version $Id: PermissionCheckProcessor.java, v 0.1 2012-11-5 下午05:13:17 xianwu.zhang Exp $  
  4.  */  
  5. public class PermissionCheckProcessor {  
  6.     public boolean process(Permission permission, HttpServletRequest request, HttpServletResponse response) {  
  7.         PermissionEnum[] permissionTypes = permission.permissionTypes();  
  8.         try {  
  9.             String cardNo = OperationContextHolder.getPrincipal().getUserId();  
  10.             HttpSession session = request.getSession(false);  
  11.          If(null != session){  
  12.                //查询开发者  
  13.                   Developer developer = developerManageServiceClient .getByCardNo(cardNo);  
  14.                if (null != developer &&  checkPermission(permissionTypes, permission.relation(),developer .getStatus())) {  
  15.                   return true;  
  16.                }  
  17. }  
  18.             sendRedirect(response, ISV_APPLY_URL);  
  19.             return false;  
  20.         } catch (Exception e) {  
  21.             sendRedirect(response, ISV_APPLY_URL);  
  22.             return false;  
  23.         }  
  24. }  
  25. //省略  
  26. }  
  27.      
  28.     private void sendRedirect(HttpServletResponse response, String redirectURI) {  
  29.         URIBroker uriBroker = uriBrokerManager.getUriBroker(redirectURI);  
  30.         String url = uriBroker.render();  
  31.         try {  
  32.             response.sendRedirect(url);  
  33.         } catch (IOException e) {  
  34.             logger.error("转向页面:" + url + "跳转出错:", e);  
  35.         }  
  36.     }  
  37. }  

 

     Xml 配置如下:

 

Xml代码  收藏代码
  1. <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">  
  2.     <property name="interceptors">  
  3.         <list>  
  4.             <ref bean="permissionCheckInterceptor" />  
  5.         </list>  
  6.     </property>  
  7. </bean>  
  8.   
  9. <bean id="permissionCheckInterceptor"  
  10.         class="com.xxx.xxx.web.home.interceptor.PermissionCheckInterceptor" />  
  11. <bean id="permissionCheckProcessor"  
  12.         class="com.xxx.xxx.web.home.interceptor.PermissionCheckProcessor" />  

 

        还有一个小细节可以优化一下,现在是每访问一个页面,都会经过这个拦截器,拦截器里面都有一次 webservice 调用以查询开发者信息(开发者 cardNo 和开发者状态)。但真实场景中,99.99% 的情况是,用户在同一个 session 下完成所有的业务,即访问的所有页面都具有同一个sessison ,因此可以在开发者第一次访问页面时,通过 webservice 调用查询到开发者信息,如果权限校验通过,则将开发者 cardNo 和开发者状态放在 session 里,那么在 session 未失效前, 开发者再次访问其他页面时,可以在拦截器里先判断目前登陆的用户卡号是否与 session 里存储的cardNo 相同,如果相同,则就不需要再调用 webserivce 了,可以直接取 session 里存储的开发者状态来进行权限校验,这样就减少了大量的不必要的 webservice 调用。

 

Java代码  收藏代码
  1. /** 
  2.     * 权限检查器 
  3.    *  @author xianwu.zhang 
  4.     * @version $Id: PermissionCheckProcessor.java, v 0.1 2012-11-5 下午05:13:17 xianwu.zhang Exp $ 
  5.     */  
  6. public class PermissionCheckProcessor {  
  7.   
  8.     public boolean process(Permission permission, HttpServletRequest request,  
  9.                            HttpServletResponse response) {  
  10.         PermissionEnum[] permissionTypes = permission.permissionTypes();  
  11.         try {  
  12.             String cardNo = OperationContextHolder.getPrincipal().getUserId();  
  13.             HttpSession session = request.getSession(false);  
  14.             if (null != session) {  
  15.                 String developerCardNo = (String) session.getAttribute("developerCardNo");  
  16.                 if (StringUtil.isNotBlank(cardNo) && StringUtil.equals(cardNo, developerCardNo)) {  
  17.                     String status = (String) session.getAttribute("status");  
  18.                     if (checkPermission(permissionTypes, permission.relation(), status)) {  
  19.                         return true;  
  20.                     }  
  21.                 } else {  
  22.                     Developer developer = developerManageServiceClient .getByCardNo(cardNo);  
  23.                     if (null != developer  
  24.                         && checkPermission(permissionTypes, permission.relation(), developer  
  25.                             .getStatus())) {  
  26.                         session.setAttribute("status", developer.getStatus());  
  27.                         session.setAttribute("developerCardNo ", cardNo);  
  28.                         return true;  
  29.                     }  
  30.                 }  
  31.             }  
  32.             sendRedirect(response, ISV_APPLY_URL);  
  33.             return false;  
  34.         } catch (Exception e) {  
  35.             sendRedirect(response, ISV_APPLY_URL);  
  36.             return false;  
  37.         }  
  38.     }  

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多