前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Shiro源码解析

Shiro源码解析

作者头像
爱撒谎的男孩
发布2019-12-31 16:28:45
8990
发布2019-12-31 16:28:45
举报
文章被收录于专栏:码猿技术专栏

文章目录

1. FilterChainManager

2. ShiroFilterFactoryBean

3. Shiro中的过滤器

3.1. OncePerRequestFilter

3.2. AdviceFilter

3.3. PathMatchingFilter

3.4. AccessControlFilter

3.5. AuthenticationFilter

3.6. 实现的过滤器

3.6.1. FormAuthenticationFilter

3.7. 自定义过滤器

4. 过滤器创建

FilterChainManager

  • 过滤器管理器,在ShiroFilterFactoryBean创建的时候会创建管理器,将配置好的过滤器添加到其中
  • 当发出请求的时候会其中找出和url匹配过滤器执行

ShiroFilterFactoryBean

  • 实现了FactoryBean接口,创建了一个SpringShiroFilter对象,在getObject方法中会创建,创建的步骤如下:
    • 创建FIlter管理器
    • 创建11个默认的过滤器,并且其中的uri会被设置全局配置的uri,具体逻辑在org.apache.shiro.spring.web.ShiroFilterFactoryBean#applyGlobalPropertiesIfNecessary方法
    • 将定义的Filter设置过滤器管理器中的filters属性中,并且调用applyGlobalPropertiesIfNecessary方法设置全局属性
    • 构建过滤器链(chain),保存在filterChains的属性中
  • 实现了BeanPostProcessor接口,在postProcessBeforeInitialization方法中就是获取到ioc容器中的Filter类型的Bean将其设置到filters属性中
代码语言:javascript
复制
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);
            //放入到filters属性中
            getFilters().put(beanName, filter);
        } else {
            log.trace("Ignoring non-Filter bean '{}'", beanName);
        }
        return bean;
    }

Shiro中的过滤器

  • Shiro中的过滤器设计的很巧妙,每一个抽象的Filter都有不同的职责
  • 下面介绍的一些过滤器都是按照父类和子类从上向下介绍。

OncePerRequestFilter

  • 该类是相对于有作用的过滤器的顶层,所有的过滤器都继承这个抽象类,该类保证了每一次请求只执行一次过滤器,其中的过滤器的doFilter方法就在其中定义,如下:
代码语言:javascript
复制
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
            log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());
            filterChain.doFilter(request, response);
        } else //noinspection deprecation
            if (/* added in 1.2: */ !isEnabled(request, response) ||
                /* retain backwards compatibility: */ shouldNotFilter(request) ) {
            log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.",
                    getName());
            filterChain.doFilter(request, response);
        } else {
            // Do invoke this filter...
            log.trace("Filter '{}' not yet executed.  Executing now.", getName());
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
                //过滤器真正执行的逻辑方法
                doFilterInternal(request, response, filterChain);
            } finally {
                // Once the request has finished, we're done and we don't
                // need to mark as 'already filtered' any more.
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        }
    }
  • 在doFilter中有一个方法doFilterInternal(request, response, filterChain),这个方法是执行过滤器的真正逻辑,被子类AdviceFilter实现

AdviceFilter

  • Advice很熟悉,这个抽象类能够实现像AOP一样的功能,其中定义了两个方法,分别是preHandlepostHandle,这两个方法能在过滤器执行前后做一些事情。
  • 其中一个重要的方法,是执行过滤器的主要方法,在其中完成AOP相应的功能,如下:
代码语言:javascript
复制
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        Exception exception = null;

        try {
            //在执行下一个过滤器前执行,如果返回的ttrue表示执行下一个过滤器,否则结束执行
            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);
            if (log.isTraceEnabled()) {
                log.trace("Successfully invoked postHandle method");
            }

        } catch (Exception e) {
            exception = e;
        } finally {
            //清除
            cleanup(request, response, exception);
        }
    }

