Java项目接口权限校验的灵活实现

引言

在Java Web开发中,接口权限校验是保护系统资源安全的关键机制。本文将介绍一种灵活、可配置的接口权限校验方案,通过注解驱动和拦截器实现,既能保证安全性,又能灵活控制哪些接口需要校验。

设计思路

实现方案的核心设计要点:

  1. 注解驱动:使用自定义注解标记需要权限校验的接口
  2. 拦截器机制:在请求处理前进行统一权限校验
  3. 灵活配置:支持方法级和类级配置,可动态调整
  4. 权限缓存:提高权限验证效率

在这里插入图片描述

实现步骤

1. 定义权限校验注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PermissionCheck {
    
    /**
     * 权限标识,支持多个权限(满足任意一个即可)
     */
    String[] value() default {};
    
    /**
     * 是否开启权限校验(默认开启)
     */
    boolean enabled() default true;
    
    /**
     * 逻辑关系:AND-需满足所有权限,OR-满足任意权限
     */
    Logical logical() default Logical.OR;
}

public enum Logical {
    AND, OR
}

2. 实现权限校验拦截器

@Component
public class PermissionInterceptor implements HandlerInterceptor {
    
    @Autowired
    private PermissionService permissionService;
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                             HttpServletResponse response, 
                             Object handler) throws Exception {
        
        // 如果不是Controller方法直接放行
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        
        // 获取方法上的注解
        PermissionCheck methodAnnotation = method.getAnnotation(PermissionCheck.class);
        // 获取类上的注解
        PermissionCheck classAnnotation = method.getDeclaringClass()
                                              .getAnnotation(PermissionCheck.class);
        
        // 1. 如果方法上明确关闭权限校验
        if (methodAnnotation != null && !methodAnnotation.enabled()) {
            return true;
        }
        
        // 2. 如果类上关闭权限校验且方法未指定
        if (classAnnotation != null && !classAnnotation.enabled() 
            && (methodAnnotation == null || methodAnnotation.enabled())) {
            return true;
        }
        
        // 3. 获取当前用户权限
        User currentUser = getCurrentUser(request);
        if (currentUser == null) {
            response.sendError(HttpStatus.UNAUTHORIZED.value(), "用户未登录");
            return false;
        }
        
        // 4. 获取需要的权限
        Set<String> requiredPermissions = getRequiredPermissions(
            methodAnnotation, classAnnotation);
        
        // 5. 无需权限校验
        if (requiredPermissions.isEmpty()) {
            return true;
        }
        
        // 6. 检查权限
        boolean hasPermission = checkPermissions(
            currentUser, requiredPermissions, 
            methodAnnotation != null ? methodAnnotation.logical() : 
                     (classAnnotation != null ? classAnnotation.logical() : Logical.OR)
        );
        
        if (!hasPermission) {
            response.sendError(HttpStatus.FORBIDDEN.value(), "权限不足");
            return false;
        }
        
        return true;
    }
    
    private Set<String> getRequiredPermissions(PermissionCheck methodAnnotation, 
                                               PermissionCheck classAnnotation) {
        Set<String> permissions = new HashSet<>();
        
        // 方法注解优先
        if (methodAnnotation != null && methodAnnotation.value().length > 0) {
            Collections.addAll(permissions, methodAnnotation.value());
            return permissions;
        }
        
        // 类注解
        if (classAnnotation != null && classAnnotation.value().length > 0) {
            Collections.addAll(permissions, classAnnotation.value());
        }
        
        return permissions;
    }
    
    private boolean checkPermissions(User user, Set<String> requiredPermissions, Logical logical) {
        // 获取用户实际权限(可从缓存中获取)
        Set<String> userPermissions = permissionService.getUserPermissions(user.getId());
        
        if (logical == Logical.AND) {
            return userPermissions.containsAll(requiredPermissions);
        } else {
            return requiredPermissions.stream()
                    .anyMatch(userPermissions::contains);
        }
    }
}

3. 注册拦截器

@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Autowired
    private PermissionInterceptor permissionInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(permissionInterceptor)
                .addPathPatterns("/api/**")  // 拦截API路径
                .excludePathPatterns("/api/public/**"); // 排除公共接口
    }
}

4. 权限服务实现

@Service
public class PermissionServiceImpl implements PermissionService {
    
    @Autowired
    private PermissionMapper permissionMapper;
    
    @Autowired
    private CacheManager cacheManager;
    
