VIP权限系统划分简单实现

概述

本系统通过拦截器和切面机制实现了VIP权限控制,只有VIP用户才能访问被特定注解标记的API接口。主要通过Spring拦截器、自定义注解,AOP和服务层检验实现。

运行原理:在 controller 上面➕VIP 注解那么就走拦截器,就说明这一整个功能都需要此 vip权限,如果是这个一个接口下面的具体的方法需要 vip 权限,那么就会走 aop 切面的这个类

核心组件

1. VIP权限拦截器 (VipInterceptor)

拦截器实现了Spring的HandlerInterceptor接口,主要负责在请求到达控制器方法前进行VIP权限校验。

package com.yixueji.interceptor;

import com.yixueji.annotation.VipRequired;
import com.yixueji.context.BaseContext;
import com.yixueji.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class VipInterceptor implements HandlerInterceptor {

    @Autowired
    private UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 检查当前请求是否需要VIP权限
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            // 判断方法上是否有VipRequired注解
            if (handlerMethod.hasMethodAnnotation(VipRequired.class)) {
                Long userId = getCurrentUserId();
                // 检查当前用户是否为VIP
                if (!userService.isVip(userId)) {
                    response.sendError(HttpStatus.FORBIDDEN.value(), "VIP功能,请升级会员");
                    return false;
                }
            }
        }
        return true;
    }
    
    /**
     * 获取当前登录用户的ID
     * 从BaseContext中获取当前线程存储的用户ID
     * @return 当前用户ID
     */
    private Long getCurrentUserId() {
        return BaseContext.getCurrentId();
    }
}

2. VIP注解 (@VipRequired)

自定义注解,用于标记需要VIP权限的控制器方法。

package com.yixueji.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface VipRequired {
}

3. 切面 (AOP)

用于拦截标注了@VipRequired注解的Service方法

package com.yixueji.aspect;

import com.yixueji.constant.MessageConstant;
import com.yixueji.context.BaseContext;
import com.yixueji.exception.VipRequiredException;
import com.yixueji.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


/**
 * VIP功能校验切面
 * 用于拦截标注了@VipRequired注解的Service方法
 */
@Aspect
@Component
@Slf4j
public class VipRequiredAspect {

    @Autowired
    private UserService userService;

    /**
     * 定义切点:拦截service包下所有标注了@VipRequired注解的方法
     */
    @Pointcut("execution(* com.yixueji.service..*.*(..))" +
            "&& @annotation(com.yixueji.annotation.VipRequired)")
    public void vipRequiredPointcut() {}

    /**
     * 前置通知,在方法执行前进行VIP校验
     * 
     * @param joinPoint 切点
     */
    @Before("vipRequiredPointcut()")
    public void beforeVipCheck(JoinPoint joinPoint) {
        log.info("AOP拦截到需要VIP权限的方法: {}", joinPoint.getSignature().getName());
        
        // 获取当前登录用户ID
        Long userId = BaseContext.getCurrentId();
        
        // 检查当前用户是否为VIP
        if (!userService.isVip(userId)) {
            // 如果不是VIP用户,抛出自定义异常
            throw new VipRequiredException(MessageConstant.VIP_REQUIRED);
        }
        
        log.info("VIP校验通过,用户ID: {}", userId);
    }
} 

4. 用户服务 (UserService)

提供isVip方法用于检查用户是否具有VIP权限。

工作流程

  1. 请求进入系统后,首先经过拦截器处理
  2. 拦截器检查控制器方法是否标记了@VipRequired注解
  3. 如有标记,从BaseContext获取当前用户ID
  4. 调用userService.isVip(userId)判断用户VIP状态
  5. 如非VIP用户,返回403禁止访问错误
  6. 如是VIP用户,允许请求继续

使用方法

  1. 配置拦截器:在Spring配置中注册VipInterceptor

  2. 标记VIP方法:在需要VIP权限的控制器方法上添加@VipRequired注解,也可以加在 service 层中具体的方法中

    @GetMapping("/premium-content")
    @VipRequired
    public ResponseEntity<?> getPremiumContent() {
        // 只有VIP用户可访问的内容
    }
    
  3. 用户身份传递:确保在处理请求前,通过BaseContext设置当前用户ID

技术优势

  1. 低侵入性:通过注解方式标记,不影响业务逻辑
  2. 可扩展性:可轻松扩展更多权限检查逻辑
  3. 集中管理:权限检查逻辑集中在拦截器中,便于维护
  4. 线程安全:使用ThreadLocal存储用户信息,确保线程安全

注意事项

  1. 确保BaseContext中的用户ID在请求开始时正确设置
  2. VIP状态检查逻辑可根据业务需求在UserService中扩展

使用拦截器和切面结合的原因

系统实现了两种不同层面的VIP权限控制机制,分别是拦截器(Interceptor)和切面(Aspect),这算是一种"防御纵深"的安全设计策略:

  1. 拦截器(VipInterceptor)

    • 作用于Web层,拦截HTTP请求
    • 在控制器方法执行前进行权限检查
    • 直接返回HTTP 403错误响应给客户端
    • 适用于标记了@VipRequired注解的Controller方法
    • 与前端交互直接相关,负责拦截外部请求
  2. 切面(VipRequiredAspect)

    • 作用于服务层,拦截内部方法调用
    • 专门针对service包下的方法
    • 通过抛出VipRequiredException异常来拒绝访问
    • 保护核心业务逻辑,不依赖控制器层的拦截
    • 可以捕获内部系统中的越权调用

采用双重实现的主要原因:

  1. 多层防御:即使绕过了拦截器,服务层仍有权限检查
  2. 不同应用场景:有些VIP功能可能没有对应的API但在内部被调用
  3. 解耦安全与业务:将权限控制和业务逻辑分离
  4. 更精细的控制:不同层次可以有不同的权限控制策略
  5. 系统健壮性:一种机制失效时,另一种仍能保护系统

这种双重实现确保了VIP权限检查的可靠性和全面性