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

 

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

 

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

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

 

@Controller
@RequestMapping("/appDetail.htm")
public class AppDetailController {
    @RequestMapping(method = RequestMethod.GET)
    public String doGet(ModelMap modelMap, HttpServletRequest httpServletRequest) {
        //1. 开发者有效性判断
        Developer developer = developerManageServiceClient
            .getByCardNo(cardNo);
        if (null == developer){
            return ERROR_VM;
        }
        if (DeveloperStatus.VALID != developer.getStatus()  &&  DeveloperStatus.FREEZE != developer.getStatus()) {
            return ERROR_VM;
        }
        //2. 业务操作,此处省略
    }
}

@Controller
@RequestMapping("/appBaseInfoEdit.htm")
public class AppBaseInfoEditController {
    @RequestMapping(method = RequestMethod.POST)
    public String modify(ModelMap modelMap, HttpServletRequest httpServletRequest,  AppBaseInfoForm appBaseInfoForm) {
        //1. 开发者有效性判断
        Developer developer = developerManageServiceClient
            .getByCardNo(cardNo);
        if (null == developer){
            return ERROR_VM;
        }
        if (DeveloperStatus.VALID !=  developer.getStatus()) {
            return ERROR_VM;
        }
        //2. 业务操作,此处省略
    }
}

 

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

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

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

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

 

@Controller
@RequestMapping("/appDetail.htm")
@Permission(permissionTypes = { PermissionEnum.DEVELOPER_VALID })
public class AppDetailController {
    @RequestMapping(method = RequestMethod.GET)
    public String doGet(ModelMap modelMap, HttpServletRequest httpServletRequest) {
        //1. 业务操作,此处省略
    }
}

@Controller
@RequestMapping("/appBaseInfoEdit.htm")
@Permission(permissionTypes = { PermissionEnum.DEVELOPER_VALID, PermissionEnum.DEVELOPER_FREEZE })
public class AppBaseInfoEditController {
    @RequestMapping(method = RequestMethod.POST)
    public String modify(ModelMap modelMap, HttpServletRequest httpServletRequest, AppBaseInfoForm appBaseInfoForm) {
        //1. 业务操作,此处省略
    }
}

 

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

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

    注解实现:

 

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Permission {
    /** 检查项枚举 */
    PermissionEnum[] permissionTypes() default {};
    /** 检查项关系 */
    RelationEnum relation() default RelationEnum.OR;
}

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

 

/**
 * 权限检查拦截器
 * 
 * @author xianwu.zhang
 * @version $Id: PermissionCheckInterceptor.java, v 0.1 2012-10-25 下午07:48:11 xianwu.zhang Exp $
 */
public class PermissionCheckInterceptor extends HandlerInterceptorAdapter {
    /** 权限检查服务 */
    private PermissionCheckProcessor permissionCheckProcessor;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Class<?> clazz = handler.getClass();
        if (clazz.isAnnotationPresent(Permission.class)) {
            Permission permission = (Permission) clazz.getAnnotation(Permission.class);
            return permissionCheckProcessor.process(permission, request,response);
        }
        return true;
    }
}

 

 * 权限检查器
*  @author xianwu.zhang
 * @version $Id: PermissionCheckProcessor.java, v 0.1 2012-11-5 下午05:13:17 xianwu.zhang Exp $
 */
public class PermissionCheckProcessor {
    public boolean process(Permission permission, HttpServletRequest request, HttpServletResponse response) {
        PermissionEnum[] permissionTypes = permission.permissionTypes();
        try {
            String cardNo = OperationContextHolder.getPrincipal().getUserId();
            HttpSession session = request.getSession(false);
	     If(null != session){
               //查询开发者
            	  Developer developer = developerManageServiceClient .getByCardNo(cardNo);
               if (null != developer &&  checkPermission(permissionTypes, permission.relation(),developer .getStatus())) {
                  return true;
               }
}
            sendRedirect(response, ISV_APPLY_URL);
            return false;
        } catch (Exception e) {
            sendRedirect(response, ISV_APPLY_URL);
            return false;
        }
}
//省略
}
   
    private void sendRedirect(HttpServletResponse response, String redirectURI) {
        URIBroker uriBroker = uriBrokerManager.getUriBroker(redirectURI);
        String url = uriBroker.render();
        try {
            response.sendRedirect(url);
        } catch (IOException e) {
            logger.error("转向页面:" + url + "跳转出错:", e);
        }
    }
}

 

     Xml 配置如下:

 

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
	<property name="interceptors">
		<list>
			<ref bean="permissionCheckInterceptor" />
		</list>
	</property>
</bean>

<bean id="permissionCheckInterceptor"
		class="com.xxx.xxx.web.home.interceptor.PermissionCheckInterceptor" />
<bean id="permissionCheckProcessor"
		class="com.xxx.xxx.web.home.interceptor.PermissionCheckProcessor" />

 

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

 

/**
    * 权限检查器
   *  @author xianwu.zhang
    * @version $Id: PermissionCheckProcessor.java, v 0.1 2012-11-5 下午05:13:17 xianwu.zhang Exp $
    */
public class PermissionCheckProcessor {

    public boolean process(Permission permission, HttpServletRequest request,
                           HttpServletResponse response) {
        PermissionEnum[] permissionTypes = permission.permissionTypes();
        try {
            String cardNo = OperationContextHolder.getPrincipal().getUserId();
            HttpSession session = request.getSession(false);
            if (null != session) {
                String developerCardNo = (String) session.getAttribute("developerCardNo");
                if (StringUtil.isNotBlank(cardNo) && StringUtil.equals(cardNo, developerCardNo)) {
                    String status = (String) session.getAttribute("status");
                    if (checkPermission(permissionTypes, permission.relation(), status)) {
                        return true;
                    }
                } else {
                    Developer developer = developerManageServiceClient .getByCardNo(cardNo);
                    if (null != developer
                        && checkPermission(permissionTypes, permission.relation(), developer
                            .getStatus())) {
                        session.setAttribute("status", developer.getStatus());
                        session.setAttribute("developerCardNo ", cardNo);
                        return true;
                    }
                }
            }
            sendRedirect(response, ISV_APPLY_URL);
            return false;
        } catch (Exception e) {
            sendRedirect(response, ISV_APPLY_URL);
            return false;
        }
    }

 

本文为原创,转载请注明出处

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,针对您的问题,Spring Boot提供了一种基于拦截器的用户权限控制方式。具体实现步骤如下: 1. 创建一个拦截器类,实现HandlerInterceptor接口,并重写preHandle方法,在该方法中进行用户权限校验。 2. 在Spring Boot的配置类中,通过addInterceptors方法将拦截器注册到Spring Boot中。 3. 在需要进行用户权限控制的Controller方法上添加@PreAuthorize注解,指定需要的权限。 下面是一个简单的示例代码: ```java public class UserInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 获取用户信息 User user = (User) request.getSession().getAttribute("user"); if (user == null) { // 用户未登录,跳转到登录页面 response.sendRedirect("/login"); return false; } // 校验用户权限 if (!user.hasPermission(request.getRequestURI())) { // 用户没有访问该资源的权限,返回403错误 response.setStatus(HttpStatus.FORBIDDEN.value()); return false; } return true; } } @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private UserInterceptor userInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(userInterceptor).addPathPatterns("/**"); } } @RestController @RequestMapping("/api") public class UserController { @GetMapping("/users") @PreAuthorize("hasRole('ADMIN')") public List<User> getUsers() { // 获取用户列表 } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值