From c1d893db5f74fc303ee0b334a69c54bc6ff48973 Mon Sep 17 00:00:00 2001 From: zihluwang Date: Tue, 21 Jul 2026 14:21:13 +0800 Subject: [PATCH] refactor(auth): simplify role enforcement with built-in @PreAuthorize - remove custom RequiresRole annotation and RoleBasedAuthorizationManager - replace @RequiresRole with @PreAuthorize("hasRole('SUPER_USER')") in AdminController - remove requiresRoleAdvisor bean from SecurityConfig --- .../config/SecurityConfig.java | 27 +-------- .../controller/AdminController.java | 7 +-- .../security/annotation/RequiresRole.java | 24 -------- .../RoleBasedAuthorizationManager.java | 57 ------------------- 4 files changed, 4 insertions(+), 111 deletions(-) delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/security/annotation/RequiresRole.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/security/authorisation/RoleBasedAuthorizationManager.java diff --git a/src/main/java/com/onixbyte/deltaforceguide/config/SecurityConfig.java b/src/main/java/com/onixbyte/deltaforceguide/config/SecurityConfig.java index fc0b81d..6158bd8 100644 --- a/src/main/java/com/onixbyte/deltaforceguide/config/SecurityConfig.java +++ b/src/main/java/com/onixbyte/deltaforceguide/config/SecurityConfig.java @@ -6,22 +6,15 @@ import com.auth0.jwt.algorithms.Algorithm; import com.onixbyte.deltaforceguide.filter.TokenAuthenticationFilter; import com.onixbyte.deltaforceguide.properties.CookieProperties; import com.onixbyte.deltaforceguide.properties.TokenProperties; -import com.onixbyte.deltaforceguide.security.annotation.RequiresRole; -import com.onixbyte.deltaforceguide.security.authorisation.RoleBasedAuthorizationManager; import com.onixbyte.deltaforceguide.security.provider.UsernamePasswordAuthenticationProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.aop.Advisor; -import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Role; +import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.ProviderManager; -import org.springframework.security.authorization.method.AuthorizationInterceptorsOrder; -import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -30,7 +23,6 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import com.onixbyte.deltaforceguide.exeption.BizException; -import org.springframework.http.HttpStatus; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.web.cors.CorsConfigurationSource; @@ -117,21 +109,4 @@ public class SecurityConfig { .withIssuer(tokenProperties.issuer()) .build(); } - - /** - * Registers the {@link RequiresRole} annotation as a method-security interceptor. - * Methods and classes annotated with {@code @RequiresRole} are checked by the - * {@link RoleBasedAuthorizationManager}. - * - * @param manager the role-based authorisation manager - * @return the advisor that intercepts {@code @RequiresRole}-annotated methods - */ - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - static Advisor requiresRoleAdvisor(RoleBasedAuthorizationManager manager) { - var pointcut = new AnnotationMatchingPointcut(null, RequiresRole.class, true); - var interceptor = new AuthorizationManagerBeforeMethodInterceptor(pointcut, manager); - interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder() + 10); - return interceptor; - } } diff --git a/src/main/java/com/onixbyte/deltaforceguide/controller/AdminController.java b/src/main/java/com/onixbyte/deltaforceguide/controller/AdminController.java index fd65341..a8fe338 100644 --- a/src/main/java/com/onixbyte/deltaforceguide/controller/AdminController.java +++ b/src/main/java/com/onixbyte/deltaforceguide/controller/AdminController.java @@ -2,14 +2,13 @@ package com.onixbyte.deltaforceguide.controller; import com.onixbyte.deltaforceguide.domain.dto.ManageRoleRequest; import com.onixbyte.deltaforceguide.security.annotation.RequiresAuth; -import com.onixbyte.deltaforceguide.security.annotation.RequiresRole; import com.onixbyte.deltaforceguide.service.UserService; -import com.onixbyte.deltaforceguide.shared.Role; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.Positive; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -37,7 +36,7 @@ public class AdminController { } @Operation(description = "为用户分配角色(仅超级管理员可操作)") - @RequiresRole(Role.ROLE_SUPER_USER) + @PreAuthorize("hasRole('SUPER_USER')") @PutMapping("/users/{id}/roles") public ResponseEntity assignRole( @PathVariable @Positive(message = "用户ID必须为正数") Long id, @@ -48,7 +47,7 @@ public class AdminController { } @Operation(description = "移除用户角色(仅超级管理员可操作)") - @RequiresRole(Role.ROLE_SUPER_USER) + @PreAuthorize("hasRole('SUPER_USER')") @DeleteMapping("/users/{id}/roles/{role}") public ResponseEntity removeRole( @PathVariable @Positive(message = "用户ID必须为正数") Long id, diff --git a/src/main/java/com/onixbyte/deltaforceguide/security/annotation/RequiresRole.java b/src/main/java/com/onixbyte/deltaforceguide/security/annotation/RequiresRole.java deleted file mode 100644 index ae3e71c..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/security/annotation/RequiresRole.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.onixbyte.deltaforceguide.security.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation for protecting controller methods or classes with role-based access control. - * When applied, the current authenticated user must possess at least one of the specified roles. - * - * @author zihluwang - */ -@Target({ElementType.METHOD, ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface RequiresRole { - - /** - * The required role names. The user must possess at least one of these. - * - * @return array of role name strings - */ - String[] value(); -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/security/authorisation/RoleBasedAuthorizationManager.java b/src/main/java/com/onixbyte/deltaforceguide/security/authorisation/RoleBasedAuthorizationManager.java deleted file mode 100644 index 90f95ee..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/security/authorisation/RoleBasedAuthorizationManager.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.onixbyte.deltaforceguide.security.authorisation; - -import com.onixbyte.deltaforceguide.security.annotation.RequiresRole; -import org.aopalliance.intercept.MethodInvocation; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.stereotype.Component; - -import java.util.Set; -import java.util.function.Supplier; - -/** - * Spring Security {@link AuthorizationManager} that enforces {@link RequiresRole} annotations. - * Checks that the authenticated user possesses at least one of the required roles. - * - * @author zihluwang - */ -@Component -public class RoleBasedAuthorizationManager implements AuthorizationManager { - - private static final Logger log = LoggerFactory.getLogger(RoleBasedAuthorizationManager.class); - - @Override - public AuthorizationDecision check( - Supplier authenticationSupplier, - MethodInvocation invocation - ) { - var auth = authenticationSupplier.get(); - if (auth == null || !auth.isAuthenticated()) { - log.debug("Access denied: no authenticated user"); - return new AuthorizationDecision(false); - } - - var annotation = invocation.getMethod().getAnnotation(RequiresRole.class); - if (annotation == null) { - annotation = invocation.getMethod().getDeclaringClass() - .getAnnotation(RequiresRole.class); - } - if (annotation == null) { - return new AuthorizationDecision(true); - } - - var requiredRoles = Set.of(annotation.value()); - var hasRole = auth.getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .anyMatch(requiredRoles::contains); - - if (!hasRole) { - log.debug("Access denied: user lacks required roles {}", requiredRoles); - } - return new AuthorizationDecision(hasRole); - } -}