Spring Security如何为用户示例添加角色详解
作者:allway2 发布时间:2023-09-13 02:31:58
前言
在这个 Spring Security 教程中,我很乐意与您分享如何通过在 Java Web 应用程序中为用户添加角色来实现授权——从数据库设计到实体类;从单元测试到在用户注册中添加默认角色;以 Web 形式更新用户的角色。
技术:Spring Web MVC、Spring Data JPA、Hibernate 框架、Spring Security、Spring Boot Test、JUnit 5、AssertJ、Thymeleaf 和 MySQL 数据库。
基本上,我们需要在数据库中有 3 个表,如下所示:
一个用户可以有一个或多个角色,一个角色可以分配给一个或多个用户,因此用户和角色表之间的实体关系是多对多的。users_roles是实现这种关系的中间表。
1. 用户和角色实体类和存储库的代码
将User 实体类编码如下:
package net.codejava;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.*;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true, length = 45)
private String email;
@Column(nullable = false, length = 64)
private String password;
@Column(name = "first_name", nullable = false, length = 20)
private String firstName;
@Column(name = "last_name", nullable = false, length = 20)
private String lastName;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "users_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
public void addRole(Role role) {
this.roles.add(role);
}
// getters and setters are not shown for brevity
}
并像这样对Role 实体类进行编码:
package net.codejava;
import javax.persistence.*;
@Entity
@Table(name = "roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(nullable = false, length = 45)
private String name;
public Role() { }
public Role(String name) {
this.name = name;
}
public Role(Integer id, String name) {
this.id = id;
this.name = name;
}
public Role(Integer id) {
this.id = id;
}
@Override
public String toString() {
return this.name;
}
// getters and setters are not shown for brevity
}
如您所见,User类有一组角色,但Role类没有任何对 User 的引用。默认情况下, @ManyToMany关系上没有级联操作——这意味着更新User对象不会更改关联的Role对象。
2. 单元测试——创建角色
接下来,让我们编写以下测试类,用于将一些Role对象持久化到数据库中:
package net.codejava;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.annotation.Rollback;
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
@Rollback(false)
public class RoleRepositoryTests {
@Autowired private RoleRepository repo;
@Test
public void testCreateRoles() {
Role user = new Role("User");
Role admin = new Role("Admin");
Role customer = new Role("Customer");
repo.saveAll(List.of(user, admin, customer));
List<Role> listRoles = repo.findAll();
assertThat(listRoles.size()).isEqualTo(3);
}
}
运行testCreateRoles()方法,我们最终将根据 3 个角色将 3 个新行插入到角色表中:用户、管理员和客户。
3. 单元测试——给用户添加角色
要测试向用户添加角色,请使用以下初始代码创建UserRepositoryTests类:
package net.codejava;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.annotation.Rollback;
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
@Rollback(false)
public class UserRepositoryTests {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepo;
@Autowired
private RoleRepository roleRepo;
// test methods go here...
}
以下是第一个测试方法的代码片段,它保留了一个没有任何角色的用户对象:
@Test
public void testCreateUser() {
User user = new User();
user.setEmail("ravikumar@gmail.com");
user.setPassword("ravi2020");
user.setFirstName("Ravi");
user.setLastName("Kumar");
User savedUser = userRepo.save(user);
User existUser = entityManager.find(User.class, savedUser.getId());
assertThat(user.getEmail()).isEqualTo(existUser.getEmail());
}
以下测试方法创建一个具有管理员角色的新用户:
@Test
public void testAddRoleToNewUser() {
Role roleAdmin = roleRepo.findByName("Admin");
User user = new User();
user.setEmail("mikes.gates@gmail.com");
user.setPassword("mike2020");
user.setFirstName("Mike");
user.setLastName("Gates");
user.addRole(roleAdmin);
User savedUser = userRepo.save(user);
assertThat(savedUser.getRoles().size()).isEqualTo(1);
}
以下测试将通过添加两个角色 User 和 Customer 来更新现有用户:
@Test
public void testAddRoleToExistingUser() {
User user = userRepo.findById(1L).get();
Role roleUser = roleRepo.findByName("User");
Role roleCustomer = new Role(3);
user.addRole(roleUser);
user.addRole(roleCustomer);
User savedUser = userRepo.save(user);
assertThat(savedUser.getRoles().size()).isEqualTo(2);
}
运行这些测试方法,您将看到插入到users和users_roles表中的行。角色表不受影响。
4. 为注册用户设置默认角色
用户注册中的一个常见场景是为新注册的用户设置默认角色,例如用户或客户角色。以下是服务层的示例代码片段:
package net.codejava;
@Service
public class UserService {
@Autowired
private UserRepository userRepo;
@Autowired RoleRepository roleRepo;
@Autowired PasswordEncoder passwordEncoder;
public void registerDefaultUser(User user) {
Role roleUser = roleRepo.findByName("User");
user.addRole(roleUser);
userRepo.save(user);
}
}
以及控制器层的代码:
package net.codejava;
@Controller
public class AppController {
@Autowired
private UserService service;
@PostMapping("/register")
public String processRegister(User user) {
service.registerDefaultUser(user);
return "register_success";
}
}
如您所见,这非常简单——感谢 Spring Data JPA 和 Hibernate 框架,极大地简化了数据访问层的编码。
5. 在 Web 表单中为用户分配角色
现在,我将向您展示如何使用 Web 用户界面编写编辑用户功能的代码,我们可以在其中更改分配给用户的角色。
首先,在UserService 类中实现如下方法:
public List<User> listAll() {
return userRepo.findAll();
}
在控制器类中:
@GetMapping("/users")
public String listUsers(Model model) {
List<User> listUsers = service.listAll();
model.addAttribute("listUsers", listUsers);
return "users";
}
此处理程序方法将显示从数据库中检索到的用户列表。并将以下相关代码放入视图页面(HTML):
<table class="table table-striped table-bordered">
<thead class="thead-dark">
<tr>
<th>User ID</th>
<th>E-mail</th>
<th>First Name</th>
<th>Last Name</th>
<th>Roles</th>
<th></th>
</tr>
</thead>
<tbody>
<tr th:each="user: ${listUsers}">
<td th:text="${user.id}">User ID</td>
<td th:text="${user.email}">E-mail</td>
<td th:text="${user.firstName}">First Name</td>
<td th:text="${user.lastName}">Last Name</td>
<td th:text="${user.roles}">Roles</td>
<td><a th:href="/@{'/users/edit/' + ${user.id}}">Edit</a></td>
</tr>
</tbody>
</table>
它将在 URL http://localhost.../users 处显示用户列表,如下所示:
在此用户列表页面上,我们可以单击编辑超链接来编辑用户。所以像这样编写处理程序方法:
@GetMapping("/users/edit/{id}")
public String editUser(@PathVariable("id") Long id, Model model) {
User user = service.get(id);
List<Role> listRoles = service.listRoles();
model.addAttribute("user", user);
model.addAttribute("listRoles", listRoles);
return "user_form";
}
并在服务类中实现以下两个方法:
public User get(Long id) {
return userRepo.findById(id).get();
}
public List<Role> listRoles() {
return roleRepo.findAll();
}
在视图层,为编辑用户表单编写如下代码:
<form th:action="@{/users/save}" th:object="${user}"
method="post" style="max-width: 600px; margin: 0 auto;">
<input type="hidden" th:field="*{id}" />
<div class="m-3">
<div class="form-group row">
<label class="col-4 col-form-label">E-mail: </label>
<div class="col-8">
<input type="email" th:field="*{email}" class="form-control" required />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">Password: </label>
<div class="col-8">
<input type="password" th:field="*{password}" class="form-control"
required minlength="6" maxlength="10"/>
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">First Name: </label>
<div class="col-8">
<input type="text" th:field="*{firstName}" class="form-control"
required minlength="2" maxlength="20"/>
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">Last Name: </label>
<div class="col-8">
<input type="text" th:field="*{lastName}" class="form-control"
required minlength="2" maxlength="20" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">Roles: </label>
<div class="col-8">
<th:block th:each="role: ${listRoles}">
<input type="checkbox" th:field="*{roles}"
th:text="${role.name}" th:value="${role.id}" class="m-2" />
</th:block>
</div>
</div>
<div>
<button type="submit" class="btn btn-primary">Update</button>
</div>
</div>
</form>
此页面中最重要的是显示角色列表并检查分配给当前用户的角色的代码:
<th:block th:each="role: ${listRoles}">
<input type="checkbox" th:field="*{roles}"
th:text="${role.name}" th:value="${role.id}" class="m-2" />
</th:block>
然后编辑用户表单将如下所示:
这里很酷的是,Thymeleaf 会根据分配给用户的角色自动显示选择的角色。此外,您可以在此处简单地选中/取消选中角色来更新用户的角色。
并编写处理表单提交的处理程序方法,如下所示:
@PostMapping("/users/save")
public String saveUser(User user) {
service.save(user);
return "redirect:/users";
}
以及服务层的相关代码:
public void save(User user) {
userRepo.save(user);
}
这是一些关于在 Spring Boot Web 应用程序中向用户添加角色的代码示例。我希望您发现这个书面教程对您有所帮助。
来源:https://blog.csdn.net/allway2/article/details/126969859


