shiro源码一:我从哪里来,到哪里去
先说我们通常使用一个shiro的基本配置 1:弄一个大概名称叫shiroConfig的类,里面一堆的@Bean的配置,其中有一个是这样的:
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(CommadminConfig commadminConfig, LoginFilterUrlConfig loginFilterUrlConfig, SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> map = new LinkedHashMap<String, String>();
map.putAll(loginFilterUrlConfig.getLoginFilterUrlMap());
//登录
shiroFilterFactoryBean.setLoginUrl("/auth/login");
//首页
shiroFilterFactoryBean.setSuccessUrl("/index");
//错误页面,认证不通过跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
Map<String, Filter> filterMap = new LinkedHashMap<String, Filter>();
StatelessAuthcFilter statelessAuthcFilter = new StatelessAuthcFilter();
statelessAuthcFilter.setCicadaLoginUrl(commadminConfig.getLoginUrl());
statelessAuthcFilter.setAnonTinyService(commadminConfig.getAnonTinyService());
filterMap.put("statelessAuthc", statelessAuthcFilter);
shiroFilterFactoryBean.setFilters(filterMap);
return shiroFilterFactoryBean;
}
如上图所示,最后返回一个shiroFilterFactoryBean,这个配置bean说明了几个问题
a:@Bean是要被spring管理的,也就是说spring容器启动的时候,会加载这个bean,关于bean和对象的区别,这个是spring的知识范畴,不展开讲了
b:shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
加入了一个map
login-filter-url-map:
"[/auth/login]": anon
"[/auth/logout]": anon
"[/user/register]": anon
"[/test/**]": anon
"[/web/gateway.do]": statelessAuthc
"[/**]": statelessAuthc
这个map其实就是application.yml里面的这些配置,加到哪里去了?
c:filterMap.put("statelessAuthc", statelessAuthcFilter);
这个statelessAuthcFilter过滤器有一个方法大概是这样的,对请求的token进行拦截判断,看是否过期,最后对请求的用户名密码进行认证的过程
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
if (isLoginRequest(request, response)) {
return true;
}
//1、获取到token,token是登录后返回给前端的。
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
if (httpServletRequest.getRequestURI().endsWith("/login.htm")) {
return true;
}
String originToken = JasyptUtils.decrypt(token);
String[] infoArray = originToken.split(CommAdminConstants.SEPEARTOR);
if (null == infoArray || infoArray.length < 3) {
LOG.error("stateless-authc-filter-token-parse-fail, token is " + token);
onLoginFail(response);
return false;
}
String userName = infoArray[0];
String userPwd = infoArray[1];
String expireTimeStr = infoArray[2];
//2、验证token中过期时间是否过期。
long tokenExpiredTime = Long.valueOf(expireTimeStr);
if (tokenExpiredTime < System.currentTimeMillis()) {
RequestContext.clear();
LOG.warn("stateless-authc-filter-token-expired, token is " + token);
onLoginFail(response);
return false;
}
RequestContext.RequestParams data = new RequestContext.RequestParams();
data.setUserName(userName);
RequestContext.set(data);
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userName, userPwd);
try {
getSubject(request, response).login(usernamePasswordToken);
} catch (Exception e) {
LOG.error("stateless-authc-filter-manu-login-fail,token is " + token, e);
onLoginFail(response);
return false;
}
return true;
}
添加了一个过滤器 shiroFilterFactoryBean.setFilters(filterMap);
首先来说,这个shiroFilterFactoryBean是shiro里面的配置bean,先进去看看
public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {
private static transient final Logger log = LoggerFactory.getLogger(ShiroFilterFactoryBean.class);
private SecurityManager securityManager;
private Map<String, Filter> filters;
private List<String> globalFilters;
private Map<String, String> filterChainDefinitionMap; //urlPathExpression_to_comma-delimited-filter-chain-definition
private String loginUrl;
private String successUrl;
private String unauthorizedUrl;
他实现了FactoryBean,BeanPostProcessor,我们知道在spring里面实现了FactoryBean,就会有一个getObject方法的实现,而这个BeanPostProcessor是spring里面的一个后置处理器,一般如果是实现了BeanPostProcessor,spring会在初始化前后这个bean的时候,会依次调用其postProcessBeforeInitialization,postProcessAfterInitialization方法,那么我们就去看看,在这个shiroFilterFactoryBean是不是有这些方法的实现,并且这些方法都做了什么事情 下面就开始正式的介绍: 我希望大家看源码的时候,尽量还是带着问题去思考,大家想下,我们在application.yml配置了那些不拦截的url后缀,为什么在shiro里面就生效了,这个url后缀和shiro某些东西是不是有内在的关联
shiro源码二:建立url后缀和filter过滤器的关联关系
ShiroFilterFactoryBean的getObject方法开始
public Object getObject() throws Exception {
if (instance == null) {
instance = createInstance();
}
return instance;
}
这个方法什么时候被执行?稍后再说springboot的启动流程的时候说,这个方法其实很关键,他后面调用的方法会建立起url后缀和过滤器filter的关联关系,先进去createInstance方法 org.apache.shiro.spring.web.ShiroFilterFactoryBean#createInstance FilterChainManager manager = createFilterChainManager();
protected FilterChainManager createFilterChainManager() {
DefaultFilterChainManager manager = new DefaultFilterChainManager();
Map<String, Filter> defaultFilters = manager.getFilters();//获取默认过滤器
//apply global settings if necessary:
for (Filter filter : defaultFilters.values()) {
applyGlobalPropertiesIfNecessary(filter);
}
//Apply the acquired and/or configured filters:
Map<String, Filter> filters = getFilters();
if (!CollectionUtils.isEmpty(filters)) {
for (Map.Entry<String, Filter> entry : filters.entrySet()) {
String name = entry.getKey();
Filter filter = entry.getValue();
applyGlobalPropertiesIfNecessary(filter);
if (filter instanceof Nameable) {
((Nameable) filter).setName(name);
}
//'init' argument is false, since Spring-configured filters should be initialized
//in Spring (i.e. 'init-method=blah') or implement InitializingBean:
manager.addFilter(name, filter, false);
}
}
// set the global filters
manager.setGlobalFilters(this.globalFilters);
//build up the chains:
Map<String, String> chains = getFilterChainDefinitionMap();//这个map就是application.yml配置的那个url后缀和和数值的对应键值对
if (!CollectionUtils.isEmpty(chains)) {
for (Map.Entry<String, String> entry : chains.entrySet()) {
String url = entry.getKey();
String chainDefinition = entry.getValue();
//创建过滤器链
manager.createChain(url, chainDefinition);
}
}
// create the default chain, to match anything the path matching would have missed
manager.createDefaultChain("/**"); // TODO this assumes ANT path matching, which might be OK here
return manager;
}
Map<String, Filter> defaultFilters = manager.getFilters();
先说这个东西是从哪来的,是什么东西?
org.apache.shiro.web.filter.mgt.DefaultFilterChainManager#getFilters
public Map<String, Filter> getFilters() {
return filters;
}
private Map<String, Filter> filters;一看是一个map集合,那么在DefaultFilterChainManager这个类中肯定有相关的put添加的方法,或者是什么构造方法传进来一个map赋值之类的代码,用ctrl+F一搜索,就找到了下面这个代码
protected void addFilter(String name, Filter filter, boolean init, boolean overwrite) {
Filter existing = getFilter(name);
if (existing == null || overwrite) {
if (filter instanceof Nameable) {
((Nameable) filter).setName(name);
}
if (init) {
initFilter(filter);
}
this.filters.put(name, filter);
}
}
按ctrl,点addFilter,看这个方法是在什么地方被调用的
protected void addDefaultFilters(boolean init) {
for (DefaultFilter defaultFilter : DefaultFilter.values()) {
addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
}
}
这个addDefaultFilters方法,好像是遍历某个DefaultFilter.values(),那么我们看看这个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),
invalidRequest(InvalidRequestFilter.class);
我们发现这个DefaultFilter ,就是一个枚举类型的,看这些枚举的类型和对应的值,是不是很熟悉的感觉,有没有,这些值。不就是我们在application.yml配置免拦截的时候,配置的吗,比如这个anon
"[/auth/login]": anon
哪我们是不是可以猜想,这种anon对应的过滤器就是AnonymousFilter.class这个类
到此
Map<String, Filter> defaultFilters = manager.getFilters();
的来源找到了,这个是shiro里面默认的过滤器
Map<String, Filter> filters = getFilters();
这个又是什么?
com.alipay.cxbiz.open.commadmin.web.conf.ShiroConfig#shiroFilterFactoryBean
filterMap.put("statelessAuthc", statelessAuthcFilter);
shiroFilterFactoryBean.setFilters(filterMap);
这个过滤器map其实就是我们在自定义shiroConfig的时候放进去的,当然这个map里面有的不仅仅是我们在这个地方放进去的,反正这个里面的过滤器,都是自定义的过滤器 比如statelessAuthcFilter这个过滤器
最后把shiro里面默认的过滤器和我们自定义的过滤器合并到一起
manager.addFilter(name, filter, false);
放到
org.apache.shiro.web.filter.mgt.DefaultFilterChainManager
Map<String, Filter> filters;
至此:shiro默认的filter和程序员定义的filter来源说明白了
接着分析
Map<String, String> chains = getFilterChainDefinitionMap();
这个map,是什么,kv都是String类型的,同样的方法,进去getFilterChainDefinitionMap里面去找,从哪里进来的
com.alipay.cxbiz.open.commadmin.web.conf.ShiroConfig#shiroFilterFactoryBean
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
就是这个地方添加进来的,前面我们说过,这个map其实就是application.yml里面配置的那个免拦截的url的集合
login-filter-url-map:
"[/auth/login]": anon
"[/auth/tiny/login]": anon
"[/auth/logout]": anon
"[/user/register]": anon
"[/templtest/**]": anon
"[/commadmin/**]": anon
"[/cicada/**]": anon
"[/web/gateway.do]": statelessAuthc
"[/**]": statelessAuthc
就是这个玩意儿的集合,接着往下走
Map<String, String> chains = getFilterChainDefinitionMap();
if (!CollectionUtils.isEmpty(chains)) {
for (Map.Entry<String, String> entry : chains.entrySet()) {
String url = entry.getKey();
String chainDefinition = entry.getValue();
manager.createChain(url, chainDefinition);
}
}
他是遍历这个map集合,获取到url和value
"[/auth/login]": anon
参照来说,/auth/login这个就是url,anon就是value
org.apache.shiro.web.filter.mgt.DefaultFilterChainManager#createChain
去掉多余的代码,核心的代码就下面,先添加一个默认的过滤器InvalidRequestFilter
public void createChain(String chainName, String chainDefinition) {
if (!CollectionUtils.isEmpty(globalFilterNames)) {
globalFilterNames.stream().forEach(filterName -> addToChain(chainName, filterName));//添加默认的过滤器InvalidRequestFilter
}
//这个value可能是一个数组
String[] filterTokens = splitChainDefinition(chainDefinition);
for (String token : filterTokens) {
String[] nameConfigPair = toNameConfigPair(token);
addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);
}
}
org.apache.shiro.web.filter.mgt.DefaultFilterChainManager#addToChain(java.lang.String, java.lang.String, java.lang.String)
public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {
if (!StringUtils.hasText(chainName)) {
throw new IllegalArgumentException("chainName cannot be null or empty.");
}
Filter filter = getFilter(filterName);//根据filterName去获取过滤器
if (filter == null) {
throw new IllegalArgumentException("There is no filter with name '" + filterName +
"' to apply to chain [" + chainName + "] in the pool of available Filters. Ensure a " +
"filter with that name/path has first been registered with the addFilter method(s).");
}
applyChainConfig(chainName, filter, chainSpecificFilterConfig);
NamedFilterList chain = ensureChain(chainName);
chain.add(filter);
}
先看看这个 Filter filter = getFilter(filterName); 这个filterName其实就是比如 "[/auth/login]": anon 这个的anon这个值,根据这个值是可以获取到一个Filter的
NamedFilterList chain = ensureChain(chainName); chain.add(filter);
protected NamedFilterList ensureChain(String chainName) {
NamedFilterList chain = getChain(chainName);
if (chain == null) {
chain = new SimpleNamedFilterList(chainName);
this.filterChains.put(chainName, chain);
}
return chain;
}
public NamedFilterList getChain(String chainName) {
return this.filterChains.get(chainName);
}
把对应的value值和filter建立一种关系,放到NamedFilterList里面去 至此:建立映射关系,shiro url和filter的关系建立起来了。后面无非就是一个请求来了 根据请求看能不能匹配这些过滤器,选择不同的过滤器去执行,最后的chain就是这样的
shiro源码三:哥走哪个filter
org.apache.shiro.web.servlet.AbstractShiroFilter#doFilterInternal
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
throws IOException, ServletException {
FilterChain chain = getExecutionChain(request, response, origChain);
chain.doFilter(request, response);
}
FilterChain chain = getExecutionChain(request, response, origChain); org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain
for (String pathPattern : filterChainManager.getChainNames()) {
if (pathPattern != null && !DEFAULT_PATH_SEPARATOR.equals(pathPattern)
&& pathPattern.endsWith(DEFAULT_PATH_SEPARATOR)) {
pathPattern = pathPattern.substring(0, pathPattern.length() - 1);
}
// If the path does match, then pass on to the subclass implementation for specific checks:
if (pathMatches(pathPattern, requestURI)) {
if (log.isTraceEnabled()) {
log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + Encode.forHtml(requestURI) + "]. " +
"Utilizing corresponding filter chain...");
}
return filterChainManager.proxy(originalChain, pathPattern);
}
}
循环去chainName,也就是/auth/login,看和我们请求的url后缀是否匹配,如果匹配,就取出对应的filterChain。这个chain里面包含了多个filter
public FilterChain proxy(FilterChain original, String chainName) {
NamedFilterList configured = getChain(chainName);
if (configured == null) {
String msg = "There is no configured chain under the name/key [" + chainName + "].";
throw new IllegalArgumentException(msg);
}
return configured.proxy(original);
}
回到上面的chain.doFilter(request, response);
org.apache.shiro.web.servlet.ProxiedFilterChain#doFilter this.filters.get(this.index++).doFilter(request, response, this); 循环调用这个url对应的filterChain里面的filter
org.apache.shiro.web.servlet.OncePerRequestFilter#doFilter org.apache.shiro.web.servlet.AdviceFilter#doFilterInternal
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
Exception exception = null;
try {
boolean continueChain = preHandle(request, response);
if (log.isTraceEnabled()) {
log.trace("Invoked preHandle method. Continuing chain?: [" + continueChain + "]");
}
if (continueChain) {
executeChain(request, response, chain);
}
postHandle(request, response);
}
org.apache.shiro.web.filter.PathMatchingFilter#preHandle
private boolean isFilterChainContinued(ServletRequest request, ServletResponse response,
String path, Object pathConfig) throws Exception {
return onPreHandle(request, response, pathConfig);
}
在这个地方,分成了两个分支,一个是忽略过滤器的,用的就是AnonymousFilter,另外一个走的是自定义的过滤器的AccessControlFilter AnonymousFilter的onPreHandle默认返回true,也就是在校验用户这块返回true,不用校验
public class AnonymousFilter extends PathMatchingFilter {
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) {
return true;
}
}
而如果是需要校验的,走的是AccessControlFilter这个过滤器 org.apache.shiro.web.filter.AccessControlFilter#onAccessDenied(javax.servlet.ServletRequest, javax.servlet.ServletResponse, java.lang.Object)
protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return onAccessDenied(request, response);
}
最终到,这个是我们自定义的StatelessAuthcFilter com.alipay.cxbiz.open.commadmin.web.auth.StatelessAuthcFilter#onAccessDenied
shiro源码四:认证流程
唐僧师徒历经千难万险,终于达到了我佛如来,灵山脚下,接受如来的考验,预知下事如何,请听下面分解 com.alipay.cxbiz.open.commadmin.web.auth.StatelessAuthcFilter#onAccessDenied 在这个方法里面,我们去验证了token的合法性,在token合法的基础上,验证用户名密码是否正确,这个在shiro里面是怎么玩的呢
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userName, userPwd);
this.getSubject(request, response).login(usernamePasswordToken);
先把userName和userPwd封装成了一个类UsernamePasswordToken,这个类是shiro里面的类
org.apache.shiro.subject.support.DelegatingSubject#login 好了,哥们开始携带UsernamePasswordToken去接受考验了
org.apache.shiro.mgt.DefaultSecurityManager#login
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = authenticate(token);
} catch (AuthenticationException ae) {
try {
onFailedLogin(token, ae, subject);
} catch (Exception e) {
if (log.isInfoEnabled()) {
log.info("onFailedLogin method threw an " +
"exception. Logging and propagating original AuthenticationException.", e);
}
}
throw ae; //propagate
}
Subject loggedIn = createSubject(token, info, subject);
onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
各位看官请看这行代码
info = authenticate(token);
org.apache.shiro.authc.AbstractAuthenticator#authenticate
try {
info = doAuthenticate(token);
if (info == null) { //如果是null,则报异常
String msg = "No account information found for authentication token [" + token + "] by this " +
"Authenticator instance. Please check that it is configured correctly.";
throw new AuthenticationException(msg);
}
}
org.apache.shiro.authc.pam.ModularRealmAuthenticator#doSingleRealmAuthentication org.apache.shiro.realm.AuthenticatingRealm#getAuthenticationInfo
info = doGetAuthenticationInfo(token);
终于历经千难万险到达了我们自定义的认证方法UserRealm#doGetAuthenticationInfo com.alipay.cxbiz.open.commadmin.web.auth.UserRealm#doGetAuthenticationInfo
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken userToken = (UsernamePasswordToken)authenticationToken;
String userName = userToken.getUsername();
UserEntity user = null;
if (userName.startsWith("2088")) {
user = new UserEntity();
user.setUserName(userName);
user.setPassword(this.commadminConfig.getTinyAppSalt());
} else {
user = this.userService.findUserByName(userName);
}
if (user == null) {
return null;
} else {
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userName, user.getPassword(), this.getName());
return simpleAuthenticationInfo;
}
}
这个地方就会根据传进来的userName去数据库查询用户信息 user = this.userService.findUserByName(userName); 如果这个user返回不是null。则 org.apache.shiro.realm.AuthenticatingRealm#getAuthenticationInfo 在这个方法里面的进行密码的比对,token是传进来的,info是根据userName去数据库查询的
if (info != null) {
assertCredentialsMatch(token, info);
}
org.apache.shiro.realm.AuthenticatingRealm#assertCredentialsMatch org.apache.shiro.authc.credential.SimpleCredentialsMatcher#doCredentialsMatch
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenCredentials = getCredentials(token);
Object accountCredentials = getCredentials(info);
return equals(tokenCredentials, accountCredentials);
}
如果密码不正确,则直接报异常 如果user返回的是null,也会报异常,这两种都是认证失败的。至此,认证流程完成了讲解
shiro源码5:授权流程
通常在代码里面判断用户有没有这个权限
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("admin")) {
// 有权限,执行相关业务
} else {
// 无权限,给相关提示
}
或者
@RequiresPermissions("sys:user:user")
public List<User> listUser() {
// 有权限,获取数据
}
public boolean hasRole(String roleIdentifier) {
return hasPrincipals() && securityManager.hasRole(getPrincipals(), roleIdentifier);
}
public boolean hasRole(PrincipalCollection principal, String roleIdentifier) {
AuthorizationInfo info = getAuthorizationInfo(principal);
return hasRole(roleIdentifier, info);
}
org.apache.shiro.realm.AuthorizingRealm#getAuthorizationInfo
info = doGetAuthorizationInfo(principals);
又到了我们自定义的UserRealm#doGetAuthorizationInfo获取权限了 com.alipay.cxbiz.open.commadmin.web.auth.UserRealm#doGetAuthorizationInfo
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String username = (String)principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(this.userService.queryRoles(username));
return authorizationInfo;
}
shiro源码6:讲讲ShiroFilterFactoryBean里面的getObject方法是怎么被调用到的
public Object getObject() throws Exception {
if (instance == null) {
instance = createInstance();
}
return instance;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(CommadminConfig commadminConfig, LoginFilterUrlConfig loginFilterUrlConfig, SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> map = new LinkedHashMap<String, String>();
map.putAll(loginFilterUrlConfig.getLoginFilterUrlMap());
//登录
shiroFilterFactoryBean.setLoginUrl("/auth/login");
//首页
shiroFilterFactoryBean.setSuccessUrl("/index");
//错误页面,认证不通过跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
Map<String, Filter> filterMap = new LinkedHashMap<String, Filter>();
StatelessAuthcFilter statelessAuthcFilter = new StatelessAuthcFilter();
statelessAuthcFilter.setCicadaLoginUrl(commadminConfig.getLoginUrl());
statelessAuthcFilter.setAnonTinyService(commadminConfig.getAnonTinyService());
filterMap.put("statelessAuthc", statelessAuthcFilter);
shiroFilterFactoryBean.setFilters(filterMap);
return shiroFilterFactoryBean;
}
这个地方有@Bean的标签,spring在容器初始化的时候,在springboot也就是在这个方法中
protected void onRefresh() {
super.onRefresh();//先初始化spring
try {
createWebServer();//再启动容器
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
会经过扫描把这个class转换成beanDefinition,存在spring容器里面,其实就是一个map集合,从上面这个方法可以看到,先是执行spring的初始化,然后再开始创建启动tomcat容器 createWebServer();在这个方法中,
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
return new ServletContextInitializerBeans(getBeanFactory());
}
获取到beanFactory,这个beanFactory是一个spring的容器工厂,拿到这个对象,就可以获取到spring里面的bean,具体bean和对象有什么区别,这个是spring源码的东西,如果不懂就把bean看做特殊的class映射的对象
org.springframework.boot.web.servlet.ServletContextInitializerBeans#addAsRegistrationBean
protected <T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
RegistrationBeanAdapter<T> adapter) {
addAsRegistrationBean(beanFactory, type, type, adapter);
}
org.springframework.boot.web.servlet.ServletContextInitializerBeans#addAsRegistrationBea 下面就是获取到所有的实现了javax.servlet.Filter的beanName,去spring容器去获取bean,这个是tomcat容器里面的东西,他启动的时候,会去获取所有的javax.servlet.Filter类型的bean
private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen);
for (Entry<String, B> entry : entries) {
String beanName = entry.getKey();//获取beanName
B bean = entry.getValue();
if (this.seen.add(bean)) {
// One that we haven't already seen
RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size());
int order = getOrder(bean);
registration.setOrder(order);
this.initializers.add(type, registration);
if (logger.isTraceEnabled()) {
logger.trace("Created " + type.getSimpleName() + " initializer for bean '" + beanName + "'; order="
+ order + ", resource=" + getResourceDescription(beanName, beanFactory));
}
}
}
}
在getBean的过程中,会判断这个bean是不是一个factoryBean
org.springframework.beans.factory.support.AbstractBeanFactory#getObjectForBeanInstance
如果是这个bean是一个factoryBean,就会调用
至此,ShiroFilterFactoryBean调用getObject的过程就解释清楚了
shiro源码7:ShiroFilterFactoryBean为什么要实现BeanPostProcessor,
且看回调方法,BeanPostProcessor是spring的处理器,为spring扩展而生,具体可以参考spring 的源码
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Filter) {
log.debug("Found filter chain candidate filter '{}'", beanName);
Filter filter = (Filter) bean;
applyGlobalPropertiesIfNecessary(filter);
getFilters().put(beanName, filter);
} else {
log.trace("Ignoring non-Filter bean '{}'", beanName);
}
return bean;
}
我们前面说过,在自定义的filter里面除了我们在
com.alipay.cxbiz.open.commadmin.web.conf.ShiroConfig#shiroFilterFactoryBean
这个方法里面添加的这个filter之外,其实还是有其他的filter的
filterMap.put("statelessAuthc", statelessAuthcFilter); 除了这个filter之外,其他filter都是通过这种方式,getFilters().put(beanName, filter);进来的,这样也就解释了为什么需要BeanPostProcessor这个问题
写在最后:源码心得
写在最后,其实这个shiro源码并不难,但是看了这个源码有几点心得思考,其实这些思想才是最重要的,也是我们在实际项目中可能会用到的
1:我们做一个组件或者是一个应用,怎么和spring结合,在shiro里面,就很好的利用了springboot web容器启动的时候,会去找javax.servlet.Filter这个类型相关的bean, 在getBean这个过程中,会判断,这个bean是不是一个factoryBean,来调用其getObject方法,shiro利用了factoryBean这个特性,来完成了filter和url之间的一种映射关系
public Object getObject() throws Exception {
if (instance == null) {
instance = createInstance();
}
return instance;
}
2:第二个就是filter的这种责任链模式,其实在平时开发的项目中,可以用到,比如,商场的优惠券的打折,我完全可以根据用户的一些积分,节假日优惠,各自条件来判断走不走某个filter,还是走某个分支,来实现我们的页面的可扩展
3:我们在application.yml里面配置的那些免校验的url后缀,把他转换成不同的filter的这种思想
4:就是不管是在权限认证的地方,还是在授权的地方,我们都知道底层都是去找某个类型的bean,比如他去收集这个AuthorizingRealm类型的bean,来循环遍历他的doGetAuthenticationInfo方法,来完成所谓的认证,这个思想其实很多地方都有,包括spring的源码里面的后置处理器,beanPostProcessor,都是这种思想,可以很好的扩展我们的程序
public class UserRealm extends AuthorizingRealm {
好了,今天就到这里吧,有想一起研究技术的,可以在下方留言