Spring Security中的 @PreAuthorize 注解使用方法和示例代码
作者:小猿、
概述
在 Spring Security 框架中,@PreAuthorize注解是实现方法级权限控制的重要工具。它提供了灵活而强大的方式来保护应用程序中的方法,确保只有具备特定权限的用户才能访问。本文将详细介绍@PreAuthorize注解的使用方法、应用场景和示例代码。
什么是 @PreAuthorize 注解
@PreAuthorize是 Spring Security 提供的一个方法级安全注解,用于在方法执行前进行权限检查。它可以基于表达式来定义访问规则,只有当表达式计算结果为true时,方法才会被执行;否则将拒绝访问并抛出AccessDeniedException。
与传统的 URL 级别的安全控制相比,@PreAuthorize提供了更细粒度的权限控制,能够直接作用于服务层或控制器层的方法。
启用 @PreAuthorize 注解
要使用@PreAuthorize注解,首先需要在 Spring 配置类中启用全局方法安全:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
// 配置内容
}Spring Boot 3.4.3 中的配置方式
Spring Boot 3.x 中,@PreAuthorize的启用方式与之前版本不同,主要变化是使用@EnableMethodSecurity替代了旧的@EnableGlobalMethodSecurity。
基础配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
@EnableMethodSecurity(prePostEnabled = true) // 启用@PreAuthorize支持
public class SecurityConfig {
// 密码编码器
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 配置用户信息(示例使用内存用户)
@Bean
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.withUsername("admin")
.password(passwordEncoder().encode("admin123"))
.roles("ADMIN")
.build(),
User.withUsername("user")
.password(passwordEncoder().encode("user123"))
.roles("USER")
.build(),
User.withUsername("editor")
.password(passwordEncoder().encode("editor123"))
.roles("EDITOR")
.build()
);
}
}注意:Spring Security 6.x 默认不再自动添加 "ROLE_" 前缀,
hasRole('ADMIN')实际检查的是 "ADMIN" 权限,而非旧版本的 "ROLE_ADMIN"。
常用表达式语法
@PreAuthorize注解的值是一个 SpEL 表达式,常用表达式包括:
hasRole('ADMIN'):检查用户是否具有指定角色hasAnyRole('ADMIN', 'USER'):检查用户是否具有任意指定角色hasAuthority('DOCUMENT_DELETE'):检查用户是否具有指定权限hasAnyAuthority('CREATE', 'UPDATE'):检查用户是否具有任意指定权限principal:获取当前用户的主体对象authentication:获取当前用户的认证对象isAuthenticated():检查用户是否已认证permitAll():允许所有用户访问denyAll():拒绝所有用户访问#parameter:引用方法参数(如#id引用方法中的 id 参数)@beanName.method(arguments):调用 Spring 管理的 Bean 的方法
实际应用场景与示例
1. 基础访问控制
在控制器层使用@PreAuthorize控制 API 访问权限:
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ApiController {
// 公开接口,所有人可访问
@GetMapping("/public")
@PreAuthorize("permitAll()")
public String publicResource() {
return "This is a public resource";
}
// 需认证后访问
@GetMapping("/protected")
@PreAuthorize("isAuthenticated()")
public String protectedResource() {
return "This is a protected resource";
}
// 仅管理员可访问
@GetMapping("/admin")
@PreAuthorize("hasRole('ADMIN')")
public String adminResource() {
return "This is an admin resource";
}
// 管理员或编辑可访问
@GetMapping("/editor")
@PreAuthorize("hasAnyRole('ADMIN', 'EDITOR')")
public String editorResource() {
return "This is an editor resource";
}
}2. 服务层方法权限控制
在服务层对业务方法进行权限控制:
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
@Service
public class DocumentService {
// 需要文档创建权限
@PreAuthorize("hasAuthority('DOCUMENT_CREATE')")
public String createDocument(String content) {
// 业务逻辑:创建文档
return "Document created with content: " + content;
}
// 需要文档删除权限或管理员角色
@PreAuthorize("hasAuthority('DOCUMENT_DELETE') or hasRole('ADMIN')")
public void deleteDocument(Long documentId) {
// 业务逻辑:删除文档
System.out.println("Deleting document with ID: " + documentId);
}
}3. 基于方法参数的权限验证
确保用户只能操作自己有权限的数据:
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
@Service
public class UserService {
// 确保用户只能更新自己的信息
@PreAuthorize("#userId == authentication.principal.id")
public void updateUserProfile(Long userId, String newName) {
// 业务逻辑:更新用户信息
System.out.println("Updating profile for user " + userId + " to " + newName);
}
// 管理员可以查看任何用户,普通用户只能查看自己
@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
public String getUserDetails(Long userId) {
// 业务逻辑:获取用户详情
return "Details for user " + userId;
}
}4. 调用自定义 Bean 进行复杂权限判断
对于复杂的权限逻辑,可以封装到专门的安全服务中:
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/projects")
public class ProjectController {
// 项目所有者或管理员可以更新项目
@PutMapping("/{projectId}")
@PreAuthorize("@projectSecurityService.isOwner(#projectId, authentication.principal) or hasRole('ADMIN')")
public String updateProject(@PathVariable Long projectId, @RequestBody String projectData) {
return "Project " + projectId + " updated successfully";
}
}对应的自定义安全服务:
import org.springframework.stereotype.Component;
@Component("projectSecurityService")
public class ProjectSecurityService {
/**
* 检查用户是否是项目的所有者
* @param projectId 项目ID
* @param principal 当前用户主体
* @return 是否为所有者
*/
public boolean isOwner(Long projectId, Object principal) {
// 在实际应用中,这里会查询数据库验证项目所有权
// 这里仅作示例:假设用户"user"拥有ID小于100的项目
String username = principal.toString();
return "user".equals(username) && projectId < 100;
}
}处理权限不足的情况
当@PreAuthorize表达式返回false时,Spring Security 会抛出AccessDeniedException。可以通过全局异常处理器统一处理:
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(AccessDeniedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public String handleAccessDenied(AccessDeniedException ex) {
return "Access denied: You don't have permission to perform this action";
}
}注意事项
- 密码安全:示例中使用了 BCrypt 密码编码器,生产环境务必避免使用明文或
{noop}无加密方式。 - 角色与权限区别:
hasRole()和hasAuthority()的区别在于,hasRole()会自动将角色名转换为大写,而hasAuthority()则严格匹配。 - 性能考虑:复杂的 SpEL 表达式可能影响性能,对于高频调用的方法,应优化权限判断逻辑。
- 测试:使用
@PreAuthorize后,需要为不同角色和权限的用户编写充分的测试用例。 - 与 URL 安全控制的配合:
@PreAuthorize通常与 URL 级别的安全控制配合使用,形成多层次的安全防护。
总结
在 Spring Boot 3.4.3 中,@PreAuthorize注解通过@EnableMethodSecurity启用,提供了强大而灵活的方法级权限控制能力。它支持复杂的 SpEL 表达式,能够实现基于角色、权限、用户属性和业务逻辑的细粒度权限判断。
合理使用@PreAuthorize可以显著提高应用程序的安全性,确保敏感操作和数据得到适当的保护。在实际开发中,应根据业务需求选择合适的权限控制策略,并遵循最小权限原则,仅为用户分配必要的权限。
到此这篇关于Spring Security中的 @PreAuthorize 注解使用方法和示例代码的文章就介绍到这了,更多相关Spring Security @PreAuthorize 注解使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
