介绍
您是否曾经构建过应用程序,然后突然意识到需要以更精细的方式管理用户访问权限?也许您已经硬编码了一些管理检查或在整个代码库中分散了权限逻辑。相信我,我经历过这种情况,维护起来并不好玩。
这就是基于角色的访问控制 (RBAC)
的作用所在。这是一种基于用户角色管理用户权限的标准化方法,可让您的应用程序更安全、更易于维护。在这篇文章中,我将引导您使用 MongoDB 在 Spring Boot 应用程序中实现 RBAC。我们将介绍从设置项目到保护您的端点的所有内容。
先决条件
在深入研究之前,请确保您已进行以下设置:
- Java 开发工具包 (JDK) 17 或更高版本:Spring Boot 3.x 需要 Java 17+。
- Spring Boot 3.x
- Spring Security 6.x
- MongoDB 6.x
您还需要对以下内容有基本的了解:
- Java 编程
- Spring 框架
- MongoDB
设置项目
1. 创建一个新的 Spring Boot 项目
首先,让我们设置我们的 Spring Boot 项目。您可以使用 Spring Initializr 或您最喜欢的 IDE。包括以下依赖项:
- Spring Web
- Spring Security
- Spring Data MongoDB
- Lombok (可选但强烈建议减少样板代码)
2. 更新 pom.xml
确保您的 pom.xml
文件包含必要的依赖项:
xml
<project ...>
<!-- ... other configurations ... -->
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Data MongoDB -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!-- Lombok (Optional) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<!-- ... other dependencies ... -->
</dependencies>
<!-- ... other configurations ... -->
</project>
配置 MongoDB
1. 添加 MongoDB 连接详细信息
我们需要将应用程序连接到 MongoDB。在您的 application.properties
或中 application.yml
,添加以下内容:
properties
spring.data.mongodb.uri=mongodb://localhost:27017/rbac_db
请随意将 rbac_db
替换为您喜欢的数据库名称。
定义实体
现在,让我们定义 RBAC 系统的核心实体:Permission
、Role
和 User
。
1. Permission.java
java
package com.example.rbac.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Data;
@Data
@Document(collection = "permissions")
public class Permission {
@Id
private String id;
private String name;
}
2. Role.java
java
package com.example.rbac.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Data;
import java.util.Set;
@Data
@Document(collection = "roles")
public class Role {
@Id
private String id;
private String name;
private Set<Permission> permissions;
}
3. User.java
java
package com.example.rbac.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Data;
import java.util.Set;
@Data
@Document(collection = "users")
public class User {
@Id
private String id;
private String username;
private String password; // We'll store hashed passwords
private Set<Role> roles;
}
简要说明:始终以哈希格式存储密码。稍后我们将介绍如何对密码进行哈希处理。
创建存储库
存储库是我们的应用程序和数据库之间的桥梁。让我们为我们的实体创建它们
1. UserRepository.java
java
package com.example.rbac.repository;
import org.springframework.data.mongodb.repository.MongoRepository;
import com.example.rbac.model.User;
public interface UserRepository extends MongoRepository<User, String> {
User findByUsername(String username);
}
2. RoleRepository.java
java
package com.example.rbac.repository;
import org.springframework.data.mongodb.repository.MongoRepository;
import com.example.rbac.model.Role;
public interface RoleRepository extends MongoRepository<Role, String> {
Role findByName(String name);
}
3. PermissionRepository.java
java
package com.example.rbac.repository;
import org.springframework.data.mongodb.repository.MongoRepository;
import com.example.rbac.model.Permission;
public interface PermissionRepository extends MongoRepository<Permission, String> {
Permission findByName(String name);
}
实现 UserDetailsService
为了与 Spring Security 集成,我们将实现一个自定义的 UserDetailsService
。
CustomUserDetailsService.java
java
package com.example.rbac.service;
import org.springframework.security.core.userdetails.*;
import org.springframework.stereotype.Service;
import com.example.rbac.model.User;
import com.example.rbac.model.Role;
import com.example.rbac.model.Permission;
import com.example.rbac.repository.UserRepository;
import java.util.*;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
getAuthorities(user.getRoles())
);
}
private Collection<SimpleGrantedAuthority> getAuthorities(Set<Role> roles) {
Set<SimpleGrantedAuthority> authorities = new HashSet<>();
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
for (Permission permission : role.getPermissions()) {
authorities.add(new SimpleGrantedAuthority(permission.getName()));
}
}
return authorities;
}
}
这里发生了什么?
- 我们使用用户名从数据库中获取用户。
- 我们构造一个Spring Security 可以用于身份验证的
UserDetails
对象。 - 我们将角色和权限转换为
GrantedAuthority
的集合。
配置 Spring Security
现在,让我们设置 Spring Security 来使用我们的自定义 UserDetailsService
。
SecurityConfig.java
java
package com.example.rbac.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.*;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import com.example.rbac.service.CustomUserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class SecurityConfig {
private final CustomUserDetailsService userDetailsService;
public SecurityConfig(CustomUserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(authz -> authz
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasRole("USER")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login").permitAll()
)
.logout(logout -> logout.permitAll());
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
return http
.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder())
.and()
.build();
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
要点:
- 禁用 CSRF:为简单起见,我们禁用 CSRF。在生产环境中,请确保正确配置 CSRF 保护。
- 授权规则:
/admin/**
端点需要ADMIN
角色。/user/**
端点需要USER
角色。- 所有其他请求都需要身份验证。
- 表单登录 : 我们在 指定自定义登录页面
/login
。
安全管理密码
安全至关重要,尤其是用户密码。确保在存储密码之前对其进行哈希处理。
UserService.java
java
package com.example.rbac.service;
import org.springframework.stereotype.Service;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import com.example.rbac.model.User;
import com.example.rbac.repository.UserRepository;
@Service
public class UserService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder passwordEncoder;
public UserService(UserRepository userRepository, BCryptPasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
public void saveUser(User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
userRepository.save(user);
}
}
为什么选择 BCrypt?
BCrypt 是一种流行的哈希算法,专为哈希密码而设计。它包含盐以防止彩虹表攻击,并且计算密集型以防止暴力攻击。
定义控制器和端点
是时候设置我们的 REST 控制器来处理传入的请求了。
1. AdminController.java
java
package com.example.rbac.controller;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin")
public class AdminController {
@GetMapping("/dashboard")
public String adminDashboard() {
return "Welcome to the Admin Dashboard!";
}
}
2. UserController.java
java
package com.example.rbac.controller;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/profile")
public String userProfile() {
return "Welcome to your Profile!";
}
}
处理身份验证
我们将创建一个简单的控制器来处理登录请求。
WebController.java
java
package com.example.rbac.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class WebController {
@GetMapping("/login")
public String login() {
return "login"; // This should correspond to a Thymeleaf template
}
}
不要忘记视图!
如果您使用 Thymeleaf,请确保 src/main/resources/templates/
下有一个 login.html
模板。
创建初始数据
为了测试我们的应用程序,让我们预加载一些角色、权限和用户。
DataLoader.java
java
package com.example.rbac.config;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import com.example.rbac.repository.RoleRepository;
import com.example.rbac.repository.PermissionRepository;
import com.example.rbac.repository.UserRepository;
import com.example.rbac.model.Permission;
import com.example.rbac.model.Role;
import com.example.rbac.model.User;
import com.example.rbac.service.UserService;
import java.util.Set;
@Component
public class DataLoader implements CommandLineRunner {
private final RoleRepository roleRepository;
private final PermissionRepository permissionRepository;
private final UserService userService;
public DataLoader(RoleRepository roleRepository, PermissionRepository permissionRepository, UserService userService) {
this.roleRepository = roleRepository;
this.permissionRepository = permissionRepository;
this.userService = userService;
}
@Override
public void run(String... args) throws Exception {
// Create Permissions
Permission readPermission = new Permission();
readPermission.setName("READ_PRIVILEGE");
permissionRepository.save(readPermission);
Permission writePermission = new Permission();
writePermission.setName("WRITE_PRIVILEGE");
permissionRepository.save(writePermission);
// Create Roles
Role adminRole = new Role();
adminRole.setName("ADMIN");
adminRole.setPermissions(Set.of(readPermission, writePermission));
roleRepository.save(adminRole);
Role userRole = new Role();
userRole.setName("USER");
userRole.setPermissions(Set.of(readPermission));
roleRepository.save(userRole);
// Create Users
User adminUser = new User();
adminUser.setUsername("admin");
adminUser.setPassword("admin123"); // Password will be hashed
adminUser.setRoles(Set.of(adminRole));
userService.saveUser(adminUser);
User normalUser = new User();
normalUser.setUsername("user");
normalUser.setPassword("user123"); // Password will be hashed
normalUser.setRoles(Set.of(userRole));
userService.saveUser(normalUser);
}
}
发生了什么?
- 我们创建两个权限:
READ_PRIVILEGE
和WRITE_PRIVILEGE
。 - 我们创建两个角色:
ADMIN
(具有两个权限)和USER
(具有读取权限)。 - 我们创建两个用户:一个管理员用户和一个普通用户。
测试应用程序
让我们确保一切按预期运行。
1. 运行应用程序
启动您的 Spring Boot 应用程序:
bash
mvn spring-boot:run
2. 访问登录页面
导航至 http://localhost:8080/login。您应该会看到您的登录页面。
3. 测试用户身份验证
管理员用户
- 用户名 :
admin
- 密码 :
admin123
登录后,尝试访问:
- http://localhost:8080/admin/dashboard --- 应显示管理仪表板。
- http://localhost:8080/user/profile --- 应显示用户个人资料。
普通用户
- 用户名 :
user
- 密码 :
user123
登录后,尝试访问:
- http://localhost:8080/user/profile --- 应显示用户个人资料。
- http://localhost:8080/admin/dashboard --- 应返回 403 Forbidden 错误。
结论
就这样!我们使用 Spring Boot 和 MongoDB 构建了一个简单但强大的 RBAC 系统。以下是我们所完成工作的简要回顾:
- 设置项目:使用必要的依赖项初始化 Spring Boot 项目。
- 配置 MongoDB:将我们的应用程序连接到 MongoDB 数据库。
- 定义的实体 :创建
User
、Role
和Permission
模型。 - 创建的存储库:设置用于数据访问的存储库。
- 实现
UserDetailsService
:将我们的用户模型与 Spring Security 集成。 - 配置 Spring Security:设置身份验证和授权规则。
- 安全管理密码:使用 BCrypt 对密码进行哈希处理。
- 定义控制器:为不同的角色创建端点。
- 创建初始数据:预加载的角色、权限和用户以供测试。
- 测试应用程序:验证我们的 RBAC 系统是否按预期工作。
下一步:
您可以通过添加更多角色、权限和安全端点来扩展此应用程序。您还可以集成 JWT 进行无状态身份验证或添加前端以与您的 API 交互。
其他最佳实践
虽然我们已经介绍了基础知识,但还有以下一些最佳做法可供考虑:
- 验证 :
@NotNull
使用和等注释@Size
来验证用户输入。 - 异常处理 :用实现全局异常处理
@ControllerAdvice
。 - 日志记录:利用日志框架实现更好的可追溯性。
- 安全标头:配置标头以防止常见的漏洞。
- CORS 配置:如果您有前端应用程序,请适当设置跨域资源共享。