基于shiro的改造集成真正支持restful请求
这个模块分离至上上上一篇api权限管理系统与前后端分离实践,感觉那样太长了找不到重点,分离出来要好点。
首先说明设计的这个安全体系是是RBAC(基于角色的权限访问控制)授权模型,即用户--角色--资源
,用户不直接和权限打交道,角色拥有资源,用户拥有这个角色就有权使用角色所用户的资源。所有这里没有权限一说,签发jwt里面也就只有用户所拥有的角色而没有权限。
为啥说是真正的restful风格集成,虽说shiro对rest不友好但他本身是有支持rest集成的filter–HttpMethodPermissionFilter
,这个shiro rest的 风格拦截器,会自动根据请求方法构建权限字符串( GET=read,POST=create,PUT=update,DELETE=delete)
构建权限字符串;eg: /users=rest[user]
, 会 自动拼接出user:read,user:create,user:update,user:delete
”权限字符串进行权限匹配(所有都得匹配,isPermittedAll)。
但是这样感觉不利于基于jwt的角色的权限控制,在细粒度上验权url(即支持get,post,delete鉴别)就更没法了(个人见解)。打个比方:我们对一个用户签发的jwt写入角色列(role_admin,role_customer)。对不同request请求:url="api/resource/",httpMethod="GET"
,url="api/resource",httpMethod="POST"
,在基于角色-资源的授权模型中,这两个url相同的请求对HttpMethodPermissionFilter是一种请求,用户对应的角色拥有的资源url=”api/resource”,只要请求的url是”api/resource”,不论它的请求方式是什么,都会判定通过这个请求,这在restful风格的api中肯定是不可取的,对同一资源有些角色可能只要查询的权限而没有修改增加的权限。
可能会说在jwt中再增加权限列就好了嘛,但是在基于用户-资源的授权模型中,虽然能判别是不同的请求,但是太麻烦了,对每个资源我们都要设计对应的权限列然后再塞入到jwt中,对每个用户都要单独授权资源这也是不可取的。
对shiro的改造这里自定义了一些规则:
shiro过滤器链的url=url+"=="+httpMethod
eg:对于url="api/resource/",httpMethod="GET"
的资源,其拼接出来的过滤器链匹配url=api/resource==GET
这样对相同的url而不同的访问方式,会判定为不同的资源,即资源不再简单是url,而是url和httpMethod的组合。基于角色的授权模型中,角色所拥有的资源形式为url+"=="+httpMethod
。
这里改变了过滤器的过滤匹配url规则,重写PathMatchingFilterChainResolver的getChain方法,增加对上述规则的url的支持。
/* *
* @Author tomsun28
* @Description
* @Date 21:12 2018/4/20
*/
public class RestPathMatchingFilterChainResolver extends PathMatchingFilterChainResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(RestPathMatchingFilterChainResolver.class);
public RestPathMatchingFilterChainResolver() {
super();
}
public RestPathMatchingFilterChainResolver(FilterConfig filterConfig) {
super(filterConfig);
}
/* *
* @Description 重写filterChain匹配
* @Param [request, response, originalChain]
* @Return javax.servlet.FilterChain
*/
@Override
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
FilterChainManager filterChainManager = this.getFilterChainManager();
if (!filterChainManager.hasChains()) {
return null;
} else {
String requestURI = this.getPathWithinApplication(request);
Iterator var6 = filterChainManager.getChainNames().iterator();
String pathPattern;
boolean flag = true;
String[] strings = null;
do {
if (!var6.hasNext()) {
return null;
}
pathPattern = (String)var6.next();
strings = pathPattern.split("==");
if (strings.length == 2) {
// 分割出url+httpMethod,判断httpMethod和request请求的method是否一致,不一致直接false
if (WebUtils.toHttp(request).getMethod().toUpperCase().equals(strings[1].toUpperCase())) {
flag = false;
} else {
flag = true;
}
} else {
flag = false;
}
pathPattern = strings[0];
} while(!this.pathMatches(pathPattern, requestURI) || flag);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "]. Utilizing corresponding filter chain...");
}
if (strings.length == 2) {
pathPattern = pathPattern.concat("==").concat(WebUtils.toHttp(request).getMethod().toUpperCase());
}
return filterChainManager.proxy(originalChain, pathPattern);
}
}
}
重写PathMatchingFilter的路径匹配方法pathsMatch(),加入httpMethod支持。
/* *
* @Author tomsun28
* @Description 重写过滤链路径匹配规则,增加REST风格post,get.delete,put..支持
* @Date 23:37 2018/4/19
*/
public abstract class BPathMatchingFilter extends PathMatchingFilter {
public BPathMatchingFilter() {
}
/* *
* @Description 重写URL匹配 加入httpMethod支持
* @Param [path, request]
* @Return boolean
*/
@Override
protected boolean pathsMatch(String path, ServletRequest request) {
String requestURI = this