权限控制 RBAC
一、RBAC 模型
基本概念
┌─────────────────────────────────────────────────────────────────┐
│ RBAC 模型 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ │
│ │ 用户 │ │
│ └─────┬────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ │ 用户-角色 │ │
│ │ (M:N) │ │
│ └─────┬─────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ │ 角色 │ │
│ └─────┬─────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ │ 角色-权限 │ │
│ │ (M:N) │ │
│ └─────┬─────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ │ 权限 │ │
│ └───────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘数据库设计
sql
-- 用户表
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(100) NOT NULL,
enabled BOOLEAN DEFAULT TRUE
);
-- 角色表
CREATE TABLE roles (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) UNIQUE NOT NULL,
description VARCHAR(100)
);
-- 权限表
CREATE TABLE permissions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) UNIQUE NOT NULL,
description VARCHAR(100),
url VARCHAR(200), -- 资源路径
method VARCHAR(10) -- 请求方法
);
-- 用户-角色关联表
CREATE TABLE user_roles (
user_id BIGINT,
role_id BIGINT,
PRIMARY KEY (user_id, role_id)
);
-- 角色-权限关联表
CREATE TABLE role_permissions (
role_id BIGINT,
permission_id BIGINT,
PRIMARY KEY (role_id, permission_id)
);二、Spring Security 集成
UserDetailsService 实现
java
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
List<Role> roles = roleRepository.findByUserId(user.getId());
List<GrantedAuthority> authorities = roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
.collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
user.getEnabled(),
true, true, true,
authorities
);
}
}动态权限配置
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private CustomUserDetailsService userDetailsService;
@Autowired
private PermissionService permissionService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/register", "/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginProcessingUrl("/login")
.successHandler((req, res, auth) -> {
res.setContentType("application/json");
res.getWriter().write("{\"code\":0,\"message\":\"登录成功\"}");
})
);
return http.build();
}
}三、方法级权限
@PreAuthorize
java
@Service
public class UserService {
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
@PreAuthorize("hasAuthority('USER_READ') or hasRole('ADMIN')")
public User getUser(Long id) {
return userRepository.findById(id);
}
// 复杂表达式
@PreAuthorize("#userId == authentication.principal.id or hasRole('ADMIN')")
public void updateUser(Long userId, User user) {
// ...
}
}自定义权限表达式
java
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Autowired
private PermissionService permissionService;
@Override
public boolean hasPermission(Authentication auth, Object targetDomainObject, Object permission) {
if (auth == null) return false;
String username = auth.getName();
String permissionName = (String) permission;
return permissionService.hasPermission(username, targetDomainObject, permissionName);
}
@Override
public boolean hasPermission(Authentication auth, Serializable targetId,
String targetType, Object permission) {
if (auth == null) return false;
String username = auth.getName();
return permissionService.hasPermission(username, targetType, targetId, permission);
}
}
// 配置
@EnableMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
@Autowired
private CustomPermissionEvaluator permissionEvaluator;
@Bean
public MethodSecurityExpressionHandler expressionHandler() {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
handler.setPermissionEvaluator(permissionEvaluator);
return handler;
}
}四、按钮级权限
前端方案
html
<!-- Thymeleaf -->
<div sec:authorize="hasRole('ADMIN')">
<button>删除</button>
</div>
<!-- Vue -->
<el-button v-if="$hasPermission('USER_DELETE')">删除</el-button>自定义注解
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
String value();
}
// 切面实现
@Aspect
@Component
public class PermissionAspect {
@Autowired
private PermissionService permissionService;
@Around("@annotation(requirePermission)")
public Object checkPermission(ProceedingJoinPoint joinPoint,
RequirePermission requirePermission) throws Throwable {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (!permissionService.hasPermission(auth.getName(), requirePermission.value())) {
throw new SecurityException("没有权限: " + requirePermission.value());
}
return joinPoint.proceed();
}
}五、面试高频问题
Q1: RBAC 是什么?
基于角色的访问控制(Role-Based Access Control),通过角色连接用户和权限
Q2: hasRole 和 hasAuthority 区别?
hasRole('ADMIN')→hasAuthority('ROLE_ADMIN')(自动加前缀)hasAuthority('USER_READ')→ 直接匹配权限
Q3: 如何动态更新权限?
将权限数据缓存到 Redis,修改后刷新缓存
六、下一章预告
下一章我们将学习 OAuth2 第三方登录:
- OAuth2 流程
- 授权码模式
- 第三方登录集成