【Shiro】Shiro 的学习教程(四)之 SpringBoot 集成 Shiro 原理
目录
- 1、第一阶段:启动服务,构建类的功能
- 2、第二阶段:正式请求
1、第一阶段:启动服务,构建类的功能
查看 Shiro 配置类 ShiroConfiguration
:
@Configuration
public class ShiroConfiguration {
// 创建 shiroFilter,负责拦截所有请求
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//给filter设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
//配置系统受限资源
//配置系统公共资源
Map<String,String> map = new HashMap<>();
// authc 请求这个资源需要认证和授权
map.put("/login", "anon");
map.put("/register", "anon");
map.put("/user/register", "anon");
map.put("/user/login", "anon");
map.put("/index", "authc");
//默认认证界面路径
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
}
配置了 ShiroFilterFactoryBean
查看 ShiroFilterFactoryBean
:实现了 FactoryBean
接口
public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {
private SecurityManager securityManager;
// 用于自定义的 filter
private Map<String, Filter> filters = new LinkedHashMap();
// 用于自定义的过滤器链
private Map<String, String> filterChainDefinitionMap = new LinkedHashMap();
// 设置全局的 url
private String loginUrl;
private String successUrl;
private String unauthorizedUrl;
// 生成的实例
private AbstractShiroFilter instance;
// ...
public Object getObject() throws Exception {
if (this.instance == null) {
this.instance = this.createInstance();
}
return this.instance;
}
protected AbstractShiroFilter createInstance() throws Exception {
log.debug("Creating Shiro Filter instance.");
FilterChainManager manager = this.createFilterChainManager();
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);
return new ShiroFilterFactoryBean.SpringShiroFilter((WebSecurityManager)securityManager, chainResolver);
}
// ...
}
Map<String, Filter> filters
:key 是过滤器名称,value 是Filter
Map<String, String> filterChainDefinitionMap
:key 是 uri,value 是过滤器名称
Spring 容器会自动检测到 FactoryBean
接口,并调用其 getObject()
方法获取实际的 bean 对象,所以,最终会调用 createInstance()
方法。
createInstance()
方法:创建FilterChainManager
对象、PathMatchingFilterChainResolver
(PathMatchingFilterChainResolver
设置了FilterChainManager
)对象,再创建了内部类SpringShiroFilter
对象
this.createFilterChainManager()
:创建FilterChainManager
。- 创建
DefaultFilterChainManager
实例,并将默认过滤器、自定义过滤器存放到属性 Map<String, Filter> filters ; - 并给所有的过滤器设置
url
:loginUrl
、successUrl
、unauthorizedUrl
- 构造过滤器链,并存放到属性 Map<String, NamedFilterList> filterChains,其中,key 为
uri
,value 为NamedFilterList (extends List<Filter>)
;并将 uri 存放在PathMatchingFilter
类的 Map<String, Object> appliedPaths 属性
- 创建
createFilterChainManager()
方法:
①:new DefaultFilterChainManager()
:管理 过滤器 filters(包括默认过滤器 + 自定义过滤器)、过滤器链
在构造器中,将默认的过滤器添加到 filters
属性中
默认过滤器 DefaultFilter
:枚举类
public enum DefaultFilter {
anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
authcBearer(BearerHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class);
}
②:applyGlobalPropertiesIfNecessary(filter)
:设置 loginUrl
、successUrl
、unauthorizedUrl
以 applyLoginUrlIfNecessary()
方法为例:
如果 ShiroFilterFactoryBean#loginUrl
有值且 AccessControlFilter#loginUrl
的值为默认值(/login.jsp
),那么就会给 AccessControlFilter#loginUrl
设置值
③:处理自定义的过滤器 filter
:将自定义过滤器添加到 DefaultFilterChainManager
中
④:getFilterChainDefinitionMap() & createChain()
:构造过滤器链
createChain()
方法:
注意下面这行代码:
String[] filterTokens = this.splitChainDefinition(chainDefinition);
将一个字符串可以转化为字符串数组,就可猜测是不是可以配置多个过滤器 map.put("/login", "anon, authc");
对此数组进行 for 循环:
String[] nameConfigPair = this.toNameConfigPair(token);
this.addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);
又将一个字符串 token 转化为了一个 字符串数组,且通过 "["
(toNameConfigPair
内部实现)将数组分为了两部分
所以,最终可以配置成:
map.put("/login", "authc, roles[admin,user], perms[file:edit]");
addToChain()
方法:生成带有 name(过滤 uri)的过滤器链 NamedFilterList
,并将过滤器添加其中(过滤 uri 和过滤器是一对多关系)
1、applyChainConfig()
方法:存放所有的过滤 uri
1-1、PathMatchingFilter#processPathConfig()
方法:
- 属性
PatternMatcher pathMatcher
:用于请求 uri 和已配置的 uri 进行配置Map<String, Object> appliedPaths
: 存放所有的过滤 uri
2、ensureChain(chainName)
方法:通过 uri 获取过滤器链,如果没有,则创建 SimpleNamedFilterList
,并将此过滤器链放入 filterChains
属性中,返回;如果通过 uri 获取到过滤器链,则直接返回
2-1、SimpleNamedFilterList
:其实就是一个有名字 name 的 List<Filter>
。
- name:uri
List<Filter>
:配置的过滤器
一个 uri 可以对应多个过滤器,所以,Filter 是一个 List 集合
new PathMatchingFilterChainResolver()
:创建PathMatchingFilterChainResolver
,负责路径和过滤器链的解析、匹配,根据 url 找到过滤器链
2、第二阶段:正式请求
任何请求都会先经过 Shiro 先过滤,直到成功才会执行 web 本身的过滤器
一个请求过来时,先到达 AbstractShiroFilter#executeChain()
方法,去根据 request 解析出来的 url 找到对应的过滤链,然后执行过滤器链。
1、executeChain()
方法如下:
1-1、getExecutionChain()
方法:这里的 resolver 就是 PathMatchingFilterChainResolver
,根据 uri 获取过滤器链 ProxiedFilterChain
1-2、getChain()
方法:从属性 filterChains
循环匹配请求 uri,若匹配成功,则调用 filterChainManager.proxy()
;否则,返回 null
1-2-1、pathMatches()
方法:
1-2-1-1、AntPathMatcher#matches()
:使用 AntPathMatcher
进行匹配
doMatch()
:匹配 uri 和 requestUri,考虑到通配符 **
。
2、chain.doFilter(request, response);
:执行过滤器链
过滤器类结构图:
最终调用 AdviceFilter#doFilterInternal()
方法:由 preHandle()
方法决定过滤器链是否执行:
通过属性 appliedPaths
循环判断是否与当前请求的 uri 相匹配,如果通过,直接返回 true
,否则,进入 onPreHandle()
方法判断:
onPreHandle()
方法供子类去重写:
如 AnonymousFilter
:匿名访问。
任何对象访问都一直返回 true,表明任何用 AnonymousFilter
过滤的请求都不需要验证。因为它一直返回 true
AccessControlFilter
:需要身份验证的过滤器
由 isAccessAllowed()
、onAccessDenied()
方法决定
isAccessAllowed()
:决定了当前请求的subject是否允许访问onAccessDenied()
:在被拒绝访问时处理。AccessControlFilter 类有很多子类重载了该方法。以FormAuthenticationFilter
类的onAccessDenied()
方法为例
如果是登陆请求,则执行登陆操作;否则,保存请求链接跳转到登陆请求界面
executeLogin()
方法:创建 token,调用 Subject#login()
方法
总结流程: