SpringBoot-Shiro權限控制( 3 )

SpringBoot-Shiro權限控制(2019.12.13)

在《Spring-Boot-shiro用戶認證》中,我們通過繼承AuthorizingRealm抽象類實現了doGetAuthenticationInfo()方法完成了用戶認證操作。接下來繼續實現doGetAuthorizationInfo()方法完成Shiro的權限控制功能。

授權也稱爲訪問控制,是管理資源訪問的過程。即根據不同用戶的權限判斷其是否有訪問相應資源的權限。在Shiro中,權限控制有三個核心的元素:權限,角色和用戶。(也稱爲權限3要素)

庫的模型分爲 : 用戶表 角色表 權限表 (3者都是多對多關係通過中間表關聯)

創建了五張表:用戶表USER、角色表ROLE、用戶角色關聯表USER_ROLE、權限表PERMISSION和權限角色關聯表ROLE_PERMISSION。用戶love角色爲admin,用戶tester角色爲test。admin角色擁有用戶的所有權限(user:user,user:add,user:delete),而test角色只擁有用戶的查看權限(user:user)。密碼都是321,沒經過Shiro提供的MD5加密。

1. Dao層

創建兩個實體類,對應用戶角色表Role和用戶權限表Permission:

@Data
public class Role implements Serializable {
    private static final long serialVersionUID = -66L;
    private Integer id;
    private String name;
    private String memo;
}
-----------------------------------------------------------------------------
@Data
public class Permission implements Serializable {
    private static final long serialVersionUID = -66L;
    private Integer id;
    /**
     * 權限路徑
     */
    private String url;
    /**
     * 權限名稱
     */
    private String name;
}

創建兩個dao接口,分別根據用戶名查詢用戶擁有所有角色和用戶擁有的所有權限:

UserRoleMapper.java

@Mapper
public interface UserRoleMapper {
    /** 
     * 根據用戶名查詢用戶擁有所有角色 
     *
     * @param username 
     * @return java.util.List<com.zhihao.entity.Role> 
     * @author: zhihao
     * @date: 2019/12/13 
     */
    List<Role> findRoleByUserName(String username);
}

UserRoleMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.zhihao.dao.UserRoleMapper">
  <select id="findRoleByUserName" resultType="com.zhihao.entity.Role">
    SELECT ro.* FROM `user` u
    LEFT JOIN user_role uro ON u.id=uro.user_id
    RIGHT JOIN role ro ON ro.id = uro.rid WHERE u.username = #{username}
  </select>
</mapper>

UserPermissionMapper.java

@Mapper
public interface UserPermissionMapper {
    /**
     *  根據用戶名 查詢出該用戶擁有的所有權限
     *
     * @param username 用戶名
     * @return java.util.List<com.zhihao.entity.Permission>
     * @author: zhihao
     * @date: 2019/12/13
     * {@link #}
     */
    List<Permission> findPermissionByUserName(String username);
}

UserPermissionMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.zhihao.dao.UserPermissionMapper">
  <select id="findPermissionByUserName" resultType="com.zhihao.entity.Permission">
  SELECT per.* FROM `permission` per 
   LEFT JOIN role__permission rope ON per.id=rope.pid 
   LEFT JOIN role ro ON ro.id=rope.rid 
   LEFT JOIN user_role uro ON uro.rid=ro.id 
   LEFT JOIN `user` u ON u.id=uro.user_id WHERE u.username = #{username};
  </select>
</mapper>

2.對ShiroRealm.java進行改造。

