一.提出问题
因为在本项目中使用了Shiro来实现用户身份认证,权限授权,加密,会话管理等,简单说就是对页面访问权限进行控制。
Shiro权限控制是在用户登录时会在Realm中添加该用户的权限信息,在登录的时候会根据请求的url和相关的权限做映射。在用户请求具体url时,会根据url获得对应的权限,在到过滤器中做权限的校验。
然而我在页面权限配置过程中发现Shiro本身自带的权限过滤器只能实现权限之间的与操作,示例如下:
<!-- 权限配置 -->
<property name="filterChainDefinitions">
<value>
<!-- 限制权限访问 -->
/page/**=authc,perms[common,system]
/page/settingEdit=authc,perms[system]
/**=authc
</value>
</property>
上述配置的本意是使得 /page/** 下的url只需common,system这两个权限之中任意一个即可访问,而/page/settingEdit则需要system权限进行访问,达到页面访问权限控制的效果。
但由于上面的perms是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;
}
导致实际上/page/**下的url需要两个权限同时具备方可成功访问,于是我们需要自定义一个权限过滤器来替代Shiro原本的权限过滤器。
二.解决问题--自定义Shiro权限过滤器
1.新建一个NewPermissionsAuthorizationFilter类
package com.shiro;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class NewPermissionsAuthorizationFilter extends PermissionsAuthorizationFilter {
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
Subject subject = this.getSubject(request, response);
if (subject.getPrincipal() == null) {
this.saveRequestAndRedirectToLogin(request, response);
} else {
// 强转为HTTP
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse)response;
// 获取响应头中ajax
String header = req.getHeader("X-Requested-With");
// 如果是ajax请求
if ("XMLHttpRequest".equals(header)){
// 设置响应头
resp.setContentType("application/json;charset=UTF-8");
resp.getWriter().print(false);
}
String unauthorizedUrl = this.getUnauthorizedUrl();
if (StringUtils.hasText(unauthorizedUrl)) {
WebUtils.issueRedirect(request, response, unauthorizedUrl);
} else {
WebUtils.toHttp(response).sendError(401);
}
}
return false;
}
@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;
}
}
2.在Shiro的配置文件中注入bean
<!--自定义权限过滤器-->
<bean id="newPermissionsAuthorizationFilter" class="com.shiro.NewPermissionsAuthorizationFilter"/>
3.在shiroFilter中添加配置
<!--配置shiro自定义过滤器-->
<property name="filters">
<map>
<!--自定义过滤器名字newPerms-->
<entry key="newPerms" value-ref="newPermissionsAuthorizationFilter"/>
</map>
</property>
最终效果:
- 保持“,”表示多个权限的并列关系,每个“,”分割的字符串可能是多个“或”权限的集合,再用“|”分割字符串,“|”需要转义。即在原有的基础上增加了权限的或操作。
还有最后一步:
4.在页面权限配置中使用自定义权限过滤器newPerms
<!-- 权限配置 -->
<property name="filterChainDefinitions">
<value>
<!-- 限制权限访问 -->
/page/**=authc,newPerms[common|system]
/page/settingEdit=authc,newPerms[system]
/**=authc
</value>
</property>
三.总结
只要多动手多动脑,没有什么问题是解决不了的。