shiro源码全面分析

2,369 阅读13分钟

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 {

好了,今天就到这里吧,有想一起研究技术的,可以在下方留言