在本教程中,我将指导您如何使用 Spring 安全性根据用户的角色对用户进行弹簧启动应用程序授权。凭据和角色动态存储在MySQL数据库中。具有休眠功能的弹簧数据JPA用于数据访问层,百里叶与弹簧安全性的集成用于视图层。我们将编写代码来保护现有的Spring Boot项目产品管理器,本教程中对此进行了介绍。因此,我建议您下载该项目,以便轻松遵循本教程。对于授权,我们将创建一些具有不同角色(权限)的用户,如下所示:
| Username | Roles |
| patrick | USER |
| alex | CREATOR |
| john | EDITOR |
| namhm | CREATOR, EDITOR |
| admin | ADMIN |
用户角色允许用户查看所有产品;角色创建者是创建新产品的权限;编辑角色用于编辑产品;和角色 ADMIN 向用户授予所有权限。
对于使用存储在数据库中的凭据和权限进行基于角色的授权,我们必须创建以下 3 个表:users 表存储凭据,角色表存储权限(权限)。用户和角色之间的实体关系是多对多的,因为一个用户可以有一个或多个角色,并且一个角色可以分配为一个或多个用户。这就是为什么我们需要中间表users_roles来实现这种多对多关联。您可以执行以下 MySQL 脚本来创建这些表:

| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | CREATE TABLE `users` ( `user_id` int(11) NOT NULL AUTO_INCREMENT, `email` varchar(45) NOT NULL, `full_name` varchar(45) NOT NULL, `password` varchar(64) NOT NULL, `enabled` tinyint(4) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `email_UNIQUE` (`email`) ); CREATE TABLE `roles` ( `role_id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(45) NOT NULL, PRIMARY KEY (`id`) ); CREATE TABLE `users_roles` ( `user_id` int(11) NOT NULL, `role_id` int(11) NOT NULL, KEY `user_fk_idx` (`user_id`), KEY `role_fk_idx` (`role_id`), CONSTRAINT `role_fk` FOREIGN KEY (`role_id`) REFERENCES `roles` (`role_id`), CONSTRAINT `user_fk` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ); |
执行以下 INSERT 语句以将 4 个角色插入到角色表中:运行以下 SQL 语句以在 users 表中创建 5 个用户:请注意,密码以 BCrypt 格式编码,并且与用户名相同。并执行以下脚本,根据上表向用户分配权限:这就是数据库的设置。
| 1 2 3 4 | INSERT INTO `roles` (`name`) VALUES ('USER'); INSERT INTO `roles` (`name`) VALUES ('CREATOR'); INSERT INTO `roles` (`name`) VALUES ('EDITOR'); INSERT INTO `roles` (`name`) VALUES ('ADMIN'); |
| 1 2 3 4 5 | INSERT INTO `users` (`username`, `password`, `enabled`) VALUES ('patrick', '$2a$10$cTUErxQqYVyU2qmQGIktpup5chLEdhD2zpzNEyYqmxrHHJbSNDOG.', '1'); INSERT INTO `users` (`username`, `password`, `enabled`) VALUES ('alex', '$2a$10$.tP2OH3dEG0zms7vek4ated5AiQ.EGkncii0OpCcGq4bckS9NOULu', '1'); INSERT INTO `users` (`username`, `password`, `enabled`) VALUES ('john', '$2a$10$E2UPv7arXmp3q0LzVzCBNeb4B4AtbTAGjkefVDnSztOwE7Gix6kea', '1'); INSERT INTO `users` (`username`, `password`, `enabled`) VALUES ('namhm', '$2a$10$GQT8bfLMaLYwlyUysnGwDu6HMB5G.tin5MKT/uduv2Nez0.DmhnOq', '1'); INSERT INTO `users` (`username`, `password`, `enabled`) VALUES ('admin', '$2a$10$IqTJTjn39IU5.7sSCDQxzu3xug6z/LPU6IF0azE/8CkHCwYEnwBX.', '1'); |
| 1 2 3 4 5 6 | INSERT INTO `users_roles` (`user_id`, `role_id`) VALUES (1, 1); -- user patrick has role USER INSERT INTO `users_roles` (`user_id`, `role_id`) VALUES (2, 2); -- user alex has role CREATOR INSERT INTO `users_roles` (`user_id`, `role_id`) VALUES (3, 3); -- user john has role EDITOR INSERT INTO `users_roles` (`user_id`, `role_id`) VALUES (4, 2); -- user namhm has role CREATOR INSERT INTO `users_roles` (`user_id`, `role_id`) VALUES (4, 3); -- user namhm has role EDITOR INSERT INTO `users_roles` (`user_id`, `role_id`) VALUES (5, 4); -- user admin has role ADMIN |
要将弹簧启动与弹簧数据JPA和休眠一起使用,请在应用程序属性中配置数据库连接信息,如下所示:修改与您的MySQL数据库匹配的URL,用户名和密码。
| 1 2 3 4 | spring.jpa.hibernate.ddl-auto=none spring.datasource.url=jdbc:mysql://localhost:3306/sales spring.datasource.username=root spring.datasource.password=password |
确保 Maven 构建文件包含以下针对弹簧 Web、弹簧数据 JPA、弹簧安全性、百里叶、MySQL JDBC 驱动程序和用于弹簧安全性的百里叶附加内容的依赖项声明:请注意,默认情况下,如果类路径中存在弹簧安全库,则用户必须登录才能使用该应用程序。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-data-jpaartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-securityartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-thymeleafartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> <dependency> <groupId>mysqlgroupId> <artifactId>mysql-connector-javaartifactId> <scope>runtimescope> dependency> <dependency> <groupId>org.thymeleaf.extrasgroupId> <artifactId>thymeleaf-extras-springsecurity5artifactId> dependency> |
接下来,我们需要创建两个实体类,它们与数据库中的用户和角色表进行映射。第一个类是角色:第二个类是 User:在这里,您可以看到我们在 User 类中使用一组角色来映射从用户到角色的单向多对多关联,例如 user.roles。有关休眠多对多关系映射的详细信息,请参阅本教程。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package net.codejava; import javax.persistence.*; @Entity @Table(name = "roles") public class Role { @Id @Column(name = "role_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String name; public Integer getId() { return id; } // remaining getters and setters } |
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | package net.codejava; import java.util.*; import javax.persistence.*; @Entity @Table(name = "users") public class User { @Id @Column(name = "user_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; private boolean enabled; @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @JoinTable( name = "users_roles", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id") ) private Setnew HashSet<>(); public Long getId() { return id; } // remaining getters and setters are not shown for brevity } |
接下来,使用以下代码创建用户存储库接口:此接口是由 Spring 数据 JPA 定义的 Crud存储库的子类型,因此 Spring 将在运行时生成实现类。我们定义了由 JPA 查询注释的 getUserBy用户名() 方法,以供春季安全用于身份验证。如果您不熟悉春季数据JPA,请查看此快速入门指南。
| 1 2 3 4 5 6 7 8 9 10 11 | package net.codejava; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; public interface UserRepository extends CrudRepository @Query("SELECT u FROM User u WHERE u.username = :username") public User getUserByUsername(@Param("username") String username); } |
Spring Security需要一个用户详细信息接口的实现来了解经过身份验证的用户信息,因此我们创建了MyUserDetails类,如下所示:您可以看到,该类包装了用户类的实例,并将几乎重写的方法委托给用户的方法。对于授权,请注意此方法:此方法返回一组角色(权限),供Spring安全在授权过程中使用。接下来,我们需要用以下代码对由Spring安全定义的用户详细信息服务接口的实现进行编码:如您所见,此类在loadUserByUsername()方法中使用用户存储库接口的实例,该实例将在对用户进行身份验证时由Spring安全调用。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | package net.codejava; import java.util.*; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; public class MyUserDetails implements UserDetails { private User user; public MyUserDetails(User user) { this.user = user; } @Override public Collection extends GrantedAuthority> getAuthorities() { Set Listnew ArrayList<>(); for (Role role : roles) { authorities.add(new SimpleGrantedAuthority(role.getName())); } return authorities; } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return user.isEnabled(); } } |
| 1 2 3 4 5 6 7 8 9 10 11 | @Override public Collection extends GrantedAuthority> getAuthorities() { Set Listnew ArrayList<>(); for (Role role : roles) { authorities.add(new SimpleGrantedAuthority(role.getName())); } return authorities; } |
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package net.codejava; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.*; public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.getUserByUsername(username); if (user == null) { throw new UsernameNotFoundException("Could not find user"); } return new MyUserDetails(user); } } |
为了将所有部分连接在一起,我们使用以下代码编写了一个Spring Security配置类:需要前4种方法来配置使用Spring数据JPA和Hibernate的身份验证提供程序。在最后一种方法中,我们为身份验证和授权配置HTTP安全性。我们还配置了一个自定义URL,用于在用户没有权限的情况下显示拒绝访问错误。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | package net.codejava; import org.springframework.context.annotation.*; import org.springframework.security.authentication.dao.*; import org.springframework.security.config.annotation.authentication.builders.*; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.*; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean public UserDetailsService userDetailsService() { return new UserDetailsServiceImpl(); } @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService()); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(authenticationProvider()); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/").hasAnyAuthority("USER", "CREATOR", "EDITOR", "ADMIN") .antMatchers("/new").hasAnyAuthority("ADMIN", "CREATOR") .antMatchers("/edit/**").hasAnyAuthority("ADMIN", "EDITOR") .antMatchers("/delete/**").hasAuthority("ADMIN") .anyRequest().authenticated() .and() .formLogin().permitAll() .and() .logout().permitAll() .and() .exceptionHandling().accessDeniedPage("/403") ; } } |
若要将 Thymeleaf 与 Spring Security 结合使用视图,请确保像这样声明相关的 XML 命名空间:要显示已登录用户的用户名,请使用以下代码:若要显示当前用户的所有角色(权限/权限/权利),请使用以下代码:显示仅适用于经过身份验证的用户的部分, 使用以下代码:要显示“注销”按钮:由于只有具有“创建者”或“管理员”角色的用户才能创建新产品,因此编写以下代码以显示“创建新产品”链接,该链接仅对授权用户可见:具有“编辑”或“管理员”角色的用户可以看到编辑/更新产品的链接, 因此,以下代码:只有管理员用户才能看到删除产品的链接:这基本上就是如何使用Thymeleaf和Spring Security在视图层中授权用户。
| 1 2 | <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5"> |
| 1 | <span sec:authentication="name">Usernamespan> |
| 1 | <span sec:authentication="principal.authorities">Rolesspan> |
| 1 2 3 4 5 | <div sec:authorize="isAuthenticated()"> Welcome <b><span sec:authentication="name">Usernamespan>b> <i><span sec:authentication="principal.authorities">Rolesspan>i> div> |
| 1 2 3 | <form th:action="@{/logout}" method="post"> <input type="submit" value="Logout" /> form> |
| 1 2 3 | <div sec:authorize="hasAnyAuthority('CREATOR', 'ADMIN')"> <a href="/new">Create New Producta> div> |
| 1 2 3 | <div sec:authorize="hasAnyAuthority('ADMIN', 'EDITOR')"> <a th:href="/@{'/edit/' + ${product.id}}">Edita> div> |
| 1 2 3 | <div sec:authorize="hasAuthority('ADMIN')"> <a th:href="/@{'/delete/' + ${product.id}}">Deletea> div> |
结论:到目前为止,您已经学会了如何使用弹簧安全软件和Thymeleaf根据用户的角色为弹簧启动应用程序授权用户。你看,Spring框架使以最小的努力实现授权变得简单方便。作为参考,您可以下载下面附带的示例项目。
| 弹簧安全授权.zip | [示例弹簧启动安全项目] | 87 千字节 |