在 Spring Security 中,基于不同版本实现 RBAC(基于角色的访问控制)功能有一些不同的方式。RBAC 的基本原理是:定义用户、角色和权限的关系,并控制不同用户对资源的访问。
Spring Security 不同版本的实现主要在配置方法、注解支持、以及代码风格上有所不同。以下是不同版本的 RBAC 配置方式和实现思路。
https://docs.spring.io/spring-security/reference/index.html
Spring Security 5.x 及以前的版本
在 Spring Security 5.x 版本中,RBAC 的实现一般是通过 WebSecurityConfigurerAdapter
类来配置 URL 访问规则、角色、权限等。方法级的权限控制通过 @EnableGlobalMethodSecurity
注解开启。
1. 数据库表设计(通用)
RBAC 设计的数据库表包括 用户(User)、角色(Role) 和 权限(Permission)。基本表结构可以参考如下设计:
-- 用户表
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,
role_name VARCHAR(50) UNIQUE NOT NULL
);
-- 用户角色关联表(多对多)
CREATE TABLE user_roles (
user_id BIGINT,
role_id BIGINT,
PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
);
-- 权限表
CREATE TABLE permissions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
permission_name VARCHAR(50) UNIQUE NOT NULL
);
-- 角色权限关联表(多对多)
CREATE TABLE role_permissions (
role_id BIGINT,
permission_id BIGINT,
PRIMARY KEY (role_id, permission_id),
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE
);
2. WebSecurityConfigurerAdapter
配置类
WebSecurityConfigurerAdapter
是 Spring Security 5 及以前版本的主要配置方式。以下是配置方法:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法级别的权限控制
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
public SecurityConfig(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN") // 配置 URL 权限
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated() // 其他请求都需要认证
.and()
.formLogin()
.permitAll()
.and()
.logout()
.permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
3. 方法级别权限控制
启用方法级别的安全注解后,可以在服务层或控制器层方法上直接使用 @PreAuthorize
或 @Secured
注解来控制权限。
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@PreAuthorize("hasRole('ADMIN')")
public void adminMethod() {
// 只有 ADMIN 角色的用户可以访问
}
@PreAuthorize("hasAnyRole('ADMIN', 'USER')")
public void userOrAdminMethod() {
// 只有 ADMIN 或 USER 角色的用户可以访问
}
}
Spring Security 6.x 及以后的版本
Spring Security 6.x 中,WebSecurityConfigurerAdapter
已被弃用,推荐使用 SecurityFilterChain
配置和 @EnableMethodSecurity
注解。
1. SecurityFilterChain
配置类
不再继承 WebSecurityConfigurerAdapter
,而是使用 SecurityFilterChain
来配置安全规则。同时,方法级别的权限控制启用方式从 @EnableGlobalMethodSecurity
改为 @EnableMethodSecurity
。
package com.pbn.demo013.config;
import com.alibaba.fastjson2.JSON;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import java.util.HashMap;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity//Spring项目总需要添加此注解,SpringBoot项目中不需要
public class WebSecurityConfig {
/*过滤链*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//authorizeRequests():开启授权保护
//anyRequest():对所有请求开启授权保护
//authenticated():
// 3用户-角色-资源 开启授权保护
/* http.authorizeRequests(
authorize -> authorize
//具有管理员角色的用户可以访问/user/**
.requestMatchers("/user/**").hasRole("ADMIN")
//对所有请求开启授权保护
.anyRequest()
//已认证的请求会被自动授权
.authenticated()
);*/
// 硬编码 2 用户-权限-资源
// http.authorizeRequests(
// authorize -> authorize
// //具有USER_LIST权限的用户可以访问/user/list
// .requestMatchers("/user/list").hasAuthority("USER_LIST")
// //具有USER_ADD权限的用户可以访问/user/add
// .requestMatchers("/user/add").hasAuthority("USER_ADD")
// //对所有请求开启授权保护
// .anyRequest()
// //已认证的请求会被自动授权
// .authenticated()
// );
// ----1 默认权限
http.authorizeRequests(authorize -> authorize.anyRequest().authenticated());
// .formLogin(withDefaults()); //表单授权方式 //3个
// 登录认证
http.formLogin(form -> {
form
.loginPage("/login").permitAll() //登录页面无需授权即可访问
.usernameParameter("username") //自定义表单用户名参数,默认是username
.passwordParameter("password") //自定义表单密码参数,默认是password
.failureUrl("/login?error") //登录失败的返回地址
.successHandler(new MyAuthenticationSuccessHandler()) //认证成功时的处理
.failureHandler(new MyAuthenticationFailureHandler()); //认证失败时的处理
}); //使用表单授权方式
// .httpBasic(withDefaults());//基本授权方式
//关闭csrf攻击防御
http.csrf((csrf) -> {
csrf.disable();
});
//登出
http.logout(logout -> {
logout.logoutSuccessHandler(new MyLogoutSuccessHandler()); //注销成功时的处理
});
//跨域处理
http.cors(withDefaults());
//会话管理 单个并发
http.sessionManagement(session -> {
session
.maximumSessions(1)
.expiredSessionStrategy(new MySessionInformationExpiredStrategy());
});
// //错误处理
// http.exceptionHandling(exception -> {
// exception.authenticationEntryPoint(new MyAuthenticationEntryPoint());//请求未认证的接口
// });
//错误处理 授权异常 request处理
http.exceptionHandling(exception -> {
exception.authenticationEntryPoint(new MyAuthenticationEntryPoint());//请求未认证的接口
// 匿名内部类,lambda
// exception.accessDeniedHandler(new MyAccessDeniedHandler());
exception.accessDeniedHandler((request, response, e) -> { //请求未授权的接口
//创建结果对象
HashMap result = new HashMap();
result.put("code", -1);
result.put("message", "没有权限");
//转换成json字符串
String json = JSON.toJSONString(result);
//返回响应
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
});
});
return http.build();
}
}
DBUserDetailsManager
是 Spring Security 中的一个实现类
它实现了 UserDetailsManager
和 UserDetailsPasswordService
接口。这两个接口分别提供了对用户数据的操作方法和密码处理的方法。DBUserDetailsManager
为开发者提供了一套完整的用户管理解决方案,包括用户账户的创建、删除、更新以及密码管理等功能,特别适用于需要基于数据库进行用户认证和授权的应用场景。通过实现 UserDetailsManager
和 UserDetailsPasswordService
接口,DBUserDetailsManager
能够很好地集成到 Spring Security 的安全框架中,提供强大的用户安全管理能力。
UserDetailsManager
UserDetailsManager
接口定义了一系列用于管理用户账户的方法,包括创建、删除、更新用户信息等。这些方法允许应用程序动态地管理用户账户,而不需要重启应用或手动修改数据库。具体来说,UserDetailsManager
提供了以下功能:
- createUser(UserDetails user): 创建一个新的用户。
- updateUser(UserDetails user): 更新现有的用户信息。
- deleteUser(String username): 删除指定用户名的用户。
- changePassword(String oldPassword, String newPassword): 允许用户更改自己的密码。
- userExists(String username): 检查指定的用户名是否存在。
UserDetailsPasswordService
UserDetailsPasswordService
接口提供了一个方法,用于处理用户密码的更新。当用户尝试更改密码时,可以通过这个服务来更新数据库中的密码。这个接口的主要方法是:
-
UserDetails updatePassword(UserDetails user, String newPassword): 更新用户的密码,并返回更新后的
UserDetails
对象。
DBUserDetailsManager
DBUserDetailsManager
是 UserDetailsManager
和 UserDetailsPasswordService
的实现类,它提供了具体的实现,以便于在基于数据库的用户管理系统中使用。DBUserDetailsManager
通常会与一个 JdbcUserDetailsManager
或其他数据访问对象(DAO)一起工作,以实际地与数据库交互,执行用户管理操作。
-
数据源配置:
DBUserDetailsManager
需要配置一个数据源(DataSource),以便连接到数据库。 - 用户表结构:它假设数据库中存在一个符合Spring Security要求的用户表结构,包括用户名、密码、权限等字段。
-
密码编码:
DBUserDetailsManager
支持使用不同的密码编码器(如BCryptPasswordEncoder),以安全的方式存储和验证用户密码。
package com.pbn.demo013.config;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.pbn.demo013.entity.User;
import com.pbn.demo013.mapper.UserMapper;
import jakarta.annotation.Resource;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;
@Component
public class DBUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {
@Resource
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", username);
User user = userMapper.selectOne(queryWrapper);
if (user == null) {
throw new UsernameNotFoundException(username);
} else {
// Collection<GrantedAuthority> authorities = new ArrayList<>();
// Collection<GrantedAuthority> authorities = new ArrayList<>();
// authorities.add(()->"USER_LIST");
// authorities.add(()->"USER_ADD");
// return new org.springframework.security.core.userdetails.User(
// user.getUsername(),
// user.getPassword(),
// user.getEnabled(),
// true, //用户账号是否过期
// true, //用户凭证是否过期
// true, //用户是否未被锁定
// authorities); //权限列表
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword())
// .roles("ADMIN")
// .roles("ADMIN1")
.authorities("USER_ADD", "USER_UPDATE") //roles,authorities相互覆盖
.build();
}
}
@Override
public UserDetails updatePassword(UserDetails user, String newPassword) {
return null;
}
@Override
public void createUser(UserDetails userDetails) {
User user = new User();
user.setUsername(userDetails.getUsername());
user.setPassword(userDetails.getPassword());
user.setEnabled(true);
userMapper.insert(user);
}
@Override
public void updateUser(UserDetails user) {
}
@Override
public void deleteUser(String username) {
}
@Override
public void changePassword(String oldPassword, String newPassword) {
}
@Override
public boolean userExists(String username) {
return false;
}
}
2. 使用 @PreAuthorize
或 @RolesAllowed
进行方法级别控制
在 Spring Security 6 中,@EnableMethodSecurity
启用后,仍可以使用 @PreAuthorize
等注解来实现方法级别的权限控制:
package com.pbn.demo013.controller;
import com.pbn.demo013.entity.User;
import com.pbn.demo013.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/user")
@EnableMethodSecurity //方法的授权
public class UserController {
@Resource
public UserService userService;
@PreAuthorize("hasRole('ADMIN') and authentication.name == 'admim'")
@GetMapping("/list")
public List<User> getList(){
return userService.list();
}
@PreAuthorize("hasAuthority('USER_ADD')")
@PostMapping("/add")
public void add(@RequestBody User user){
userService.saveUserDetails(user);
}
}
3. 配置基于角色的访问控制逻辑
基于角色的访问控制逻辑可以通过 SecurityFilterChain
中的 authorizeHttpRequests()
方法来配置,以匹配不同的 URL 路径。新的 requestMatchers()
方法替代了以前的 antMatchers()
方法,以更好地支持多种路径匹配。
小结
功能 | Spring Security 5.x | Spring Security 6.x |
---|---|---|
配置类 | WebSecurityConfigurerAdapter |
SecurityFilterChain |
方法级权限控制注解启用 | @EnableGlobalMethodSecurity |
@EnableMethodSecurity |
URL 匹配方法 | antMatchers() |