Spring Boot實現簡單的用戶權限管理(超詳細版)

一、前言

爲了避免浪費時間進行不必要的閱讀,這裏先對項目進行簡單的介紹。在實際應用場景中,每個用戶都有對應的角色,而每個角色又有對應的一些角色。因爲一個用戶可以有多個角色,一個角色也可以被多個用戶所擁有,角色和權限的關係也同理,這裏主要利用多對多的映射關係將他們聯繫起來,對他們進行管理。

主要實現的功能:

  1. 添加用戶、角色和權限
  2. 刪除用戶、角色和權限
  3. 給用戶添加角色、給角色添加權限
  4. 根據用戶名稱查詢用戶擁有的權限

二、項目環境

Java版本:jdk1.8.0_181
IDE:IntelliJ IDEA 2019.1.3
數據庫:postgresql 9.5
測試工具:postman

ps:數據庫類型不同不要緊,在創建的時候勾選不一樣的數據庫驅動的就行。

三、項目文件結構

項目創建的時候,需要勾選Web中的Spring Web StarterSQL中Spring Data JPA、PostgreSQL Driver(如果使用的是mysql數據庫,則勾選MySQL Driver),IDEA會自動幫我們在Maven的配置文件中添加相關的依賴。

以下是本項目的目錄結構:
項目文件結構

四、項目代碼

數據庫連接配置

  • application.yml
spring:
  datasource:
    driver-class-name: org.postgresql.Driver
    username: postgres
    password: 123456
    url: jdbc:postgresql://localhost:5432/postgres
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        temp:
          use_jdbc_metadata_defaults: false

1.Entity層

Entity層爲數據庫實體層,一般一個實體類對應數據庫中的一張數據表,類中的屬性與數據表中的字段一 一對應。默認情況下,類名即爲數據表的表名,屬性名則是對應字段名,字段類型也與變量的類型相對應。

本層註解簡單解釋:

  1. @Entity
    該註解用於表明這個類是一個實體類,會給他生成一張對應的數據表。
  2. @Table(name = “table_name”)
    該註解主要用於修改表名,name的值就是修改的數據表的名稱。
  3. @Id
    該註解用於聲明主鍵,標在哪個屬性上面對應的哪個字段就是主鍵
  4. @GeneratedValue(strategy = GenerationType.IDENTITY)
    該註解的strategy屬性主要用於設置主鍵的增長方式,IDENTITY表示主鍵由數據庫自己生成,從1開始單調遞增。
  5. @Column(name = “column_name”)
    該註解的name屬性用於更改數據表的列名,如果不想用默認的就用這個屬性改吧
  6. @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    這個註解得上是本項目得核心了,它聲明瞭實體之間的多對多關係,使兩張數據表關聯關聯起來,一般是通過生成一張映射表來實現這種映射關係。關於上面的cascade屬性和fetch屬性,有興趣的讀者可以查資料瞭解。
  7. @JoinTable
    這個註解是配套@ManyToMany使用的,一般在多對多關係的維護端標註,用於生成上面提到的映射表。一般該註解常用三個屬性:name屬性表示生成的數據表的名稱,joinColumns屬性表示關係維護端的主鍵,inverseJoinColumns則表示關係被維護端的主鍵。關於嵌套在裏面的@JoinColumn註解,在這裏主要用於配置映射表的外鍵,一般有兩個屬性:name用於配置外鍵在映射表中的名稱,referencedColumnName 用於表明外鍵在原表中的字段名稱。
  8. @JsonBackReference
    關於這個註解,建議先去掉試試然後再加上,對比一下效果。它主要可以使標註屬性避免被json序列化,進而避免多對多關係的查詢中出現死循環的情況。但是加上了這注解後,就不能進行反向查詢了(也就是說不能利用權限名查詢擁有這個權限的角色了)

注意:以下代碼都省略了要導入的包,getter和setter方法。需要導入相關包可以用快捷鍵Alt+Insert,用快捷鍵Alt+Insert然後選擇Getter and Setter可以快速生成相關方法。

  • User.java
@Entity
@Table(name = "user_tabel")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Integer userId;
    @Column(name = "user_name")
    private String userName;
    //關鍵點
    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinTable(
            name = "user_role", //name是表名
            //joinColumns設置的是entity中屬性到關係表的映射名稱,name是映射表中的字段名
            joinColumns = {@JoinColumn(name = "user_id")},
            //inverseJoinColumns,name是關係實體Role的id在關係表中的名稱
            inverseJoinColumns = {@JoinColumn(name = "role_id")}
    )
    private List<Role> roles;
    
    //省略了getter和setter方法
}
  • Role.java
@Entity
@Table(name = "role_table")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Integer roleId;
    @Column(name = "role_name")
    private String roleName;
    //作爲被維護端,只需要設置mappedBy屬性,其值與User中對應List類型變量名相同
    //@JsonBackReference可以避免屬性被json序列化,出現死循環
    @JsonBackReference
    @ManyToMany(mappedBy = "roles")
    private List<User> users;

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinTable(
            name = "role_auth", //name是表名
            joinColumns = @JoinColumn(name = "role_id"),
            inverseJoinColumns =@JoinColumn(name = "auth_id")
    )
    private List<Authority> authorities;

	 //省略了getter和setter方法
}
  • Authority.java
@Entity
@Table(name = "auth_table")
public class Authority {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "auth_id")
    private Integer authorityId;
    @Column(name = "auth_name")
    private String authorityName;

    @JsonBackReference
    @ManyToMany(mappedBy = "authorities")
    private List<Role> roles;

    //省略了getter和setter方法
}

2.dao層

dao層是數據持久層,也被稱爲mapper層。主要負責訪問數據庫,向數據庫發送SQL語句,完成基礎的增刪查改任務。主要通過定義繼承JpaRepository類的接口來實現,<>中填寫的是實體類的名稱和該實體主鍵的變量類型。

在接口中聲明的方法不用我們去實現,只要滿足命名規則JpaRepository類會自動幫我們生成相應的sql語句
詳情見:官方文檔

  • UserRepository.java
public interface UserRepository extends JpaRepository<User, Integer> {
    public List<User> findAllByUserName(String userName);
    public void deleteByUserName(String userName);
}
  • RoleRepository.java
public interface RoleRepository extends JpaRepository<Role, Integer> {
    public List<Role> findAllByRoleName(String roleName);
    public void deleteByRoleName(String roleName);
}
  • AuthorityRepository.java
public interface AuthorityRepository extends JpaRepository<Authority, Integer> {
    public List<Authority> findAllByAuthorityName(String authorityName);
    public void deleteByAuthorityName(String authorityName);
}

3.service層

service層是業務邏輯層,主要通過調用dao層的接口,接收dao層返回的數據,完成項目的基本功能設計。由於本項目的service層是在後面才加的,所以有些應該在本層實現的功能寫在了controller層orz。

踩到的坑

  1. 涉及到兩張表以上的更新或者刪除操作,爲了保證數據庫的一致性,需要添加 @Transactional事務註解,否則程序會拋出異常。(關於事務的詳情,如果不熟悉的話,強烈建議去弄懂。)
  2. 如果要執行刪除操作,需要先把它的List先清空,也就相當於把映射表中的關係清除。否則會拋出org.hibernate.exception.ConstraintViolationException異常。(我這裏用到了多種清除方式:如果刪除維護端數據,只是把維護端的List清空就行;如果刪除被維護端的數據,則把用戶(維護端)的List中要移除的角色(被維護端)都remove掉,不知道我是不是想多了)
  • EntityService.java
@Service
public class EntityService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private RoleRepository roleRepository;
    @Autowired
    private AuthorityRepository authorityRepository;

    @Transactional
    public void deleteUser(String userName) {
        List<User> users = userRepository.findAllByUserName(userName);
        //如果刪除維護端數據,只是把維護端的List清空
        for(User user : users) {
            user.getRoles().clear();
            userRepository.save(user); //執行save()之後纔會保存到數據庫中
        }
        userRepository.deleteByUserName(userName);
    }

    @Transactional
    public void deleteRole(String roleName) {
        List<Role> roles = roleRepository.findAllByRoleName(roleName);
        List<User> users = userRepository.findAll();
        for (User user : users) {
            List<Role> userRole = user.getRoles();
            for (Role role : roles) {
                if (userRole.contains(role)) {
                    userRole.remove(role);
                }
                role.getAuthorities().clear();
                roleRepository.save(role);
            }
            userRepository.save(user);
        }
        roleRepository.deleteByRoleName(roleName);
    }

    @Transactional
    public void deleteAuthority(String authName) {
        List<Authority> authorities = authorityRepository.findAllByAuthorityName(authName);
        List<Role> roles = roleRepository.findAll();
        //如果刪除被維護端的數據,則把用戶(維護端)的List中要移除的角色(被維護端)都remove掉
        for (Role role : roles) {
            List<Authority> roleAuthoritis = role.getAuthorities();
            for (Authority authority : authorities) {
                if (roleAuthoritis.contains(authority)) {
                    roleAuthoritis.remove(authority);
                }
            }
            roleRepository.save(role);
        }
        authorityRepository.deleteByAuthorityName(authName);
    }

}

