Spring安全的角色和許可權原始碼與教程 - javadevjournal

banq發表於2021-05-28

在本文中,我們將研究Spring安全形色和特權以及如何使用此功能來構建您的應用程式。
企業應用程式包含多個部分,它不允許所有使用者訪問整個應用程式。我們可能會提出一些要求,即我們希望根據使用者角色和特權提供對應用程式的訪問。讓我們以管理電子商務商店的簡單後端應用程式為例。
  1. 具有ADMIN角色的使用者將具有執行任何操作的完全許可權。
  2. 客戶服務代理可以讀取客戶和訂單資訊,但看不到其他選項。
  3. 產品經理只能看到更新/建立產品的選項。

Spring安全性使使用角色和特權來構建這些型別的規則變得更加容易。我們可以在註冊/建立過程中為使用者分配角色和特權以及這些角色。在本文中,我們將瞭解如何使用Spring安全形色和特權功能來處理此類用例。為了確保我們有共同的理解,讓我們看幾個重要的術語。
  • Role角色:角色代表了系統的高階別角色(例如ADMIN,MANAGER等),每個角色都可以具有低階別的許可權。
  • Privileges 許可權:許可權定義角色的低階許可權(例如,ADMIN可以讀取/寫入/刪除,但MANAGER只能讀取/編輯)

可以從GitHub Repository下載完整的應用程式。
 

1.資料庫設計
設計spring安全形色和許可權的方法有多種,但是最常見和靈活的方法之一是圍繞使用者組構建角色和特權模組。作為任何應用程式的一部分,將使用者分為幾類,讓我們以下面的示例為例,以便更好地理解:

  1. 前端使用者應轉到“CUSTOMER組”。
  2. 後端使用者可以EMPLOYEE分組。
  3. 我們可以建立支援使用者的另一個變體(例如ADMIN,MANAGER等等)

我們將使用相同的應用程式概念。應用程式的每個使用者都將屬於某個組,我們將使用這些組來驅動角色和許可權。這是我們的應用程式的資料庫設計。

Spring安全的角色和許可權原始碼與教程 - javadevjournal

  1. 每個使用者都屬於某個組。
  2. 組將在註冊/建立時分配給使用者。
  3. principle_group 定義系統中所有可用的組(例如客戶,管理員等)

UserGoup實體:

@Entity
@Table(name = "principle_groups")
public class Group{
    
    //removed getter and setter to save space
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String code;
    private String name;

    @ManyToMany(mappedBy = "userGroups")
    private Set<UserEntity> users;
}

Group是一個簡單的JPA實體,幷包含了組名稱和程式碼資訊。有趣的部分是@ManyToMany與User實體的關係。這種多對多關係將為我們建立另一個資料庫表

UserEntity重點是與Group實體的關係。

@Entity
@Table(name = "user")
public class UserEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String firstName;
    private String lastName;
    @Column(unique = true)
    private String email;
    private String password;
    private String token;
    private boolean accountVerified;
    private int failedLoginAttempts;
    private boolean loginDisabled;

    @OneToMany(mappedBy = "user")
    private Set<SecureToken> tokens;

    @ManyToMany(cascade = {
            CascadeType.PERSIST,
            CascadeType.MERGE
    })
    @JoinTable(name = "user_groups",
            joinColumns =@JoinColumn(name = "customer_id"),
            inverseJoinColumns = @JoinColumn(name = "group_id"
    ))
    private Set<Group> userGroups= new HashSet<>();

    public Set<Group> getUserGroups() {
        return userGroups;
    }

    public void setUserGroups(Set<Group> userGroups) {
        this.userGroups = userGroups;
    }
}

我們可以根據您的要求將資料填充到principal_groups表中。我們將填充以下2組:
  1. 顧客
  2. 行政

Spring安全的角色和許可權原始碼與教程 - javadevjournal
 

2.向使用者新增組
分配給使用者的組上派生spring安全形色和許可權。讓我們更改註冊過程,以將使用者組分配給使用者。我們將對我們的DefaultUserService內容進行微小的更改。在註冊過程中,我們會將組新增到使用者個人資料中。

@Service("userService")
public class DefaultUserService implements UserService{

