当前位置:   article > 正文

Spring Security 中的RBAC角色和权限_spring security rabc

spring security rabc

在这篇文章中,我们将看看使用 Spring boot的R ole B ased A ccess Control ( RBAC )。

了解 RBAC

RBAC 模型中存在三个关键实体。他们是,

  1. 用户或主题 ——执行操作的系统参与者。它可以代表一个自然人、一个自动帐户,甚至是另一个应用程序。
  2. 角色 ——由职位、部门或职能层次结构定义的权限级别。
  3. 特权——执行操作的批准或许可

话虽如此,以下是这些实体如何相互映射的说明。

基本上,用户可以执行操作。要执行操作,他们需要具有一定的权限或特权。这就是为什么将权限分配给角色而将角色分配给用户的原因。让我们看看如何实现这些。

RBAC 实体

让我们创建上述对象以表示为数据库实体。

用户实体

  1. @Data
  2. @Entity
  3. public class UserAccount {
  4. @Id
  5. @GeneratedValue(strategy = GenerationType.IDENTITY)
  6. private Integer id;
  7. @Column(unique = true)
  8. private String username;
  9. private String password;
  10. private boolean active;
  11. @OneToMany(mappedBy = "user")
  12. private List<UserToRole> userToRoles;
  13. }

用户角色实体

  1. @Data
  2. @Entity
  3. public class UserRole {
  4. @Id
  5. @GeneratedValue(strategy = GenerationType.IDENTITY)
  6. private Integer id;
  7. private String roleName;
  8. @OneToMany(mappedBy = "role")
  9. private List<UserRoleToPrivilege> userRoleToPrivileges;
  10. }

UserPrivileges 实体

  1. @Data
  2. @Entity
  3. public class UserPrivilege {
  4. @Id
  5. @GeneratedValue(strategy = GenerationType.IDENTITY)
  6. private Integer id;
  7. private String privilegeName;
  8. }

UserRoleToPrivilege 实体

  1. @Data
  2. @Entity
  3. public class UserRoleToPrivilege {
  4. @Id
  5. @GeneratedValue(strategy = GenerationType.IDENTITY)
  6. private Integer id;
  7. @ManyToOne
  8. private UserRole role;
  9. @ManyToOne
  10. private UserPrivilege privilege;
  11. }

UserToRole 实体

  1. @Data
  2. @Entity
  3. public class UserToRole {
  4. @Id
  5. @GeneratedValue(strategy = GenerationType.IDENTITY)
  6. private Integer id;
  7. @ManyToOne
  8. private UserAccount user;
  9. @ManyToOne
  10. private UserRole role;
  11. }

自动生成的数据库ER图

 

pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.7.4</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>com.springhow.examples.springboot</groupId>
  12. <artifactId>spring-boot-security-rbac</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>spring-boot-security-rbac</name>
  15. <description>Demo project for Spring Boot</description>
  16. <properties>
  17. <java.version>1.8</java.version>
  18. </properties>
  19. <dependencies>
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-starter-data-jpa</artifactId>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.springframework.boot</groupId>
  26. <artifactId>spring-boot-starter-web</artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter-security</artifactId>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.projectlombok</groupId>
  34. <artifactId>lombok</artifactId>
  35. </dependency>
  36. <dependency>
  37. <groupId>mysql</groupId>
  38. <artifactId>mysql-connector-java</artifactId>
  39. <scope>runtime</scope>
  40. </dependency>
  41. <dependency>
  42. <groupId>org.springframework.boot</groupId>
  43. <artifactId>spring-boot-starter-test</artifactId>
  44. <scope>test</scope>
  45. </dependency>
  46. </dependencies>
  47. <build>
  48. <plugins>
  49. <plugin>
  50. <groupId>org.springframework.boot</groupId>
  51. <artifactId>spring-boot-maven-plugin</artifactId>
  52. </plugin>
  53. </plugins>
  54. </build>
  55. </project>

application.properties

  1. ##################################################
  2. # define mysql DataSource properties
  3. spring.jpa.hibernate.ddl-auto=update
  4. spring.datasource.url=jdbc:mysql://localhost:3306/rbac?autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
  5. spring.datasource.username=root
  6. spring.datasource.password=root
  7. spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver
  8. spring.jpa.show-sql: true
  9. ##################################################
  10. # Thymeleaf
  11. #spring.thymeleaf.cache=false
  12. #spring.thymeleaf.prefix=classpath:/templates
  13. #spring.thymeleaf.suffix=.html

填充数据库条目

有了上述实体,让我们用适当的角色和权限填充数据库。对于这个测试,我使用 data.sql 文件直接输入了条目。

  1. insert into user_account(id, username, password, active) values (1, 'user1', '{noop}user1', 1);
  2. insert into user_account(id, username, password, active) values (2, 'user2', '{noop}user2', 1);
  3. insert into user_account(id, username, password, active) values (3, 'admin', '{noop}admin', 1);
  4. insert into user_role(id, role_name) values (1, 'USER');
  5. insert into user_role(id, role_name) values (2, 'ADMIN');
  6. insert into user_to_role(id, user_id, role_id) values (1, 1, 1);
  7. insert into user_to_role(id, user_id, role_id) values (2, 2, 1);
  8. insert into user_to_role(id, user_id, role_id) values (3, 3, 2);
  9. insert into user_privilege(id, privilege_name) values (1, 'canReadUser');
  10. insert into user_privilege(id, privilege_name) values (2, 'canReadAdmin');
  11. insert into user_role_to_privilege(id, role_id, privilege_id) values (1, 1, 1);
  12. insert into user_role_to_privilege(id, role_id, privilege_id) values (2, 2, 1);
  13. insert into user_role_to_privilege(id, role_id, privilege_id) values (3, 2, 2);

