shiro可以与web进行集成,通过一个ShiroFilter入口来拦截需要安全控制的URL。ShiroFilter通过aop对servlet的filter进行增强,因此先调用shiroFilter对请求进行第一步过滤。我们可以自定义拦截器,在拦截器中调用subject.login()方法进行用户身份验证,通过身份验证的则放行。通过给controller的接口添加requirePermission()
,对登录用户的权限进行检查,看是否满足接口权限的要求,满足了才允许使用某接口。
URL书写规则:
url = 拦截器[参数]
- anon拦截器:匿名访问,不需要登录即可访问
- authc拦截器:需要身份认证通过后才能访问
- roles[admin]拦截器:需要admin角色授权才能访问
url路径支持通配符,注意通配符匹配不包括目录分隔符 “/”:
- ?:匹配一个字符,如”/admin?” 将匹配 / admin1、admin2,但不匹配 / admin 或 / admin123;
- *:匹配零个或多个字符串,如 / admin * 将匹配 / admin、/admin123,但不匹配 / admin/1;
- :匹配路径中的零个或多个路径,如 / admin/ 将匹配 / admin/a 或 / admin/a/b。
url模式匹配顺序
按照在配置中的声明顺序,即从头开始使用第一个匹配的url模式对应的拦截器,组成一条由url顺序规定的拦截器链。注意,如果先声明的url包含了后声明的url,那么后面的url就要使用前面url对应的拦截器。
拦截器链
Shiro 对 Servlet 容器的 FilterChain 进行了代理,即 ShiroFilter 在继续 Servlet 容器的 Filter 链的执行之前,通过 ProxiedFilterChain 对 Servlet 容器的 FilterChain 进行了代理;即先走 Shiro 自己的 Filter 体系,然后才会委托给 Servlet 容器的 FilterChain 进行 Servlet 容器级别的 Filter 链执行;Shiro 的 ProxiedFilterChain 执行流程:1、先执行 Shiro 自己的 Filter 链;2、再执行 Servlet 容器的 Filter 链(即原始的 Filter)。
swagger 放行
filterChainDefinitionMap.put("/redis/login", "anon");
// swagger 默认通过
filterChainDefinitionMap.put("/v2/**", "anon");
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
filterChainDefinitionMap.put("/captcha.jpg", "anon");
filterChainDefinitionMap.put("/csrf","anon");
filterChainDefinitionMap.put("/redis/user/*","token,authc");
filterChainDefinitionMap.put("/**","token,authc");
如何判断请求能否和指定的url pattern匹配
我们进入ShiroFilterFactoryBean
,找到createInstance()
中的PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
,进入后找到:
这里的pathMatches()
方法对我们定义的pattern
和请求path进行匹配,如果不确定url pattern写的是否正确可以来这里检查一下。
自定义过滤器
自定义过滤器继承自抽象类AccessControlFilter
,需要重写isAccessAllowed()
和onAccessAllowed()
两个抽象方法,AccessControlFilter
中定义了onPreHandle()
方法如下:
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return this.isAccessAllowed(request, response, mappedValue) || this.onAccessDenied(request, response, mappedValue);
}
或连接符左侧如果返回true,默认请求允许访问,可以去filter链上找下一个过滤器处理;如果左侧返回false,执行右侧方法,最终返回true则继续拦截器链的执行,直接返回false表示自己已经处理了。
在shiroConfig配置文件中,我们通过setFilter()
方法将自定义拦截器传给shiroFilterFactoryBean,并配置拦截url和对应过滤器。启动应用后,根据访问的url对应的过滤器进行过滤操作,因为我们自定义的过滤器中做了subject.login()
登录认证操作,因此通过自定义过滤器的请求都是已经登录了的请求,未登录请求是无法通过过滤器的。
shiro授权流程
调用subject.checkRole()
或者后端授权注解@RequiresPermissions()
时,shiro需要判断subject是否有某角色或权限,首先会到SecurityManager
中:
之后到ModularRealmAuthorizer
:
流转到 AuthorizingRealm 执行授权:
进入getAuthorizationInfo()
获取用户的权限:
protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
return null;
} else {
AuthorizationInfo info = null;
if (log.isTraceEnabled()) {
log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");
}
// 拿到缓存实例
Cache<Object, AuthorizationInfo> cache = this.getAvailableAuthorizationCache();
Object key;
if (cache != null) {
if (log.isTraceEnabled()) {
log.trace("Attempting to retrieve the AuthorizationInfo from cache.");
}
// 调用缓存获取用户拥有的权限信息
key = this.getAuthorizationCacheKey(principals);
info = (AuthorizationInfo)cache.get(key);
if (log.isTraceEnabled()) {
if (info == null) {
log.trace("No AuthorizationInfo found in cache for principals [" + principals + "]");
} else {
log.trace("AuthorizationInfo found in cache for principals [" + principals + "]");
}
}
}
// 缓存没拿到用户的权限信息,调用自定义customRealm的doget方法获取安全数据源中的权限信息
if (info == null) {
info = this.doGetAuthorizationInfo(principals);
if (info != null && cache != null) {
if (log.isTraceEnabled()) {
log.trace("Caching authorization info for principals: [" + principals + "].");
}
key = this.getAuthorizationCacheKey(principals);
// 拿到之后存入缓存
cache.put(key, info);
}
}
return info;
}
}
最后用用户所拥有的权限和接口所需要的权限进行对比,循环匹配,匹配成功说明该用户拥有权限,否则抛出subject does not have permission
异常: