在本教程中,我将指导您如何使用 Spring 安全性根据用户的角色对用户进行弹簧启动应用程序授权。凭据和角色动态存储在MySQL数据库中。具有休眠功能的弹簧数据JPA用于数据访问层,百里叶与弹簧安全性的集成用于视图层。我们将编写代码来保护现有的Spring Boot项目产品管理器,本教程中对此进行了介绍。因此,我建议您下载该项目,以便轻松遵循本教程。对于授权,我们将创建一些具有不同角色(权限)的用户,如下所示:
Username | Roles |
patrick | USER |
alex | CREATOR |
john | EDITOR |
namhm | CREATOR, EDITOR |
admin | ADMIN |
用户角色允许用户查看所有产品;角色创建者是创建新产品的权限;编辑角色用于编辑产品;和角色 ADMIN 向用户授予所有权限。
1. 设计和创建表
对于使用存储在数据库中的凭据和权限进行基于角色的授权,我们必须创建以下 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
|
2. 配置数据源属性
要将弹簧启动与弹簧数据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
|
3. 配置依赖关系
确保 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.boot</ groupId >
< artifactId >spring-boot-starter-data-jpa</ artifactId >
</ dependency >
< dependency >
< groupId >org.springframework.boot</ groupId >
< artifactId >spring-boot-starter-security</ artifactId >
</ dependency >
< dependency >
< groupId >org.springframework.boot</ groupId >
< artifactId >spring-boot-starter-thymeleaf</ artifactId >
</ dependency >
< dependency >
< groupId >org.springframework.boot</ groupId >
< artifactId >spring-boot-starter-web</ artifactId >
</ dependency >
< dependency >
< groupId >mysql</ groupId >
< artifactId >mysql-connector-java</ artifactId >
< scope >runtime</ scope >
</ dependency >
< dependency >
< groupId >org.thymeleaf.extras</ groupId >
< artifactId >thymeleaf-extras-springsecurity5</ artifactId >
</ dependency >
|
4. 代码实体类
接下来,我们需要创建两个实体类,它们与数据库中的用户和角色表进行映射。第一个类是角色:第二个类是 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 Set<Role> roles = new HashSet<>();
public Long getId() {
return id;
}
// remaining getters and setters are not shown for brevity
}
|
5. 代码用户存储库
接下来,使用以下代码创建用户存储库接口:此接口是由 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<User, Long> {
@Query ( "SELECT u FROM User u WHERE u.username = :username" )
public User getUserByUsername( @Param ( "username" ) String username);
}
|
6. 实施用户详细信息和用户详细信息服务
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<Role> roles = user.getRoles();
List<SimpleGrantedAuthority> authorities = new 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<Role> roles = user.getRoles();
List<SimpleGrantedAuthority> authorities = new 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);
}
}
|
7. 配置弹簧安全认证和授权
为了将所有部分连接在一起,我们使用以下代码编写了一个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" )
;
}
}
|
8. 使用百里叶集成与弹簧安全实现授权
若要将 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" >Username</ span >
|
1
|
< span sec:authentication = "principal.authorities" >Roles</ span >
|
1
2
3
4
5
|
< div sec:authorize = "isAuthenticated()" >
Welcome < b >< span sec:authentication = "name" >Username</ span ></ b >
< i >< span sec:authentication = "principal.authorities" >Roles</ span ></ 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 Product</ a >
</ div >
|
1
2
3
|
< div sec:authorize = "hasAnyAuthority('ADMIN', 'EDITOR')" >
< a th:href = "/@{'/edit/' + ${product.id}}" >Edit</ a >
</ div >
|
1
2
3
|
< div sec:authorize = "hasAuthority('ADMIN')" >
< a th:href = "/@{'/delete/' + ${product.id}}" >Delete</ a >
</ div >
|
结论:到目前为止,您已经学会了如何使用弹簧安全软件和Thymeleaf根据用户的角色为弹簧启动应用程序授权用户。你看,Spring框架使以最小的努力实现授权变得简单方便。作为参考,您可以下载下面附带的示例项目。
弹簧安全授权.zip | [示例弹簧启动安全项目] | 87 千字节 |