PathMatchingFilter

  • 用于请求的url匹配,如果请求的url匹配了,那么需要执行onPreHandle判断是否继续执行后面的过滤器
  • url匹配的逻辑主要在preHandle方法中,如下:
代码语言:javascript
复制
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {

        if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
            if (log.isTraceEnabled()) {
                log.trace("appliedPaths property is null or empty.  This Filter will passthrough immediately.");
            }
            return true;
        }
        
        for (String path : this.appliedPaths.keySet()) {
            // If the path does match, then pass on to the subclass implementation for specific checks
            //(first match 'wins'):
            if (pathsMatch(path, request)) {
                log.trace("Current requestURI matches pattern '{}'.  Determining filter chain execution...", path);
                Object config = this.appliedPaths.get(path);
                return isFilterChainContinued(request, response, path, config);
            }
        }

        //no path matched, allow the request to go through:
        return true;
    }

AccessControlFilter

  • 用于控制访问资源的权限,该类继承上面的三个类,其中有如下几个重要的方法:
    • protected abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue):判断当前的访问是否有权限,如果有权限,那么正常继续执行,如果返回false,那么将会执行onAccessDenied方法
    • protected abstract boolean onAccessDenied(ServletRequest request, ServletResponse response):isAccessAllowed返回false的时候将会执行,表示当前没有权限访问,需要执行相应的逻辑
    • public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue):在过滤器继续执行之前执行的逻辑,其中默认就是调用了isAccessAllowed判断权限,如下:
代码语言:javascript
复制
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    	//逻辑或,如果第一条件为false,将会执行第二个方法,反之不执行
        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
    }

AuthenticationFilter

  • 抽象类,实现了父类的isAccessAllowed方法,用于权限控制,只有当前的用户认证之后才返回true
  • 其中的几个重要的方法如下:
    • isAccessAllowed:实现了父类的方法,只有用户认证之后才返回true
    • issueSuccessRedirect:重定向方法,默认重定向到配置的successUrl

实现的过滤器

  • 上面介绍的几个都是抽象类,在DefaultFilter中的11个过滤器都是实现了上面的抽象类

FormAuthenticationFilter

  • shiro默认配置的过滤器,名称是authc
  • 其中实现了登录的请求和认证的功能,我们可以不用重写登录的方法,而是配置一个url映射到该过滤器即可完成登录。
  • 默认的登录的用户名和密码的映射字段如下:
    • 当然我们可以覆盖这个属性,只需要创建一个当前的过滤器,将其添加到IOC中即可,当然其中的beanName要是authc
代码语言:javascript
复制
public static final String DEFAULT_USERNAME_PARAM = "username";
public static final String DEFAULT_PASSWORD_PARAM = "password";
  • 其中实现的onAccessDenied方法有如下的两个功能:
    • 如果和配置的loginUrlng 相同,那么表示是登录的功能(也不全然是,也可以是一个没有认证的重定向的url,这个在前后端分离的时候常用)
    • 重定向的功能,如果既不是登录的url也没有通过认证,那么将会重定向到配置的loginUrl

自定义过滤器

  • 在现在的项目中大多使用的是前后端分离,现在我们使用自定义一个过滤器实现登录。
代码语言:javascript
复制
/**
 * 继承AccessControlFilter抽象类,实现权限控制的访问
 */
@Slf4j
public class LoginFilter extends AccessControlFilter {

    private final static  String USERNAME="userName";

    private final static String PASSWORD="password";

    /**
     * 登录成功的url
     */
    private String successUrl="success";

    /**
     * 登录失败的url
     */
    private String failUrl="fail";

    public LoginFilter(String successUrl, String failUrl) {
        this.successUrl = successUrl;
        this.failUrl = failUrl;
    }

    /**
     * 判断是否认证成功
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        Subject subject = SecurityUtils.getSubject();
        return subject.isAuthenticated();
    }

    /**
     * 创建UserNamePasswordToken
     */
    private  UsernamePasswordToken createToken(ServletRequest request){
        String userName = WebUtils.getCleanParam(request, USERNAME);
        String password = WebUtils.getCleanParam(request, PASSWORD);
        return new UsernamePasswordToken(userName,password);
    }

