天天看點

Shiro - 權限控制

在上一篇《Shiro – 登陸驗證》中我們在自定義的UserRealm中繼承了抽象類AuthorizingRealm,實作該類中的doGetAuthenticationInfo方法完成了登陸驗證功能的實作。這次我們繼續實作另一個方法doGetAuthorizationInfo()來實作權限控制功能。

授權也稱為通路控制,是管理資源通路的過程。即根據不同使用者的權限判斷其是否有通路相應資源的權限。

在Shiro中,權限控制有三個核心的元素:權限,角色和使用者。

資料庫相關表設計

在這裡,我們使用RBAC(Role-Based Access Control,基于角色的通路控制)模型設計使用者,角色和權限間的關系。簡單地說,一個使用者擁有若幹角色,每一個角色擁有若幹權限。這樣,就構造成“使用者-角色-權限”的授權模型。

在這種模型中,使用者與角色之間,角色與權限之間,一般者是多對多的關系。

根據以上模型設計如下簡易表結構

Shiro - 權限控制

接下來我們來實作Realm的doGetAuthorizationInfo()方法。

@Slf4j
public class ShiroRealm extends AuthorizingRealm {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private UserRoleMapper userRoleMapper;
    @Autowired
    private UserPermissionMapper userPermissionMapper;

    /**
     * 擷取使用者角色和權限
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        String userName = user.getUserName();

        System.out.println("使用者" + userName + "擷取權限-----ShiroRealm.doGetAuthorizationInfo");
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

        // 擷取使用者角色集
        List<Role> roleList = userRoleMapper.findByUserName(userName);
        Set<String> roleSet = new HashSet<String>();
        for (Role r : roleList) {
            roleSet.add(r.getName());
        }
        simpleAuthorizationInfo.setRoles(roleSet);

        // 擷取使用者權限集
        List<Permission> permissionList = userPermissionMapper.findByUserName(userName);
        Set<String> permissionSet = new HashSet<String>();
        for (Permission p : permissionList) {
            permissionSet.add(p.getName());
        }
        simpleAuthorizationInfo.setStringPermissions(permissionSet);
        return simpleAuthorizationInfo;
    }
}
           

這裡有兩個比較重要的查詢接口

// 擷取使用者角色集
List<Role> roleList = userRoleMapper.findByUserName(userName);
Set<String> roleSet = new HashSet<String>();
for (Role r : roleList) {
	roleSet.add(r.getName());
}
simpleAuthorizationInfo.setRoles(roleSet);

// 擷取使用者權限集
List<Permission> permissionList = userPermissionMapper.findByUserName(userName);
Set<String> permissionSet = new HashSet<String>();
for (Permission p : permissionList) {
	permissionSet.add(p.getName());
}
simpleAuthorizationInfo.setStringPermissions(permissionSet);
return simpleAuthorizationInfo;
           

将目前使用者的角色集和權限集注入到Shiro中。

接下來我們來看下ShiroConfig中的配置,有什麼變化;

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)
           

要想使用這些注解,需要在ShiroConfig加上如下配置

/**
 * Shiro 權限相關注解 使用
 * @return
 */
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
	DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
	advisorAutoProxyCreator.setProxyTargetClass(true);
	return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
	AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
	authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
	return authorizationAttributeSourceAdvisor;
}
           

配置完成之後我們就可以直接使用這些注解了。

@Controller
@RequestMapping("/user")
public class UserController {

    @RequiresPermissions("user:user")
    @RequestMapping("list")
    public String userList(Model model) {
        model.addAttribute("value", "擷取使用者資訊");
        return "user";
    }
    
    @RequiresPermissions("user:add")
    @RequestMapping("add")
    public String userAdd(Model model) {
        model.addAttribute("value", "新增使用者");
        return "user";
    }
    
    @RequiresPermissions("user:delete")
    @RequestMapping("delete")
    public String userDelete(Model model) {
        model.addAttribute("value", "删除使用者");
        return "user";
    }
}
           

本以為在ShiroConfig中配置了

shiroFilterFactoryBean.setUnauthorizedUrl("/403");

,當通路的時候,沒有權限的通路會自動重定向到/403,結果證明并不是這樣。後來研究發現,該設定隻對filterChain起作用,比如在filterChain中設定了

filterChainDefinitionMap.put("/user/update", "perms[user:update]");

,如果使用者沒有

user:update

權限,那麼當其通路

/user/update

的時候,頁面會被重定向到/403。

相關解釋可參考:https://www.cnblogs.com/qlong8807/p/5524381.html

針對以上問題,我們可以定義一個全局異常捕獲類

@ControllerAdvice
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler {
    @ExceptionHandler(value = AuthorizationException.class)
    public String handleAuthorizationException() {
        return "403";
    }
}
           

參考:https://mrbird.cc/Spring-Boot-Shiro%20Authorization.html

繼續閱讀