    @Override
    public Set<String> getUserPermissions(Long userId) {
        // 从缓存获取
        Cache cache = cacheManager.getCache("userPermissions");
        Cache.ValueWrapper wrapper = cache.get(userId);
        if (wrapper != null) {
            return (Set<String>) wrapper.get();
        }
        
        // 从数据库查询
        Set<String> permissions = permissionMapper.selectPermissionsByUserId(userId);
        // 放入缓存
        cache.put(userId, permissions);
        return permissions;
    }
}

使用示例

1. 类级别权限控制

@RestController
@RequestMapping("/users")
@PermissionCheck(value = {"USER_MANAGE"}, logical = Logical.AND)
public class UserController {
    
    // 需要USER_MANAGE权限
    @GetMapping
    public List<User> listUsers() {
        // ...
    }
    
    // 不需要权限校验(覆盖类级别设置)
    @PermissionCheck(enabled = false)
    @GetMapping("/public")
    public User getPublicUser() {
        // ...
    }
}

2. 方法级别权限控制

@RestController
@RequestMapping("/products")
public class ProductController {
    
    // 需要PRODUCT_READ权限
    @PermissionCheck("PRODUCT_READ")
    @GetMapping
    public List<Product> listProducts() {
        // ...
    }
    
    // 需要同时具备PRODUCT_WRITE和PRODUCT_MANAGE权限
    @PermissionCheck(value = {"PRODUCT_WRITE", "PRODUCT_MANAGE"}, logical = Logical.AND)
    @PostMapping
    public Product createProduct(@RequestBody Product product) {
        // ...
    }
}

3. 公共接口(无需权限)

@RestController
@RequestMapping("/public")
public class PublicController {
    
    // 无需任何权限校验
    @GetMapping("/info")
    public SystemInfo getSystemInfo() {
        // ...
    }
}

进阶优化

1. 动态权限配置

可将权限配置存储在数据库中,实现动态管理:

CREATE TABLE api_permission (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    api_path VARCHAR(255) NOT NULL,
    http_method VARCHAR(10) NOT NULL,
    permission_code VARCHAR(50) NOT NULL,
    enabled BOOLEAN DEFAULT true,
    UNIQUE KEY uni_api_method (api_path, http_method)
);

在拦截器中增加数据库权限检查逻辑:

// 在PermissionInterceptor中增加
private boolean checkDynamicPermission(String path, String method) {
    List<String> requiredPermissions = permissionService
        .getPermissionsForApi(path, method);
    
    if (requiredPermissions.isEmpty()) {
        return true; // 无配置表示不需要权限
    }
    
    // 检查用户权限...
}

2. 权限缓存策略

使用Redis缓存用户权限数据,提高性能:

@Configuration
public class RedisConfig {
    
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(30))  // 30分钟过期
                .disableCachingNullValues();
        
        return RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
    }
}

@Service
public class RedisPermissionService implements PermissionService {
    
    @Autowired
    private RedisTemplate<String, Set<String>> redisTemplate;
    
    @Override
    public Set<String> getUserPermissions(Long userId) {
        String key = "user:permissions:" + userId;
        Set<String> permissions = redisTemplate.opsForSet().members(key);
        
        if (permissions == null || permissions.isEmpty()) {
            // 从数据库加载
            permissions = permissionMapper.selectPermissionsByUserId(userId);
            if (!permissions.isEmpty()) {
                redisTemplate.opsForSet().add(key, permissions.toArray(new String[0]));
                redisTemplate.expire(key, 30, TimeUnit.MINUTES);
            }
        }
        
        return permissions;
    }
}

方案对比

实现方式优点缺点适用场景
拦截器+注解灵活、非侵入、配置简单无法动态修改中小型项目
动态数据库配置可动态调整、集中管理增加数据库访问、实现复杂大型复杂系统
AOP实现解耦彻底、可复用性高配置复杂、学习曲线陡峭需要高度解耦的系统

总结

本文介绍了一种基于注解和拦截器的接口权限校验方案,具有以下特点:

  1. 灵活配置:通过注解控制每个接口的权限要求
  2. 非侵入式:不影响业务逻辑代码
  3. 层次分明:支持类级别和方法级别的权限控制
  4. 易于扩展:可结合数据库实现动态权限管理
  5. 性能优化:通过缓存减少权限查询开销

通过合理的权限校验实现,可以大大提高系统的安全性,同时保持代码的整洁和可维护性。根据项目需求,可以选择合适的实现方式和优化策略。

提示:在实际项目中,建议结合Spring Security或Shiro等安全框架,可以更全面地解决认证授权问题。本文方案适用于需要轻量级权限控制的场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zc-code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值