在Shiro中,用戶角色和權限的獲取是在ShiroRealm的doGetAuthorizationInfo()方法中實現的,所以接下來手動實現該方法:

	/**
     * 獲取用戶角色和權限
     *
     * @param principal
     * @return org.apache.shiro.authz.AuthorizationInfo
     * @author: zhihao
     * @date: 2019/12/13
     * {@link #}
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
        //通過shiro工具獲取登錄後的user對象,獲取到用戶名
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        String username = user.getUsername();
        //創建授權對象進行封裝角色和權限信息進去進行返回 注意不是SimpleAuthenticationInfo
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //獲取用戶角色集
        List<Role> roleList = roleService.findRoleByUserName(username);
        Set<String> roleSet = new HashSet<>();
        for (Role role : roleList) {
            roleSet.add(role.getName());
        }
        System.out.println("用戶擁有的角色>>>"+roleSet);
        //添加角色進角色授權
        info.setRoles(roleSet);
        List<Permission> permissionList = permissionService.findPermissionByUserName(username);
        Set<String> permissionSet = new HashSet<>();
        for (Permission permission : permissionList) {
            permissionSet.add(permission.getName());
        }
        System.out.println("用戶擁有的權限>>>"+permissionSet);
        //添加權限進權限授權
        info.setStringPermissions(permissionSet);
        return info;
    }
//..下面是之前的登錄認證

在上述代碼中,我們通過方法獲取了當前登錄用戶的角色和權限集,然後保存到SimpleAuthorizationInfo對象中,並返回給Shiro,這樣Shiro中就存儲了當前用戶的角色和權限信息了。

3. 對ShiroConfig.java進行改造配置。

要使用權限相關的註解必須要在配置中開啓註解的使用,需要在ShiroConfig中添加如下配置:

	/**
     * 開啓shiro認證註解
     *
     * @param securityManager
     * @return AuthorizationAttributeSourceAdvisor
     * @author: zhihao
     * @date: 2019/12/13
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

Shiro爲我們提供了一些和權限相關的註解,如下所示:

// 表示當前Subject已經通過login進行了身份驗證;即Subject.isAuthenticated()返回true。
@RequiresAuthentication  
 
// 表示當前Subject已經身份驗證或者通過記住我登錄的。
@RequiresUser  

// 表示當前Subject沒有身份驗證或通過記住我登錄過,即是遊客身份。
@RequiresGuest  

// 表示當前Subject需要角色admin和user。  
@RequiresRoles(value={"admin", "user"}, logical= Logical.AND)  

// 表示當前Subject需要權限user:a或user:b。
@RequiresPermissions (value={"user:a", "user:b"}, logical= Logical.OR)

4. 編寫一個權限控制的UserController來測試權限

@RestController
public class UserController {
    /**
     * 模擬從數據庫獲取了全部用戶
     *
     * @return java.lang.String
     * @author: zhihao
     * @date: 2019/12/13
     */
    @RequiresPermissions(value = "user:user") //使用shiro權限註解標明,只能擁有這個user:user權限的用戶訪問
    @RequestMapping("/list")
    public String getUserList(){
        String userList ="擁有獲取全部用戶的權限 ``user:user``";
        return userList;
    }

    @RequiresPermissions(value = "user:add")
    @RequestMapping("/add")
    public String addUser(){
        String userList ="擁有添加用戶權限 ``user:add``";
        return userList;
    }
    
    @RequiresPermissions(value = "user:delete")
    @RequestMapping("/delete")
    public String deleteUser(){
        String userList ="擁有刪除用戶權限 ``user:delete``";
        return userList;
    }
}

5. 在LoginController中添加一個/403跳轉:

	@GetMapping("/403")
    public ModelAndView forbid() {
        ModelAndView view = new ModelAndView();
        view.setViewName("403");
        return view;
    }

6. 測試沒有權限用戶後端報AuthorizationException異常,

在測試中發現使用沒有權限的用戶訪問後臺拋出了異常:org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method:…異常!!!

本以爲在ShiroConfig中配置了shiroFilterFactoryBean.setUnauthorizedUrl("/403");,沒有權限的訪問會自動重定向到/403,結果證明並不是這樣。後來研究發現,該設置只對filterChain起作用,比如在filterChain中設置了filterChainDefinitionMap.put("/user/update", "perms[user:update]");,如果用戶沒有user:update權限,那麼當其訪問/user/update的時候,頁面會被重定向到/403。

對應以上問題可以有2種解決方法:

第一種: 定義一個全局異常處理進行處理:

@ControllerAdvice
@Order(value = Ordered.HIGHEST_PRECEDENCE) //最高精度,拋出的異常符合這個異常就進來
public class GlobalExceptionHandler {
    @ExceptionHandler(value = AuthorizationException.class)
    public String handleAuthorizationException() {
        return "403";
    }
}

第二種在配置類中配置個異常解析器

	/**
     * 異常解析器
     *
     * @return org.springframework.web.servlet.handler.SimpleMappingExceptionResolver
     * @author: zhihao
     * @date: 2019/12/13
     * {@link #}
     */
	@Bean
    public SimpleMappingExceptionResolver  simpleMappingExceptionResolver(){
        SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
        Properties properties = new Properties();
        //攔截到AuthorizationException 就跳轉到resources資源下的 如果帶文件夾需要加上文件夾: /xxx/403模板頁面
        properties.setProperty("AuthorizationException", "/403");
        resolver.setExceptionMappings(properties);
        return resolver;
    }

最後發現無權限訪問後正常到403頁面了!

擴展資料:

建表語句: 在項目代碼中

項目代碼(點擊打開)

發佈了29 篇原創文章 · 獲贊 3 · 訪問量 1610
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章