1. 概述
本教程延續了使用 Spring Security 進行註冊系列,重點介紹瞭如何正確實現 角色和權限。
2. 用户、角色和權限
我們先來看我們的實體。我們有三個主要實體:
- 用户
- 角色代表用户在系統中的高級角色。每個角色將具有一組低級別的權限。
- 權限代表系統中的低級別、粒度細化的權限/授權。
以下是用户:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
private String email;
private String password;
private boolean enabled;
private boolean tokenExpired;
@ManyToMany
@JoinTable(
name = "users_roles",
joinColumns = @JoinColumn(
name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(
name = "role_id", referencedColumnName = "id"))
private Collection<Role> roles;
}
如你所見,用户包含角色以及用於適當註冊機制的幾個附加詳細信息。
接下來,以下是角色:
@Entity
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@ManyToMany(mappedBy = "roles")
private Collection<User> users;
@ManyToMany
@JoinTable(
name = "roles_privileges",
joinColumns = @JoinColumn(
name = "role_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(
name = "privilege_id", referencedColumnName = "id"))
private Collection<Privilege> privileges;
}
最後,讓我們看一下權限:
@Entity
public class Privilege {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@ManyToMany(mappedBy = "privileges")
private Collection<Role> roles;
}
如你所見,我們正在考慮用户 <-> 角色以及角色 <-> 權限之間的關係是多對多雙向關係。
接下來,讓我們專注於在系統中設置權限和角色的早期配置。
我們將將其與應用程序的啓動關聯起來,並且我們將使用一個<em>ApplicationListener</em>在<em>ContextRefreshedEvent</em>上加載初始數據在服務器啓動時:
@Component
public class SetupDataLoader implements
ApplicationListener<ContextRefreshedEvent> {
boolean alreadySetup = false;
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
@Autowired
private PrivilegeRepository privilegeRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
@Transactional
public void onApplicationEvent(ContextRefreshedEvent event) {
if (alreadySetup)
return;
Privilege readPrivilege
= createPrivilegeIfNotFound("READ_PRIVILEGE");
Privilege writePrivilege
= createPrivilegeIfNotFound("WRITE_PRIVILEGE");
List<Privilege> adminPrivileges = Arrays.asList(
readPrivilege, writePrivilege);
createRoleIfNotFound("ROLE_ADMIN", adminPrivileges);
createRoleIfNotFound("ROLE_USER", Arrays.asList(readPrivilege));
Role adminRole = roleRepository.findByName("ROLE_ADMIN");
User user = new User();
user.setFirstName("Test");
user.setLastName("Test");
user.setPassword(passwordEncoder.encode("test"));
user.setEmail("[email protected]");
user.setRoles(Arrays.asList(adminRole));
user.setEnabled(true);
userRepository.save(user);
alreadySetup = true;
}
@Transactional
Privilege createPrivilegeIfNotFound(String name) {
Privilege privilege = privilegeRepository.findByName(name);
if (privilege == null) {
privilege = new Privilege(name);
privilegeRepository.save(privilege);
}
return privilege;
}
@Transactional
Role createRoleIfNotFound(
String name, Collection<Privilege> privileges) {
Role role = roleRepository.findByName(name);
if (role == null) {
role = new Role(name);
role.setPrivileges(privileges);
roleRepository.save(role);
}
return role;
}
}
所以,這段簡單的設置代碼中發生了什麼?沒什麼複雜:
這裏有兩個快速説明。我們首先將查看<strong>術語</strong>。我們在這裏使用“權限-角色”術語。但是,在 Spring 中,這些術語略有不同。在 Spring 中,我們的權限被稱為角色,也稱為(授予的)權限,這有點令人困惑。
這對於實現來説不是問題,但絕對值得注意。
現在讓我們來查看認證過程。 我們將查看如何在自定義的 有趣的是,這裏跟蹤的是如何將權限(和角色)映射到 GrantedAuthority 實體。 這種映射使整個安全配置 此外,讓我們將角色組織成層級關係。 我們已經瞭解瞭如何通過將權限映射到角色來實施基於角色的訪問控制。這允許我們為用户分配單個角色,而無需分配所有單個權限。 然而,隨着角色的數量增加,用户可能需要多個角色,從而導致角色爆炸: 為了克服這個問題,我們可以使用 Spring Security 的角色層級: 分配 角色 ADMIN 自動賦予用户 STAFF 和 USER 角色中的權限。 然而,具有 角色 STAFF 的用户只能執行 STAFF 和 USER 角色中的操作。 讓我們在 Spring Security 中通過簡單地暴露一個類型為 RoleHierarchy 的 Bean 來創建此層級關係: 我們使用 > 符號在表達式中定義角色層級關係。這裏,我們已配置 ADMIN 角色包含 STAFF 角色,而 STAFF 角色又包含 USER 角色。 要將此角色層級關係包含在 Spring Web Expressions 中,我們需要將 roleHierarchy 實例添加到 WebSecurityExpressionHandler 中: 最後,將 expressionHandler 添加到 http.authorizeRequests(): /roleHierarchy 端點使用 ROLE_STAFF 進行保護,以證明 webSecurityExpressionHandler 正在正常工作。 正如我們所看到的,角色層級關係是一種減少需要為用户添加的角和權限數量的好方法。@Service("userDetailsService")
@Transactional
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Autowired
private IUserService service;
@Autowired
private MessageSource messages;
@Autowired
private RoleRepository roleRepository;
@Override
public UserDetails loadUserByUsername(String email)
throws UsernameNotFoundException {
User user = userRepository.findByEmail(email);
if (user == null) {
return new org.springframework.security.core.userdetails.User(
" ", " ", true, true, true, true,
getAuthorities(Arrays.asList(
roleRepository.findByName("ROLE_USER"))));
}
return new org.springframework.security.core.userdetails.User(
user.getEmail(), user.getPassword(), user.isEnabled(), true, true,
true, getAuthorities(user.getRoles()));
}
private Collection<? extends GrantedAuthority> getAuthorities(
Collection<Role> roles) {
return getGrantedAuthorities(getPrivileges(roles));
}
private List<String> getPrivileges(Collection<Role> roles) {
List<String> privileges = new ArrayList<>();
List<Privilege> collection = new ArrayList<>();
for (Role role : roles) {
privileges.add(role.getName());
collection.addAll(role.getPrivileges());
}
for (Privilege item : collection) {
privileges.add(item.getName());
}
return privileges;
}
private List<GrantedAuthority> getGrantedAuthorities(List<String> privileges) {
List<GrantedAuthority> authorities = new ArrayList<>();
for (String privilege : privileges) {
authorities.add(new SimpleGrantedAuthority(privilege));
}
return authorities;
}
}5. 角色層級


