使用 MongoDB 在 Spring Boot 中构建安全的 RBAC 系统

embedded/2024/10/19 1:28:27/

介绍

您是否曾经构建过应用程序,然后突然意识到需要以更精细的方式管理用户访问权限?也许您已经硬编码了一些管理检查或在整个代码库中分散了权限逻辑。相信我,我经历过这种情况,维护起来并不好玩。

这就是基于角色的访问控制 (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 文件包含必要的依赖项:

<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,添加以下内容:

spring.data.mongodb.uri=mongodb://localhost:27017/rbac_db

请随意将 rbac_db 替换为您喜欢的数据库名称。

定义实体

现在,让我们定义 RBAC 系统的核心实体:PermissionRoleUser

1. Permission.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 {@Idprivate String id;private String name;
}
2. Role.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 {@Idprivate String id;private String name;private Set<Permission> permissions;
}
3. User.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 {@Idprivate String id;private String username;private String password; // We'll store hashed passwordsprivate Set<Role> roles;
}

简要说明:始终以哈希格式存储密码。稍后我们将介绍如何对密码进行哈希处理。

创建存储库

存储库是我们的应用程序和数据库之间的桥梁。让我们为我们的实体创建它们

1. UserRepository.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
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
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

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;}@Overridepublic 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

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;}@Beanpublic 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();}@Beanpublic AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {return http.getSharedObject(AuthenticationManagerBuilder.class).userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()).and().build();}@Beanpublic BCryptPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

要点:

  • 禁用 CSRF:为简单起见,我们禁用 CSRF。在生产环境中,请确保正确配置 CSRF 保护。
  • 授权规则
  • /admin/** 端点需要 ADMIN 角色。
  • /user/** 端点需要 USER 角色。
  • 所有其他请求都需要身份验证。
  • 表单登录: 我们在 指定自定义登录页面 /login

安全管理密码

安全至关重要,尤其是用户密码。确保在存储密码之前对其进行哈希处理。

UserService.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
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
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

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

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;}@Overridepublic void run(String... args) throws Exception {// Create PermissionsPermission readPermission = new Permission();readPermission.setName("READ_PRIVILEGE");permissionRepository.save(readPermission);Permission writePermission = new Permission();writePermission.setName("WRITE_PRIVILEGE");permissionRepository.save(writePermission);// Create RolesRole 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 UsersUser adminUser = new User();adminUser.setUsername("admin");adminUser.setPassword("admin123"); // Password will be hashedadminUser.setRoles(Set.of(adminRole));userService.saveUser(adminUser);User normalUser = new User();normalUser.setUsername("user");normalUser.setPassword("user123"); // Password will be hashednormalUser.setRoles(Set.of(userRole));userService.saveUser(normalUser);}
}

发生了什么?

  • 我们创建两个权限:READ_PRIVILEGEWRITE_PRIVILEGE
  • 我们创建两个角色:ADMIN(具有两个权限)和 USER(具有读取权限)。
  • 我们创建两个用户:一个管理员用户和一个普通用户。

测试应用程序

让我们确保一切按预期运行。

1. 运行应用程序

启动您的 Spring Boot 应用程序:

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 数据库。
  • 定义的实体:创建 UserRolePermission 模型。
  • 创建的存储库:设置用于数据访问的存储库。
  • 实现 UserDetailsService:将我们的用户模型与 Spring Security 集成。
  • 配置 Spring Security:设置身份验证和授权规则。
  • 安全管理密码:使用 BCrypt 对密码进行哈希处理。
  • 定义控制器:为不同的角色创建端点。
  • 创建初始数据:预加载的角色、权限和用户以供测试。
  • 测试应用程序:验证我们的 RBAC 系统是否按预期工作。

下一步:

您可以通过添加更多角色、权限和安全端点来扩展此应用程序。您还可以集成 JWT 进行无状态身份验证或添加前端以与您的 API 交互。

其他最佳实践

虽然我们已经介绍了基础知识,但还有以下一些最佳做法可供考虑:

  • 验证@NotNull 使用和等注释 @Size 来验证用户输入。
  • 异常处理:用实现全局异常处理 @ControllerAdvice
  • 日志记录:利用日志框架实现更好的可追溯性。
  • 安全标头:配置标头以防止常见的漏洞。
  • CORS 配置:如果您有前端应用程序,请适当设置跨域资源共享。

文章原地址


http://www.ppmy.cn/embedded/120092.html

相关文章

isEmpty和isBlank的区别

在 Java 中&#xff0c;String 类的 isEmpty() 和 isBlank() 方法有以下区别&#xff1a; isEmpty(): 定义: 检查字符串是否为空。返回值: 如果字符串的长度为 0&#xff0c;则返回 true&#xff1b;否则返回 false。示例:String str1 ""; String str2 "Hello…

什么是IPv6

目前国内的网络正在快速的向IPv6升级中&#xff0c;从网络基础设施如运营商骨干网、城域网&#xff0c;到互联网服务商如各类云服务&#xff0c;以及各类终端设备厂商如手机、电脑、路由器、交换机等。目前运营商提供的IPv6线路主要分为支持前缀授权和不支持前缀授权两种。 说…

开源链动 2+1 模式 S2B2C 商城小程序助力品牌实现先营后销与品效合一

摘要&#xff1a;本文探讨了在当今市场环境下&#xff0c;如何做到先营后销、品效合一。通过研究社区用户喜好&#xff0c;打造适合家庭消费的商品&#xff0c;并结合开源链动 21 模式 S2B2C 商城小程序&#xff0c;实现品牌的精准定位、创新包装以及小规格产品供应&#xff0c…

项目管理专业资质认证ICB 3中关于项目经理素质的标准

项目管理专业资质认证ICB 3中关于项目经理素质的标准&#xff0c;的确很全面&#xff0c;下面摘录之&#xff1a;

leetcode每日一题day20(24.9.30)——座位预约管理系统

思路&#xff1a;由于一直是出最小的编号&#xff0c;并且除此之外只有添加元素的操作&#xff0c;如果使用数组存储&#xff0c;并记录&#xff0c;这样出最小编号时间是O(n)复杂度,释放一个座位则是O(1)在操作出线机会均等的情况下&#xff0c;平均是O(n/2), 但对于这种重复 …

【解密 Kotlin 扩展函数】扩展函数的底层原理(十八)

导读大纲 1.1.1 从 Java 调用扩展函数1.1.2 扩展函数无法重载 1.1.1 从 Java 调用扩展函数 在编译器底层下,扩展函数是一种静态方法,它接受接收器对象作为第一个参数 调用它不涉及创建适配器对象或任何其他运行时开销这使得从 Java 使用扩展函数变得非常简单 调用静态方法并传…

TI DSP TMS320F280025 Note13:CPUtimer定时器原理分析与使用

TMS320F280025 CPUtimer定时器原理分析与使用 ` 文章目录 TMS320F280025 CPUtimer定时器原理分析与使用框图分析定时器中断定时器使用CPUtimers.cCPUtimers.h框图分析 定时器框图如图所示 定时器有一个预分频模块和一个定时/计数模块, 其中预分频模块包括一个 16 位的定时器分…

手机二要素接口如何用C#实现调用

一、什么是手机二要素&#xff1f; 手机二要素又称运营商二要素&#xff0c;运营商二要素核验&#xff0c;实名核验&#xff0c;手机号核验&#xff0c;手机二要素核验&#xff0c;即传入姓名、手机号码&#xff0c;校验此两项是否一致。实时核验&#xff0c;返回校验结果&…