引言
在Java Web开发中,接口权限校验是保护系统资源安全的关键机制。本文将介绍一种灵活、可配置的接口权限校验方案,通过注解驱动和拦截器实现,既能保证安全性,又能灵活控制哪些接口需要校验。
设计思路
实现方案的核心设计要点:
- 注解驱动:使用自定义注解标记需要权限校验的接口
- 拦截器机制:在请求处理前进行统一权限校验
- 灵活配置:支持方法级和类级配置,可动态调整
- 权限缓存:提高权限验证效率
实现步骤
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实现 | 解耦彻底、可复用性高 | 配置复杂、学习曲线陡峭 | 需要高度解耦的系统 |
总结
本文介绍了一种基于注解和拦截器的接口权限校验方案,具有以下特点:
- 灵活配置:通过注解控制每个接口的权限要求
- 非侵入式:不影响业务逻辑代码
- 层次分明:支持类级别和方法级别的权限控制
- 易于扩展:可结合数据库实现动态权限管理
- 性能优化:通过缓存减少权限查询开销
通过合理的权限校验实现,可以大大提高系统的安全性,同时保持代码的整洁和可维护性。根据项目需求,可以选择合适的实现方式和优化策略。
提示:在实际项目中,建议结合Spring Security或Shiro等安全框架,可以更全面地解决认证授权问题。本文方案适用于需要轻量级权限控制的场景。