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
This commit is contained in:
zihluwang committed 2026-07-21 14:21:13 +08:00
1 parent db5056e8d5
commit c1d893db5f
4 files changed
+4 -111

No files matched your search

@@ -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;
}
}
@@ -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<Void> 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<Void> removeRole(
@PathVariable @Positive(message = "用户ID必须为正数") Long id,
@@ -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();
}
@@ -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<MethodInvocation> {
private static final Logger log = LoggerFactory.getLogger(RoleBasedAuthorizationManager.class);
@Override
public AuthorizationDecision check(
Supplier<Authentication> 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);
}
}