    /**
     * 没有认证成功执行的方法
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        Subject subject=SecurityUtils.getSubject();
        try {
            //执行登录
            subject.login(createToken(request));
            Map<String,String> params=new HashMap<>();
            params.put("token", subject.getSession().getId().toString());
            //此处需要将token传入重定向的url,因为重定向的url不是匿名访问的,配置的authc,并且还需要将token返回
            //如果走的是匿名的url的话,那么从Subject中获取的Session并不是认证成功的
            WebUtils.issueRedirect(request, response, successUrl,params);
        }catch (AuthenticationException ex){
            log.info("登录失败",ex);
            //登录失败,重定向到指定的url
            WebUtils.issueRedirect(request, response, failUrl);
        }
        //不在执行下面的逻辑
        return false;
    }
}
  • 配置过滤器
代码语言:javascript
复制
/**
     * 创建登录的过滤器
     */
    @Bean
    public LoginFilter loginFilter(){
        return new LoginFilter("/user/success", "/user/unauthentic");
    }

//配置登录的url
filterChainDefinitionMap.put("/login", "loginFilter");

//成功的重定向的请求
 	/**
     * LoginFilter登录成功重定向的请求
     * @return
     */
    @RequestMapping("success")
    public PageResponse success(String token){
        return new PageResponse("登录成功","0",token);
    }


//失败的重定向
 /**
     * 没有登录跳转到的uri
     * @return
     */
    @GetMapping("/unauthentic")
    public PageResponse unauthentic(){
        return new PageResponse("尚未登录","401");
    }
  • 配置好之后,就能直接通过login访问了

过滤器创建

  • 过滤器的来源:
    • shiro容器中默认的11个过滤器,如下:
代码语言:javascript
复制
anon(AnonymousFilter.class),
   authc(FormAuthenticationFilter.class),
   authcBasic(BasicHttpAuthenticationFilter.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);
代码语言:javascript
复制
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;
            //设置全局属性(如果自定义的过滤器中的三个url没有改变,那么将会启用全局配置的url,否则还是使用自定义设置的url)
            applyGlobalPropertiesIfNecessary(filter);
            getFilters().put(beanName, filter);
        } else {
            log.trace("Ignoring non-Filter bean '{}'", beanName);
        }
        return bean;
    }
  • 同一个url能否配置多个过滤器?
    • 肯定是能的,只需要在配置filterChainDefinitionMap的时候指定多个即可,使用逗号分隔,如下:

    filterChainDefinitionMap.put("/user/login", "anon,customFilter");

  • 假设对于请求的url能够匹配多个filterChainDefinitionMap,那么应该使用哪一个过滤器链?
    • 从源码中可以看到,在获取拦截器链的逻辑是只要第一个匹配了,那么就直接返回这个拦截器链,下面的就不执行了,因此是先在filterChainDefinitionMap中配置的先被使用,后面的会被忽略,源码如下:
代码语言:javascript
复制
/org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
    	//获取管理器
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }
		//获取请求的url
        String requestURI = getPathWithinApplication(request);
        //循环遍历所有配置在filterChainDefinitionMap中的过滤器的名字
        for (String pathPattern : filterChainManager.getChainNames()) {
            //url匹配
            if (pathMatches(pathPattern, requestURI)) {
                if (log.isTraceEnabled()) {
                    log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "].  " +
                            "Utilizing corresponding filter chain...");
                }
                //如果匹配了直接返回过滤器链的代理对象
                return filterChainManager.proxy(originalChain, pathPattern);
            }
        }
        return null;
    }
  • 创建过滤器的流程图如下:
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-08-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • FilterChainManager
  • ShiroFilterFactoryBean
  • Shiro中的过滤器
    • OncePerRequestFilter
      • AdviceFilter
        • PathMatchingFilter
          • AccessControlFilter
            • AuthenticationFilter
              • 实现的过滤器
                • FormAuthenticationFilter
              • 自定义过滤器
              • 过滤器创建
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档