猜你喜欢
- 这个系列我们看看C#中有哪些我们知道,但是又不知道怎么用,又或者懒得去了解的东西,比如这篇我们要介绍的toDictionary和ToLook
- 本文实例讲述了C#警惕匿名方法造成的变量共享。分享给大家供大家参考,具体如下:匿名方法匿名方法是.NET 2.0中引入的高级特性,“匿名”二
- 平时开发,基本不改变的常量我们都放在了配置项里,如properties或yml文件里,这个时候为了只在启动时候进行加载。如何做呢?我们通过s
- ConstantConstant 和 ConstantPool 是用于表示常量的一种机制。Constant 接口定义了常量的基本属性和方法,
- 修改加密和验证方法/** * 生成BCryptPasswordEncoder密码 *
- 1.引言在操作应用的时候,会有很多不同的手势操作,如按下、单击、双击、长按等手势,我们可以在这些手势事件中添加相应的业务逻辑,那么如何检测不
- 本文实例为大家分享了Java实现坦克大战小游戏的具体代码,供大家参考,具体内容如下创作背景:n年前的学期末课题设计,从b站上学的,一个代码一
- 环境: idea2020.1插件: LeetCode-editor 6.7一、IDEA安装LeetCode插件安装完成重启idea打开插件U
- 背景Timsort 是一个混合、稳定的排序算法,简单来说就是归并排序和二分插入排序算法的混合体,号称世界上最好的排序算法。Timsort一直
- 最近过年发红包拜年成为一种新的潮流,作为程序猿对算法的好奇远远要大于对红包的好奇,这里介绍一种自己想到的一种随机红包分配策略,还请大家多多指
- JSONObject的使用 一、 JSON对象的使用:String content = "{'username&
- 首先来说一下本文中例子所要实现的功能:基于ProtoBuf序列化对象使用Socket实现时时通信数据包的编码和解码下面来看具体的步骤:一、U
- 多线程安全嘛在 Spring 框架中,Bean 是应用程序的核心构建块,代表了在 Spring 容器中管理的对象或组件。Spring 容器负
- 代码如下所示:<!-- 配置数据源 --> <bean id="dataSource" c
- 对于单链表不熟悉的可以看一下基于Java实现单链表的增删改查一、原地反转1、新建一个哨兵节点下一结点指向头结点2、把待反转链表的下一节点插入
- 不讲太多理论知识,官网都有,直接上手。1.测试表DROP TABLE IF EXISTS `user`;CREATE TABLE `user
- Overview在今天的开发学习中,我遇到了一个需求是在App的flash页面添加bing每日一图。这些都简单,但是当我获取到了图片的Url
- 目录前言Lottie案例尝试1. 集成依赖2. 添加 LottieAnimationView 加载网络资源3. 加载本地资源4. 循环播放
- 一、打印直角三角形这个循环控制打印十行空格for (int x = 1; x <= 10; x++) {//因为要打印一个十行的直角三
- /* * Copyright 2012-2013 The Haohui Network Cor