引言
在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 {@Autowiredprivate PermissionService permissionService;@Overridepublic 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 {@Autowiredprivate PermissionInterceptor permissionInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(permissionInterceptor).addPathPatterns("/api/**") // 攔截API路徑.excludePathPatterns("/api/public/**"); // 排除公共接口}
}
4. 權限服務實現
@Service
public class PermissionServiceImpl implements PermissionService {@Autowiredprivate PermissionMapper permissionMapper;@Autowiredprivate CacheManager cacheManager;@Overridepublic 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權限@GetMappingpublic List<User> listUsers() {// ...}// 不需要權限校驗(覆蓋類級別設置)@PermissionCheck(enabled = false)@GetMapping("/public")public User getPublicUser() {// ...}
}
2. 方法級別權限控制
@RestController
@RequestMapping("/products")
public class ProductController {// 需要PRODUCT_READ權限@PermissionCheck("PRODUCT_READ")@GetMappingpublic List<Product> listProducts() {// ...}// 需要同時具備PRODUCT_WRITE和PRODUCT_MANAGE權限@PermissionCheck(value = {"PRODUCT_WRITE", "PRODUCT_MANAGE"}, logical = Logical.AND)@PostMappingpublic 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 {@Beanpublic 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 {@Autowiredprivate RedisTemplate<String, Set<String>> redisTemplate;@Overridepublic 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等安全框架,可以更全面地解決認證授權問題。本文方案適用于需要輕量級權限控制的場景。