@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
String hierarchy = "ROLE_ADMIN > ROLE_STAFF \n ROLE_STAFF > ROLE_USER";
roleHierarchy.setHierarchy(hierarchy);
return roleHierarchy;
}
@Bean
public DefaultWebSecurityExpressionHandler customWebSecurityExpressionHandler() {
DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy());
return expressionHandler;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.expressionHandler(webSecurityExpressionHandler())
.antMatchers(HttpMethod.GET, "/roleHierarchy")
.hasRole("STAFF")
...
}
6. 用户註冊
最後,讓我們來查看一下新用户的註冊過程。
我們已經看到如何通過設置來創建用户並分配角色(和權限)給它。
現在,讓我們來查看一下在註冊新用户時如何執行此操作:
@Override
public User registerNewUserAccount(UserDto accountDto) throws EmailExistsException {
if (emailExist(accountDto.getEmail())) {
throw new EmailExistsException
("There is an account with that email adress: " + accountDto.getEmail());
}
User user = new User();
user.setFirstName(accountDto.getFirstName());
user.setLastName(accountDto.getLastName());
user.setPassword(passwordEncoder.encode(accountDto.getPassword()));
user.setEmail(accountDto.getEmail());
user.setRoles(Arrays.asList(roleRepository.findByName("ROLE_USER")));
return repository.save(user);
}
在這一簡單實現中,由於我們假設正在註冊一個標準用户,因此我們為其分配了 ROLE_USER 角色。
當然,可以通過使用多個硬編碼的註冊方法或允許客户端發送正在註冊的用户的類型,輕鬆地在相同的方式中實現更復雜的邏輯。
7. 結論
在本文中,我們演示瞭如何使用 JPA 實現角色和權限,用於 Spring Security 支持的系統。
我們還配置了角色層次結構,以簡化我們的訪問控制配置。