4.controller層

controller層是控制層,其功能爲請求和響應控制,負責前後端交互,接受前端請求,調用service層,接收service層返回的數據,最後返回具體的頁面和數據到客戶端。

本層註解簡單解釋:

  1. @RestController
    Spring4之後新加入的註解,相當於@Controller + @ResponseBody。
    @Controller 將當前修飾的類注入SpringBoot IOC容器,使得從該類所在的項目跑起來的過程中,這個類就被實例化。當然也有語義化的作用,即代表該類是充當Controller的作用
    @ResponseBody 它的作用簡單來說說就是指該類中所有的API接口返回的數據,甭管你對應的方法返回Map或是其他Object,它會以Json字符串的形式返回給客戶端,根據嘗試,如果返回的是String類型,則仍然是String。

  2. @RequestMapping("/user")
    該註解用來處理請求地址的映射,可用於類或方法上。用於類上,表示類中的所有響應請求的方法都是以該地址作爲父路徑。

  3. @Autowired
    養成看源代碼的好習慣,在IDEA中按住Ctrl鍵點擊該註解,可以查看該註解的解析。我理解了一下,大概就是調用這個類的構造方法對這個類進行實例化操作。

  4. @RequestParam(value = “userName”)
    該註解可以獲取請求報文中的數據(數據一般以鍵值對方式傳輸),把然後把獲取到的數據複製給方法的參數中。例如上面就是獲取名爲"userName"的數據值。

再簡單介紹一下,增加用戶、角色和權限的操作。一般我們添加的時候,是先添加權限,再添加角色,最後添加角色(可以聯想一下,是不是先有權限才能給角色分配呀)。有些人會對如何關聯用戶和角色、角色和權限有疑惑(包括一開始的自己),在實體類中存在一個List對象,只要在其中添加對應的對象映射表中就會創建好映射關係。(可以看看下面添加的代碼,然後自己做實驗觀察現象)

只要這個不是你的第一個spring boot程序,相信你都看得懂。如果感覺功能不夠,讀者還可以自行添加。

  • EntityController
@RestController
@RequestMapping("/user")
public class EntityController {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private RoleRepository roleRepository;
    @Autowired
    private AuthorityRepository authorityRepository;
    @Autowired
    private EntityService entityService;

    /*
        用戶部分的增刪查改
     */
    @RequestMapping("/finduser")
    public List<User> findByName(@RequestParam(value = "userName") String userName) {
        return userRepository.findAllByUserName(userName);
    }

    @RequestMapping("/findalluser")
    public List<User> findAllUser() {
        return userRepository.findAll();
    }

    @RequestMapping("/adduser")
    public List<User> addUser(@RequestParam(value = "userName") String userName,
                              @RequestParam(value = "roleName") String roleName) {
        User user = new User();
        Role role = roleRepository.findAllByRoleName(roleName).get(0);
        user.setUserName(userName);
        user.setRoles(new ArrayList<>());
        user.getRoles().add(role);//給用戶設置權限
        userRepository.save(user);
        return userRepository.findAll();
    }

    /*
        給用戶添加角色
     */
    @RequestMapping("/adduserrole")
    public List<User> addUserRole(@RequestParam(value = "userName") String userName,
                              @RequestParam(value = "roleName") String roleName) {
        User user = userRepository.findAllByUserName(userName).get(0);
        Role role = roleRepository.findAllByRoleName(roleName).get(0);
        if (user.getRoles() == null) {
            user.setRoles(new ArrayList<>());
        }
        user.getRoles().add(role);//給用戶設置權限
        userRepository.save(user);
        return userRepository.findAll();
    }

