一、問題產生
在項目開發過程中,安全對於後臺管理很重要。shiro是一個比較常流行的安全框架,網上關於shiro的使用和實現原理也比較全面。這裏不做詳細介紹,在項目中的權限配置會有各種不同的需求,例如有的url需要用戶擁有多個權限中的一個權限就能夠訪問,這個就要自己編寫攔截器的規則。
二、具體場景
自定義的攔截器沒有重寫PermissionsAuthorizationFilter的isAccessAllowed方法,使用的權限控制的默認的攔截器。yml文件的全蠍配置如下:
- /user/getStatis-->e-perms[green,user]
- /user/findUserByNameAndTime-->e-perms[air,user,wallet]
- /greenplant/findTreesType-->e-perms[seeds,plants]
- /wallet/findWalletByCondition-->e-perms[user,wallet]
例如:/user/getStatis的業務需求是擁有green或者user權限的用戶都要有權限能調用該接口。但是上面的配置導致無論是擁有green或者user權限的用戶都不能訪問該接口。
三、原因分析
shiro權限控制是在用戶登錄時會再realm中增加該用戶的權限信息,在登錄的時候會根據請求的url和相關的權限做映射。在用戶請求具體url時,會根據url獲得對應的權限,在到攔截器中做權限的校驗。上面配置不生效的原因是因爲shiro的下面代碼,該代碼是在PermissionsAuthorizationFilter中,默認的權限校驗規則。
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
Subject subject = this.getSubject(request, response);
String[] perms = (String[])((String[])mappedValue);
boolean isPermitted = true;
if (perms != null && perms.length > 0) {
if (perms.length == 1) {
if (!subject.isPermitted(perms[0])) {
isPermitted = false;
}
} else if (!subject.isPermittedAll(perms)) {
isPermitted = false;
}
}
return isPermitted;
}
從上面的代碼可以看出,我們的配置會默認被搶轉爲string類型的字符串數組。當只有一個權限時,會直接判斷有沒有該權限;當配置多個權限時,從下面的代碼可以看出只用在請求url的用戶擁有所有的權限時,纔會返回true,否則就會被拒絕訪問。
protected boolean isPermittedAll(Collection<Permission> permissions, AuthorizationInfo info) {
if (permissions != null && !permissions.isEmpty()) {
Iterator var3 = permissions.iterator();
while(var3.hasNext()) {
Permission p = (Permission)var3.next();
if (!this.isPermitted(p, info)) {
return false;
}
}
}
return true;
}
四、問題解決
shiro源碼只考慮了權限的和的問題沒有通過配置解決權限的或的問題。因此爲了滿足業務需求,在自定義攔截器中需要重寫PermissionsAuthorizationFilter的isAccessAllowed方法。爲了滿足“和”和“或”的不同權限配置的需求,對isAccessAllowed方法做如下修改:
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
Subject subject = this.getSubject(request, response);
String[] perms = (String[]) ((String[]) mappedValue);
boolean isPermitted = true;
if (perms != null && perms.length > 0) {
if (perms.length == 1) {
if (!isOneOfPermitted(perms[0], subject)) {
isPermitted = false;
}
} else if (!isAllPermitted(perms,subject)) {
isPermitted = false;
}
}
return isPermitted;
}
/**
* 以“,”分割的權限爲並列關係的權限控制,分別對每個權限字符串進行“|”分割解析
* 若並列關係的權限有一個不滿足則返回false
*
* @param permStrArray 以","分割的權限集合
* @param subject 當前用戶的登錄信息
* @return 是否擁有該權限
*/
private boolean isAllPermitted(String[] permStrArray, Subject subject) {
boolean isPermitted = true;
for (int index = 0, len = permStrArray.length; index < len; index++) {
if (!isOneOfPermitted(permStrArray[index], subject)) {
isPermitted = false;
}
}
return isPermitted;
}
/**
* 判斷以“|”分割的權限有一個滿足的就返回true,表示權限的或者關係
*
* @param permStr 權限數組種中的一個字符串
* @param subject 當前用戶信息
* @return 是否有權限
*/
private boolean isOneOfPermitted(String permStr, Subject subject) {
boolean isPermitted = false;
String[] permArr = permStr.split("\\|");
if (permArr.length > 0) {
for (int index = 0, len = permArr.length; index < len; index++) {
if (subject.isPermitted(permArr[index])) {
isPermitted = true;
}
}
}
return isPermitted;
}
方法做了上述修改,保持“,”表示多個權限的並列關係,每個“,”分割的字符串可能是多個“或”權限的集合,再用“|”分割字符串,“|”需要轉義。修改代碼後,權限配置做了如下修改:
- /user/getStatis-->e-perms[green|user]
- /user/findUserByNameAndTime-->e-perms[air|user|wallet]
- /greenplant/findTreesType-->e-perms[seeds|plants]
- /wallet/findWalletByCondition-->e-perms[user|wallet]
這樣就能是使得擁有green或者user權限的用戶都能成功的訪問 /user/getStatis接口了,同時兼容了shiro自帶的權限檢驗規則。