轻松掌握!如何在 Spring Boot 3.3 中实现基于角色的访问控制
在现代 Web 应用程序中,安全性是一个不可或缺的部分。无论是保护敏感数据,还是根据用户角色分配特定权限,角色基于的访问控制(RBAC, Role-Based Access Control) 是实现权限管理的关键模式。RBAC 通过为用户分配特定的角色,进而控制他们可以访问的资源。相比于传统的权限控制,RBAC 提供了更灵活、更易于维护的权限管理机制。
在 Spring Boot 3.3 中,Spring Security 为我们提供了强大且灵活的工具来实现 RBAC。通过配置不同的角色,我们可以实现对特定页面或功能的访问控制,确保只有符合条件的用户才能执行特定操作。
运行效果:
若想获取项目完整代码以及其他文章的项目源码,且在代码编写时遇到问题需要咨询交流,欢迎加入下方的知识星球。
本文的目标 是通过代码示例,帮助您快速掌握如何在 Spring Boot 3.3 中实现基于角色的访问控制,并结合前后端实现自定义的登录页。我们将逐步展示如何配置自定义的登录页面、如何使用 Spring Security 实现动态的角色管理,最终结合前端的 Thymeleaf 模板、jQuery 和 Bootstrap 进行完整实现。
项目环境搭建
pom.xml 配置
首先,配置项目的 Maven 依赖,引入 Spring Security、Thymeleaf、Lombok 等必要的库。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.icoderoad</groupId>
<artifactId>rbac-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rbac-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml 配置文件
我们可以在 application.yml
中配置默认的用户角色,并且可以根据实际需求扩展更多的角色与权限。
server:
port: 8080
spring:
security:
user:
name: admin
password: admin123
roles: ADMIN
myapp:
security:
roles:
admin: ADMIN
user: USER
读取配置类
通过 @ConfigurationProperties
注解,我们可以将配置文件中的角色信息动态加载到 Java 类中,避免硬编码,保证代码的灵活性和可维护性。
package com.icoderoad.rbac.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "myapp.security.roles")
@Data
public class SecurityProperties {
private String admin;
private String user;
}
自定义登录页配置
Spring Security 默认提供了一个简单的登录页面,但在实际应用中,我们通常需要自定义登录页面以提供更友好的用户体验。
在 Spring Boot 3.3 中,自定义登录页面非常方便,只需通过 SecurityConfig
类进行相应配置即可。下面我们将详细介绍如何配置自定义登录页面。
创建自定义登录页面
首先,我们在 src/main/resources/templates
目录下创建一个 login.html
页面,作为我们的自定义登录页。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div class="container mt-5">
<h2>登录</h2>
<form id="loginForm" action="/perform_login" method="post">
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input type="text" class="form-control" id="username" name="username" placeholder="输入用户名">
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input type="password" class="form-control" id="password" name="password" placeholder="输入密码">
</div>
<button type="submit" class="btn btn-primary">登录</button>
</form>
</div>
</body>
</html>
这个页面将通过 Spring Security 的默认登录机制提交表单信息。
在 SecurityConfig 中启用自定义登录页
通过 Spring Security 的 HttpSecurity
配置,我们可以非常轻松地设置自定义的登录页面。
package com.icoderoad.rbac.config;
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;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final SecurityProperties securityProperties;
public SecurityConfig(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(authorizeRequests ->
authorizeRequests
.requestMatchers("/favicon.ico").permitAll()
.requestMatchers("/admin/**").hasRole(securityProperties.getAdmin())
.requestMatchers("/user/**").hasRole(securityProperties.getUser())
.anyRequest().authenticated()
).
formLogin(formLogin -> formLogin
.loginPage("/login") // 自定义登录页面路径
.loginProcessingUrl("/perform_login") // 登录表单提交的路径
.defaultSuccessUrl("/home", true) // 登录成功后的跳转
.failureUrl("/login?error=true") // 登录失败后的跳转
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/perform_logout") // 自定义登出路径
.logoutSuccessUrl("/login?logout=true") // 登出成功后的跳转
.deleteCookies("JSESSIONID") // 清理Cookies
)
.csrf().disable() // 根据需要禁用 CSRF
.build();
}
@Bean
public UserDetailsService userDetailsService() {
var admin = User.withUsername("admin")
.password("{noop}admin123")
.roles(securityProperties.getAdmin())
.build();
var user = User.withUsername("user")
.password("{noop}user123")
.roles(securityProperties.getUser())
.build();
return new InMemoryUserDetailsManager(admin, user);
}
}
formLogin().loginPage("/login")
配置表明 /login
为自定义的登录页面路径。同时,我们通过 permitAll()
允许任何用户访问登录页面,而无需身份验证。
后端实现角色访问控制
在控制器中,我们可以根据用户角色定义不同的访问路径。通过简单的 GetMapping
注解,我们将特定路径分配给特定角色。
package com.icoderoad.rbac.controller;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class RoleController {
@RequestMapping("/home")
public String handleLogin() {
// 获取当前用户的认证信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 检查用户是否拥有 ADMIN 角色
if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_ADMIN"))) {
return "redirect:/admin"; // 如果用户是管理员,重定向到管理员页面
}
// 检查用户是否拥有 USER 角色
if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_USER"))) {
return "redirect:/user"; // 如果用户是普通用户,重定向到用户页面
}
// 如果没有匹配的角色,默认重定向到主页
return "redirect:/login";
}
// 自定义登录页面
@GetMapping("/login")
public ModelAndView login() {
return new ModelAndView("login"); // 渲染登录页面
}
@GetMapping("/admin")
public String adminPage() {
return "admin";
}
@GetMapping("/user")
public String userPage() {
return "user";
}
}
前端交互实现
我们在控制器中定义不同角色的访问路径,并结合 Thymeleaf 实现前端的展示。
admin.html
:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Admin 页面</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">管理系统</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="切换导航">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/admin">首页</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/user">用户管理</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/logout">登出</a>
</li>
</ul>
</div>
</nav>
<div class="container mt-5">
<h1>欢迎, 管理员!</h1>
<p>这是管理员页面,您可以在这里管理用户和系统设置。</p>
<div class="card mt-4">
<div class="card-header">
用户列表
</div>
<div class="card-body">
<table class="table">
<thead>
<tr>
<th>用户名</th>
<th>角色</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>admin</td>
<td>管理员</td>
<td><button class="btn btn-danger">删除</button></td>
</tr>
<tr>
<td>user</td>
<td>普通用户</td>
<td><button class="btn btn-danger">删除</button></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.0.7/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>
user.html
:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>User 页面</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">用户中心</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="切换导航">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/user">首页</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin">返回管理员</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/logout">登出</a>
</li>
</ul>
</div>
</nav>
<div class="container mt-5">
<h1>欢迎, 用户!</h1>
<p>这是用户页面,您可以查看个人信息和设置。</p>
<div class="card mt-4">
<div class="card-header">
个人信息
</div>
<div class="card-body">
<h5 class="card-title">用户名: user</h5>
<p class="card-text">这里是用户的个人信息描述。</p>
<a href="#" class="btn btn-primary">编辑信息</a>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.0.7/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>
总结
通过 Spring Boot 3.3,我们可以轻松实现基于角色的访问控制。本文展示了如何通过配置自定义登录页面、Spring Security 配置类、角色访问控制和前后端交互来构建一个完整的 RBAC 系统。
在实际项目中,通过 @ConfigurationProperties
动态加载角色配置可以使系统更加灵活。结合自定义的登录页面和权限控制,能够为用户提供更加个性化且安全的访问体验。这种灵活的设计不仅能够提升安全性,还能有效提升系统的可维护性,使得开发人员可以根据不同的角色要求快速调整权限策略,适应业务需求的变化。