    @RequestMapping("/deleteuser")
    public List<User> deleteUser(
            @RequestParam(value = "userName") String userName) {
        entityService.deleteUser(userName);
        return userRepository.findAll();
    }

    /*
        查詢用戶權限
     */
    @RequestMapping("/getauth")
    public Set<Authority> getAuthority(
            @RequestParam(value = "userName") String userName) {
        Set<Authority> authoritieSet = new HashSet<>();
        User user = userRepository.findAllByUserName(userName).get(0);
        for(Role role : user.getRoles()){
            for(Authority authority : role.getAuthorities()) {
                authoritieSet.add(authority);
            }
        }
        return authoritieSet;
    }

    /*
        角色部分的增刪查改
     */
    @RequestMapping("/findallrole")
    public List<Role> findAllRole() {
        return roleRepository.findAll();
    }

    @RequestMapping("/addrole")
    public List<Role> addRole(
            @RequestParam(value = "roleName") String roleName,
            @RequestParam(value = "authName") String authName) {
        Role role = new Role();
        Authority authority = authorityRepository.findAllByAuthorityName(authName).get(0);
        role.setRoleName(roleName);
        role.setAuthorities(new ArrayList<>());
        role.getAuthorities().add(authority);
        roleRepository.save(role);
        return roleRepository.findAll();
    }

    /*
        給角色添加權限
     */
    @RequestMapping("/addroleauth")
    public List<Role> addRoleAuth(
            @RequestParam(value = "roleName") String roleName,
            @RequestParam(value = "authName") String authName) {
        Role role = roleRepository.findAllByRoleName(roleName).get(0);
        Authority authority = authorityRepository.findAllByAuthorityName(authName).get(0);
        if (role.getAuthorities() == null) {
            role.setAuthorities(new ArrayList<>());
        }
        role.getAuthorities().add(authority);
        roleRepository.save(role);
        return roleRepository.findAll();
    }

    @RequestMapping("/deleterole")
    public List<Role> deleteRole(
            @RequestParam(value = "roleName") String roleName) {
        entityService.deleteRole(roleName);
        return roleRepository.findAll();
    }

    /*
        權限部分的增刪查改
     */
    @RequestMapping("/findallauth")
    public List<Authority> findAllAuthority() {
        return authorityRepository.findAll();
    }

    @RequestMapping("/addauth")
    public List<Authority> addAuthority(
            @RequestParam(value = "authName" ) String authName) {
        Authority authority = new Authority();
        authority.setAuthorityName(authName);
        authorityRepository.save(authority);
        return authorityRepository.findAll();
    }

    @RequestMapping("/deleteauth")
    public List<Authority> deletAuthority(
            @RequestParam(value = "authName") String authName) {
        entityService.deleteAuthority(authName);
        return authorityRepository.findAll();
    }
}

五、運行效果

寫得函數有點多,這裏挑選一部分來演示吧。

  • 數據表
    在程序運行之後,它會自動爲我們在數據庫中創建5張表,其中包括3個實體對應的數據表以及2張映射表。
    在這裏插入圖片描述

  • 查詢操作
    由於先前已經進行了一些實驗,數據表中已經有了少量的數據,所以我們就現在演示查詢吧。
    首先按照上文說的添加順序,先是權限的查詢。
    在這裏插入圖片描述
    接着是角色的查詢:
    在這裏插入圖片描述
    接着是用戶查詢:
    在這裏插入圖片描述
    最後,我們通過用戶名來查詢他擁有的權限。
    在這裏插入圖片描述

  • 增加角色操作
    添加權限的操作很常規不做演示,添加用戶的操作和添加角色的操作差不多可以借鑑。
    在這裏插入圖片描述

六、參考資料

  1. https://www.cnblogs.com/hhhshct/p/9492741.html
  2. https://liuyanzhao.com/7913.html
  3. https://blog.csdn.net/lidai352710967/article/details/83509821
  4. https://blog.csdn.net/H_Shun/article/details/78013017
  5. https://blog.csdn.net/Just_learn_more/article/details/90665009
  6. https://www.sojson.com/blog/295.html
  7. https://www.jianshu.com/p/6bbb5748ac83
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章