转载

Shiro源码解析

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

ShiroFilterFactoryBean

  • 实现了FactoryBean接口,创建了一个 SpringShiroFilter 对象,在getObject方法中会创建,创建的步骤如下:

    org.apache.shiro.spring.web.ShiroFilterFactoryBean#applyGlobalPropertiesIfNecessary
    applyGlobalPropertiesIfNecessary
    filterChains
    
  • 实现了BeanPostProcessor接口,在 postProcessBeforeInitialization 方法中就是获取到ioc容器中的Filter类型的Bean将其设置到filters属性中

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方法就在其中定义,如下:
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相应的功能,如下:
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

onPreHandle
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判断权限,如下:
    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
    issueSuccessRedirect
    

实现的过滤器

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

FormAuthenticationFilter

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

自定义过滤器

  • 在现在的项目中大多使用的是前后端分离,现在我们使用自定义一个过滤器实现登录。
/**
 * 继承AccessControlFilter抽象类,实现权限控制的访问
 */
@Slf4j
public class LoginFilterextends 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;
    }
}
  • 配置过滤器
/**
     * 创建登录的过滤器
     */
    @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访问了
原文  https://chenjiabing666.github.io/2019/08/13/Shiro源码解析/
正文到此结束
Loading...