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

news2024/9/27 7:37:22

介绍

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

这就是基于角色的访问控制 (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 {
    @Id
    private 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 {
    @Id
    private 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 {
    @Id
    private String id;
    private String username;
    private String password; // We'll store hashed passwords
    private 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;
    }
    @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

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

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;
    }
    @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_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.coloradmin.cn/o/2169353.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity3D地形系统一口气讲完!谢啦!!☆⌒(*^-゜)v

Unity 3D 地形系统概述 在三维游戏世界中&#xff0c;通常会将丰富多彩的游戏元素融合在一起&#xff0c;比如游戏中起伏的地形、郁郁葱葱的树木、蔚蓝的天空、、凶恶的猛兽等&#xff0c;营造出身临其境的游戏沉浸感&#xff0c;让玩家置身游戏世界&#xff0c;忘记现实。 地…

定时任务上云改造方案

优质博文&#xff1a;IT-BLOG-CN 一、Job单元化 什么Job需要单元化&#xff1a;所有会往单元化DB更新/删除/插入数据的Job都需要进行单元化改造。 什么是单元化DB 【1】指配置为DRC双向复制的DB&#xff1b; 【2】单元化的DB部署在多个Zone&#xff0c;每个Zone内的实例都会…

【含文档】基于Springboot+Vue的高校失物招领平台(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定…

物体实例分割,机器人拾取

物体实例分割是计算机视觉领域的一个关键任务&#xff0c;它旨在从图像中分割出每个独立物体&#xff0c;并且为每个物体实例提供一个独特的标识。这一任务不仅识别出图像中的物体&#xff0c;还能区分出多个同类物体的不同实例&#xff0c;例如在一张桌子上摆放的多个相同的杯…

『功能项目』宠物的攻击巨型化【80】

本章项目成果展示 我们打开上一篇79宠物的召唤跟随的项目&#xff0c; 本章要做的事情是实现在战斗中有几率触发宠物巨型化攻击将怪物击飞的效果 首先在主角预制体中增加隐藏的宠物巨型化 制作巨型化宠物的攻击效果 将该动画控制器放置在隐藏的巨型化宠物的动画控制器上 首先查…

新160个crackme - 065-Eternal Bliss

运行分析 选择验证方式&#xff0c;破解字符串标题提示为vb程序 PE分析 VB程序&#xff0c;32位&#xff0c;无壳 静态分析&动态调试 使用VB Decompiler进行分析&#xff0c;发现Command1_Click_403604为check按钮事件&#xff0c;需要使Me 1 CBool(expression) 将表达…

传输层协议 --- UDP

序言 在之前的文章 Socket 编程 中&#xff0c;我们只是简单的知道了怎么利用 UDP协议 或者是 TCP协议 来发送我们的数据&#xff0c;并且我们还知道 UDP 是不可靠的&#xff0c;TCP 是可靠的。但这是为什么呢&#xff1f;底层的构造和策略决定他们的属性&#xff01;这篇文章中…

从环境部署到开发实战:消息队列 RocketMQ

文章目录 一、消息队列简介1.1 什么是消息队列1.2 常见消息队列对比1.3 RockectMQ 核心概念1.4 RockectMQ 工作机制 &#xff08;★&#xff09; 二、RocketMQ 部署相关2.1 服务器单机部署2.2 管控台页面 三、RocketMQ 的基本使用3.1 入门案例3.2 消息发送方式3.2.1 同步消息3.…

微服务-流量染色

1. 功能目的 通过设置请求头的方式将http请求优先打到指定的服务上&#xff0c;为微服务开发调试工作提供便利 请求报文难模拟&#xff1a;可以直接在测试环境页面上操作&#xff0c;流量直接打到本地IDEA进行debug请求链路较长&#xff1a;本地开发无需启动所有服务&#xf…

前端常见算法题集

很久没练算法了&#xff0c;打算接下来一段时间每天坚持写题和写题解 这是一篇前端常用算法题集&#xff0c;题目从从简到难&#xff0c;编程语言主要为JavaScript&#xff0c;顺便练习和熟记js的各种方法... 目录 字符串类 1.字符串相加 字符串类 下图为js中常用的字符串方…

神经网络介绍及其在Python中的应用(一)

作者简介&#xff1a;热爱数据分析&#xff0c;学习Python、Stata、SPSS等统计语言的小高同学~ 个人主页&#xff1a;小高要坚强的博客 当前专栏&#xff1a;Python之机器学习 本文内容&#xff1a;神经网络介绍及其在Python中的线性回归应用 作者“三要”格言&#xff1a;要坚…

使用python爬取豆瓣网站?如何简单的爬取豆瓣网站?

1.对python爬虫的看法 首先说说我对python的看法&#xff0c;我的专业是大数据&#xff0c;我从事的工作是java开发&#xff0c;但是在工作之余&#xff0c;我对python又很感兴趣&#xff0c;因为我觉得python是一门很好的语言&#xff0c;第一&#xff1a;它可以用来爬取数据…

fmql之字符驱动设备(2)

例行的点灯来喽。 之前是寄存器读写&#xff0c;现在要学习通过设备树点灯。 dtsled.c 寄存器写在reg 把用到的寄存器写在设备树的led节点的reg属性。 其实还是对寄存器的读写。 &#xff08;不推荐&#xff09; 头文件 #include <linux/kernel.h> #include <li…

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-09-26

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-09-26 1. LLMs Still Can’t Plan; Can LRMs? A Preliminary Evaluation of OpenAI’s o1 on PlanBench Authors: Karthik Valmeekam, Kaya Stechly, Subbarao Kambhampati LLMs仍然无法规划&#xff1b;LRMs可以…

【通俗易懂】FFT求解全过程,各参数详细解释

在进行FFT全过程讲解之前&#xff0c;小编先给大家解释一下&#xff0c;在FFT中出现的一些参数名词解释。 &#xff08;1&#xff09;采样频率 Fs Fs 1 / 采样间隔 根据奈奎斯特定理&#xff1a;Fs ≥ 最高频率分量的两倍&#xff0c;这样才能避免混叠 &#xff08;2&…

解决macOS安装redis以后不支持远程链接的问题

参考文档:https://blog.csdn.net/qq_37703224/article/details/142542179?spm1001.2014.3001.5501 安装的时候有个提示, 使用指定配置启动: /opt/homebrew/opt/redis/bin/redis-server /opt/homebrew/etc/redis.conf那么我们可以尝试修改这个配置文件: code /opt/homebrew/…

傅里叶级数在机器人中的应用(动力学参数辨识)

B站首发&#xff01;草履虫都能看懂的【傅里叶变换】讲解&#xff0c;清华大学李永乐老师教你如何理解傅里叶变换&#xff0c;辨清美颜和变声原理&#xff0c;&#xff01;&#xff01;_哔哩哔哩_bilibiliB站首发&#xff01;草履虫都能看懂的【傅里叶变换】讲解&#xff0c;清…

AI 智能体 | 手捏素材选题库 Coze Bot,帮你实现无限输出

做自媒体的同学经常遇到的一个痛点就是无限输出&#xff0c;那怎么才能有源源不断的选题呢&#xff1f;那就是搭建一个选题素材库。 下面就为大家介绍一下基于 Coze Bot 快速搭建素材选题库&#xff0c;希望能让大家才思泉涌。 一、流程拆解 日常素材库积累的过程可以描述为…

eslint-plugin-react的使用中,所出现的react版本警告

记一次使用eslint-plugin-react的警告 Warning: React version not specified in eslint-plugin-react settings. See https://github.com/jsx-eslint/eslint-plugin-react#configuration . 背景 我们在工程化项目中&#xff0c;常常会通过eslint来约束我们代码的一些统一格…

汽车总线之----J1939总线

instruction SAE J1939 是由美国汽车工程协会制定的一种总线通信协议标准&#xff0c;广泛应用于商用车&#xff0c;船舶&#xff0c;农林机械领域中&#xff0c;J1939协议是基于CAN的高层协议&#xff0c;我们来看一下两者之间的关系。在J1939 中&#xff0c;物理层和数据链路…