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:
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.filter.TokenAuthenticationFilter;
|
||||||
import com.onixbyte.deltaforceguide.properties.CookieProperties;
|
import com.onixbyte.deltaforceguide.properties.CookieProperties;
|
||||||
import com.onixbyte.deltaforceguide.properties.TokenProperties;
|
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 com.onixbyte.deltaforceguide.security.provider.UsernamePasswordAuthenticationProvider;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
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.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.ProviderManager;
|
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.method.configuration.EnableMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
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.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import com.onixbyte.deltaforceguide.exeption.BizException;
|
import com.onixbyte.deltaforceguide.exeption.BizException;
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
||||||
import org.springframework.web.cors.CorsConfigurationSource;
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
@@ -117,21 +109,4 @@ public class SecurityConfig {
|
|||||||
.withIssuer(tokenProperties.issuer())
|
.withIssuer(tokenProperties.issuer())
|
||||||
.build();
|
.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.domain.dto.ManageRoleRequest;
|
||||||
import com.onixbyte.deltaforceguide.security.annotation.RequiresAuth;
|
import com.onixbyte.deltaforceguide.security.annotation.RequiresAuth;
|
||||||
import com.onixbyte.deltaforceguide.security.annotation.RequiresRole;
|
|
||||||
import com.onixbyte.deltaforceguide.service.UserService;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import jakarta.validation.constraints.Positive;
|
import jakarta.validation.constraints.Positive;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
@@ -37,7 +36,7 @@ public class AdminController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Operation(description = "为用户分配角色(仅超级管理员可操作)")
|
@Operation(description = "为用户分配角色(仅超级管理员可操作)")
|
||||||
@RequiresRole(Role.ROLE_SUPER_USER)
|
@PreAuthorize("hasRole('SUPER_USER')")
|
||||||
@PutMapping("/users/{id}/roles")
|
@PutMapping("/users/{id}/roles")
|
||||||
public ResponseEntity<Void> assignRole(
|
public ResponseEntity<Void> assignRole(
|
||||||
@PathVariable @Positive(message = "用户ID必须为正数") Long id,
|
@PathVariable @Positive(message = "用户ID必须为正数") Long id,
|
||||||
@@ -48,7 +47,7 @@ public class AdminController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Operation(description = "移除用户角色(仅超级管理员可操作)")
|
@Operation(description = "移除用户角色(仅超级管理员可操作)")
|
||||||
@RequiresRole(Role.ROLE_SUPER_USER)
|
@PreAuthorize("hasRole('SUPER_USER')")
|
||||||
@DeleteMapping("/users/{id}/roles/{role}")
|
@DeleteMapping("/users/{id}/roles/{role}")
|
||||||
public ResponseEntity<Void> removeRole(
|
public ResponseEntity<Void> removeRole(
|
||||||
@PathVariable @Positive(message = "用户ID必须为正数") Long id,
|
@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();
|
|
||||||
}
|
|
||||||
-57
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user