    @Autowired
    private UserRepository userRepository;

    @Autowired
    UserGroupRepository groupRepository;

    @Override
    public void register(UserData user) throws UserAlreadyExistException {
        if(checkIfUserExist(user.getEmail())){
            throw new UserAlreadyExistException("User already exists for this email");
        }
        UserEntity userEntity = new UserEntity();
        BeanUtils.copyProperties(user, userEntity);
        encodePassword(user, userEntity);
        updateCustomerGroup(userEntity);
        userRepository.save(userEntity);
        sendRegistrationConfirmationEmail(userEntity);

    }

    private void updateCustomerGroup(UserEntity userEntity){
        Group group= groupRepository.findByCode("customer");
        userEntity.addUserGroups(group);
    }
}

您始終可以根據需要更改組分配邏輯。我們甚至可以在後端系統中構建邏輯以將組分配給使用者。
 

3.自定義UserDetailsS​​ervice實現
UserDetailsS​​ervice是Spring Security框架中的類,是用來檢索使用者的身份驗證和授權資訊的核心介面。該介面還負責提供使用者GrantedAuthority列表,該列表用於為使用者派生我們的spring安全形色和許可權。讓我們實現spring security的自定義UserDetailsS​​ervice,以返回GrantedAuthority基於使用者組的列表。

@Service("userDetailsService")
@Transactional
public class CustomUserDetailService implements UserDetailsService{

    @Autowired
    UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        final UserEntity customer = userRepository.findByEmail(email);
        if (customer == null) {
            throw new UsernameNotFoundException(email);
        }
        boolean enabled = !customer.isAccountVerified(); // we can use this in case we want to activate account after customer verified the account
        UserDetails user = User.withUsername(customer.getEmail())
                .password(customer.getPassword())
                .disabled(customer.isLoginDisabled())
                .authorities(getAuthorities(customer)).build()
                ;

        return user;
    }

    private Collection<GrantedAuthority> getAuthorities(UserEntity user){
        Set<Group> userGroups = user.getUserGroups();
        Collection<GrantedAuthority> authorities = new ArrayList<>(userGroups.size());
        for(Group userGroup : userGroups){
            authorities.add(new SimpleGrantedAuthority(userGroup.getCode().toUpperCase()));
        }

        return authorities;
    }
}


這裡有趣的事情是我們如何構建GrantedAuthority實體。我們使用一種簡單的邏輯來構建GrantedAuthority與分配的使用者組相同的列表。您可以更改/自定義邏輯以構建更復雜的GrantedAuthorities。

 

4. Spring Security Authority對映
透過UserDetailsS​​ervice實現子類,我們可以開始透過hasAnyAuthority()或hasAuthority()方法使用授權處理UI上的資料資訊可見性。讓我們看一下修改後的spring安全配置。

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/login", "/register","/home")
            .permitAll()
            .antMatchers("/account/**").hasAnyAuthority("CUSTOMER", "ADMIN")
            .and()
            ...
}

看著上面的配置中,我們告訴Spring security,只允許CUSTOMER和ADMIN許可權使用者訪問 /account/**,注意使用者授權是由UserDetailsService提供。您也可以使用相同的選項根據使用者角色來顯示/隱藏連結。這裡將Spring安全性與Thymeleaf結合使用的示例程式碼。

<ul class="navbar-nav ml-auto">
    <li class="dropdown user user-menu" sec:authorize="hasAnyAuthority('CUSTOMER', 'ADMIN')">
        <a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
            <span class="hidden-xs" sec:authentication="name"></span>
        </a>
        <ul class="dropdown-menu">
            <li class="user-header">
                <img th:src="@{/dist/img/avatar5.png}" class="img-circle" alt="User Image">
                <p>
                    Spring Security Course
                    <small>Java Development Journal</small>
                </p>
            </li>
            <li class="user-footer">
                <div class="pull-right">
                    <a href="javascript: document.logoutForm.submit()" class="btn btn-default btn-flat">Sign out</a>
                </div>
            </li>
        </ul>
    </li>
    <form name="logoutForm" th:action="@{/logout}" method="post" th:hidden="true">
        <input hidden type="submit" value="Sign Out"/>
    </form>
</ul>


 

相關文章