Spring Security如何为用户示例添加角色详解

Java
391
0
0
2023-03-14
标签   Spring
目录
  • 前言
  • 1. 用户和角色实体类和存储库的代码
  • 2. 单元测试——创建角色 
  • 3. 单元测试——给用户添加角色
  • 4. 为注册用户设置默认角色
  • 5. 在 Web 表单中为用户分配角色
  • 总结

前言

在这个 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 应用程序中向用户添加角色的代码示例。我希望您发现这个书面教程对您有所帮助。