请注意,我使用的是  NoOpPasswordEncoder ,因为密码前面带有 {noop}.

Spring Security userDetailsS​​ervice

在我们之前的帖子中,我们总是使用一个角色来调用 USER 系统中的所有用户。但是,我们需要进行更改以从数据库中选择这些角色和权限。这是一个如何做到这一点的粗略示例。

  1. @Component
  2. public class DatabaseUserDetailsService implements UserDetailsService {
  3. private final
  4. UserAccountRepository userAccountRepository;
  5. public DatabaseUserDetailsService(UserAccountRepository userAccountRepository) {
  6. this.userAccountRepository = userAccountRepository;
  7. }
  8. @Override
  9. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  10. UserAccount userAccount = userAccountRepository.findByUsername(username);
  11. if (userAccount == null) {
  12. throw new UsernameNotFoundException("User with username [" + username + "] not found in the system");
  13. }
  14. Set<GrantedAuthority> authorities = new HashSet<>();
  15. for (UserToRole userToRole : userAccount.getUserToRoles()) {
  16. authorities.add(new SimpleGrantedAuthority("ROLE_" + userToRole.getRole().getRoleName()));
  17. for (UserRoleToPrivilege userRoleToPrivilege : userToRole.getRole().getUserRoleToPrivileges()) {
  18. authorities.add(new SimpleGrantedAuthority(userRoleToPrivilege.getPrivilege().getPrivilegeName()));
  19. }
  20. }
  21. return new CustomUserDetails(userAccount.getUsername(), userAccount.getPassword(), userAccount.isActive(), authorities);
  22. }
  23. }

这里要注意的一件有趣的事情是,我们添加了角色和权限作为权限。但是,所有角色都以 ROLE_. 这种特定的方式是由于安全表达式的喜欢 hasRole 和 hasAuthority 工作方式。

这样,开发人员可以使用表达式为 url 映射设置角色级别和权限级别设置,您将在下面看到。

保护 API 端点

使用 WebSecurityConfigurerAdapter,您可以自定义谁可以访问哪个 URL。看看这个配置片段。

  1. @Configuration
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3. protected void configure(HttpSecurity http) throws Exception {
  4. http.authorizeRequests()
  5. .antMatchers("/user").access("hasAuthority('canReadUser')")
  6. .antMatchers("/admin").access("hasAuthority('canReadAdmin')")
  7. .anyRequest().authenticated()
  8. .and().httpBasic()
  9. .and().formLogin();
  10. }
  11. }

在这里, admin 用户可以同时访问 /user 和 /admin 因为 ADMIN 角色同时拥有 canReadUser 和 canReadAdmin 权限。但是, user1 或者 user2 无法访问 /admin ,因为他们会得到 403 Forbidden 响应。

有了以上所有内容,让我们测试结果。

  1. $ curl -i -u "user1:user1" http://localhost:8080/user
  2. HTTP/1.1 200
  3. Set-Cookie: JSESSIONID=9BEC44655277BBDF6832817AFF4CAAA1; Path=/; HttpOnly
  4. X-Content-Type-Options: nosniff
  5. X-XSS-Protection: 1; mode=block
  6. Cache-Control: no-cache, no-store, max-age=0, must-revalidate
  7. Pragma: no-cache
  8. Expires: 0
  9. X-Frame-Options: DENY
  10. Content-Type: text/plain;charset=UTF-8
  11. Content-Length: 11
  12. Date: Tue, 29 Dec 2020 15:16:57 GMT
  13. Hello user!
  1. $ curl -i -u "user1:user1" http://localhost:8080/admin
  2. HTTP/1.1 403
  3. Set-Cookie: JSESSIONID=0910F6115CB28A9DF914D22052396448; Path=/; HttpOnly
  4. X-Content-Type-Options: nosniff
  5. X-XSS-Protection: 1; mode=block
  6. Cache-Control: no-cache, no-store, max-age=0, must-revalidate
  7. Pragma: no-cache
  8. Expires: 0
  9. X-Frame-Options: DENY
  10. Content-Type: application/json
  11. Transfer-Encoding: chunked
  12. Date: Tue, 29 Dec 2020 15:17:28 GMT
  13. {
  14. "timestamp" : "2020-12-29T15:17:28.537+00:00",
  15. "status" : 403,
  16. "error" : "Forbidden",
  17. "message" : "",
  18. "path" : "/admin"
  19. }

如您所见,当 user1 尝试访问 /admin 端点时,他们会收到 403 - Forbidden 消息。

这是供您学习本教程的GitHub 存储库。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/181823?site
推荐阅读
相关标签
  

闽ICP备14008679号