Spring Boot Security 角色授权示例

在本教程中,我将指导您如何使用 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>
    &nbsp;
    <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 千字节
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值