我的公众号:MarkerHub,Java网站:https://markerhub.com

更多精选文章请点击:Java笔记大全.md

小Hub领读:

权限框架个别都是一堆过滤器、拦截器的组合使用,在shiro中,有多少个内置的过滤器你晓得吗?在哪些场景用那些过滤器,这篇文章心愿你能对shiro有个新的意识!

别忘了,点个 [在看] 反对一下哈~


前两篇原创shiro相干文章:

1、极简入门,Shiro的认证与受权流程解析

2、只须要6个步骤,springboot集成shiro,并实现登录


咱们都晓得shiro是个认证权限框架,除了登录、退出逻辑咱们须要侵入我的项目代码之外,验证用户是否曾经登录、是否领有权限的代码其实都是过滤器来实现的,能够这么说,shiro其实就是一个过滤器链汇合。

那么明天咱们具体讨论一下shiro底层到底给咱们提供了多少默认的过滤器供咱们应用,又都有什么用呢?带着问题,咱们先去shiro官网看看对于默认过滤器集的阐明。

  • http://shiro.apache.org/web.h...
When running a web-app, Shiro will create some useful default Filter instances and make them available in the [main] section automatically. You can configure them in main as you would any other bean and reference them in your chain definitions.

The default Filter instances available automatically are defined by the DefaultFilter enum and the enum’s name field is the name available for configuration.

翻译过去意思:

当运行web应用程序时,Shiro将创立一些有用的默认过滤器实例,并使它们在[main]局部主动可用。您能够像配置任何其余bean一样在main中配置它们,并在链定义中援用它们。

默认筛选器实例由DefaultFilter enum中定义,enum s name字段是可用于配置的名称。

于是我看了一下DefaultFilter的源码:

public enum DefaultFilter {    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);    ...}

终于晓得咱们罕用的anon、authc、perms、roles、user过滤器是哪里来的了!这些过滤器咱们都是能够间接应用的。但你要弄清楚这些默认过滤器,你还不得不去深刻理解一下shiro更底层为咱们提供的过滤器,根本咱们的这些默认过滤器都是通过继承这几个底层过滤器演变而来的。

那么这些过滤器都有哪些呢?咱们来看一个图:

下面我标记了7个咱们接下来要介绍的过滤器,咱们一个个来介绍,弄清楚这些过滤器之后,置信你对shiro的意识会更深一层了。具体authc、perms、roles等这些默认过滤器与这7个过滤器有什么关系你就会明确。

1、AbstractFilter

这个过滤器还得说说,shiro最底层的形象过滤器,尽管咱们极少间接继承它,它通过实现Filter取得过滤器的个性。

实现一些过滤器根本初始化操作,FilterConfig:过滤器配置对象,用于servlet容器在初始化期间将信息传递给其余过滤器。

2、NameableFilter

命名过滤器,给过滤器定义名称!也是比拟基层的过滤器了,未拓展其余性能,咱们很少会间接继承这个过滤器。为重写doFilter办法。

3、OncePerRequestFilter

重写doFilter办法,保障每个servlet办法只会被过滤一次。能够看到doFilter办法中,第一行代码就是String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();而后通过request.getAttribute(alreadyFilteredAttributeName) != null来判断过滤器是否曾经被调用过,从而保障过滤器不会被反复调用。

进入办法之前,先标记alreadyFilteredAttributeName为True,形象doFilterInternal办法执行之后再remove掉alreadyFilteredAttributeName

所以OncePerRequestFilter过滤器保障只会被一次调用的性能,提供了形象办法doFilterInternal让前面的过滤器能够重写,执行真正的过滤器解决逻辑。

protected abstract void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)            throws ServletException, IOException;

这个过滤器咱们曾经能够开始在咱们的我的项目继承应用,比方拦挡用户申请,判断用户是否曾经登录(携带token或cookie信息),如果未登录则返回Json数据告知未登录!

比方:
开源mblog博客我的项目中,过滤器就是继承OncePerRequestFilter。

  • https://gitee.com/mtons/mblog
/** * 公众号:MarkerHub**/public class AuthenticatedFilter extends OncePerRequestFilter {    // 前端弹窗的js代码    private static final String JS = "<script type='text/javascript'>var wp=window.parent; if(wp!=null){while(wp.parent&&wp.parent!==wp){wp=wp.parent;}wp.location.href='%1$s';}else{window.location.href='%1$s';}</script>";    private String loginUrl = "/login";    // 重写doFilterInternal办法    @Override    protected void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)            throws ServletException, IOException {        Subject subject = SecurityUtils.getSubject();        // 曾经登陆就跳过过滤器        if (subject.isAuthenticated() || subject.isRemembered()) {            chain.doFilter(request, response);        } else {            // 未登录就返回json或者js代码            WebUtils.saveRequest(request);            String path = WebUtils.getContextPath((HttpServletRequest) request);            String url = loginUrl;            if (StringUtils.isNotBlank(path) && path.length() > 1) {                url = path + url;            }            if (isAjaxRequest((HttpServletRequest) request)) {                response.setContentType("application/json;charset=UTF-8");                response.getWriter().print(JSON.toJSONString(Result.failure("您还没有登录!")));            } else {                response.getWriter().write(new Formatter().format(JS, url).toString());            }        }    }}

未登录状况,ajax申请过滤器返回您还没有登录!提醒,web申请则返回一段js代码,前端渲染会跳出一个登陆窗口,这也就是未什么大家常遇到的点击登录,以后跳出一个登陆弹窗的一种实现形式!

成果:

4、AdviceFilter

看到Advice,很天然想到切面盘绕编程,个别有pre、post、after几个办法。所以这个AdviceFilter过滤器就是提供了和AOP类似的切面性能。

继承OncePerRequestFilter过滤器重写doFilterInternal办法,咱们能够先看看:

能够看到下面4个序号:

  • 1、preHandle 前置过滤,默认true
  • 2、executeChain 执行真正代码过滤逻辑->chain.doFilter
  • 3、postHandle 后置过滤
  • 4、cleanup 其实次要逻辑是afterCompletion办法

于是,咱们从OncePerRequestFilter的一个doFilterInternal分化成了切面编程,更容易前后管制执行逻辑。所以如果继承AdviceFilter时候,咱们能够重写preHandle办法,判断用户是否满足已登录或者其余业务逻辑,返回false时候示意不通过过滤器。

5、PathMatchingFilter

申请门路匹配过滤器,通过匹配申请url,判断申请是否须要过滤,如果url未在须要过滤的汇合内,则跳过,否则进入isFilterChainContinued的onPreHandle办法。

咱们能够看下代码:

从下面3个步骤中能够看到,PathMatchingFilter提供的性能是:自定义匹配url,匹配上的申请最终跳转到onPreHandle办法。

这个过滤器为前面的罕用过滤器提供的根底,比方咱们在config中配置如下

/login = anon/admin/* = authc

拦挡/login申请,通过AnonymousFilter过滤器,咱们能够看下

  • org.apache.shiro.web.filter.authc.AnonymousFilter
public class AnonymousFilter extends PathMatchingFilter {    /**     * 公众号:MarkerHub    **/    @Override    protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) {        // Always return true since we allow access to anyone        return true;    }}

AnonymousFilter重写了onPreHandle办法,只不过间接返回了true,阐明拦挡的链接能够间接通过,不须要其余拦挡逻辑。

而authc->FormAuthenticationFilter也是间接继承了PathMatchingFilter。

public class FormAuthenticationFilter extends AuthenticatingFilter

所以,须要拦挡某个链接进行业务逻辑过滤的能够继承PathMatchingFilter办法拓展哈。

6、AccessControlFilter

访问控制过滤器。继承PathMatchingFilter过滤器,重写onPreHandle办法,又分出了两个形象办法来管制

  • isAccessAllowed 是否容许拜访
  • onAccessDenied 是否回绝拜访

所以,咱们当初能够通过重写这个形象两个办法来管制过滤逻辑。另外多提供了3个办法,不便前面的过滤器应用。

/** * 公众号:MarkerHub**/protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {    saveRequest(request);    redirectToLogin(request, response);}protected void saveRequest(ServletRequest request) {    WebUtils.saveRequest(request);}protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {    String loginUrl = getLoginUrl();    WebUtils.issueRedirect(request, response, loginUrl);}

其中redirectToLogin提供了调整到登录页面的逻辑与实现,为前面的过滤器发现未登录跳转到登录页面提供了根底。

这个过滤器,咱们能够灵活运用。

7、AuthenticationFilter

继承AccessControlFilter,重写了isAccessAllowed办法,通过判断用户是否曾经实现登录来判断用户是否容许持续前面的逻辑判断。这里能够看出,从这个过滤器开始,后续的判断会与用户的登录状态相干,间接继承这些过滤器,咱们不须要再本人手动去判断用户是否曾经登录。并且提供了登录胜利之后跳转的办法。

public abstract class AuthenticationFilter extends AccessControlFilter {    public void setSuccessUrl(String successUrl) {        this.successUrl = successUrl;    }    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {        Subject subject = getSubject(request, response);        return subject.isAuthenticated();    }}

8、AuthenticatingFilter

继承AuthenticationFilter,提供了主动登录、是否登录申请等办法。

/** * 公众号:MarkerHub**/public abstract class AuthenticatingFilter extends AuthenticationFilter {    public static final String PERMISSIVE = "permissive";    //TODO - complete JavaDoc    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {        AuthenticationToken token = createToken(request, response);        if (token == null) {            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +                    "must be created in order to execute a login attempt.";            throw new IllegalStateException(msg);        }        try {            Subject subject = getSubject(request, response);            subject.login(token);            return onLoginSuccess(token, subject, request, response);        } catch (AuthenticationException e) {            return onLoginFailure(token, e, request, response);        }    }    protected abstract AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception;    /**     * 公众号:MarkerHub    **/    @Override    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {        return super.isAccessAllowed(request, response, mappedValue) ||                (!isLoginRequest(request, response) && isPermissive(mappedValue));    }    ...}
  • executeLogin 执行登录
  • onLoginSuccess 登录胜利跳转
  • onLoginFailure 登录失败跳转
  • createToken 创立登录的身份token
  • isAccessAllowed 是否容许被拜访
  • isLoginRequest 是否登录申请

这个办法提供了主动登录的课程,比方咱们获取到token之后履行主动登录,这场景还是很场景的。

比方在开源我的项目renren-fast中,就是这样解决的:

  • https://gitee.com/renrenio/re...
public class OAuth2Filter extends AuthenticatingFilter {    @Override    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {        //获取申请token        String token = getRequestToken((HttpServletRequest) request);        if(StringUtils.isBlank(token)){            return null;        }        return new OAuth2Token(token);    }    @Override    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {        if(((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())){            return true;        }        return false;    }    @Override    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {        //获取申请token,如果token不存在,间接返回401        String token = getRequestToken((HttpServletRequest) request);        if(StringUtils.isBlank(token)){            HttpServletResponse httpResponse = (HttpServletResponse) response;            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");            httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());            String json = new Gson().toJson(R.error(HttpStatus.SC_UNAUTHORIZED, "invalid token"));            httpResponse.getWriter().print(json);            return false;        }        return executeLogin(request, response);    }/** *公众号:MarkerHub**/    @Override    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {        HttpServletResponse httpResponse = (HttpServletResponse) response;        httpResponse.setContentType("application/json;charset=utf-8");        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");        httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());        try {            //解决登录失败的异样            Throwable throwable = e.getCause() == null ? e : e.getCause();            R r = R.error(HttpStatus.SC_UNAUTHORIZED, throwable.getMessage());            String json = new Gson().toJson(r);            httpResponse.getWriter().print(json);        } catch (IOException e1) {        }        return false;    }    /**     * 获取申请的token     */    private String getRequestToken(HttpServletRequest httpRequest){        //从header中获取token        String token = httpRequest.getHeader("token");        //如果header中不存在token,则从参数中获取token        if(StringUtils.isBlank(token)){            token = httpRequest.getParameter("token");        }        return token;    }}

onAccessDenied办法校验通过之后执行executeLogin办法实现主动登录!

9、FormAuthenticationFilter

基于form表单的账号密码主动登录的过滤器,咱们只须要看这个办法就明确,和renren-fast的实现类似:

public class FormAuthenticationFilter extends AuthenticatingFilter {    public static final String DEFAULT_USERNAME_PARAM = "username";    public static final String DEFAULT_PASSWORD_PARAM = "password";    public static final String DEFAULT_REMEMBER_ME_PARAM = "rememberMe";    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {        String username = getUsername(request);        String password = getPassword(request);        return createToken(username, password, request, response);    }    /**     * 公众号:MarkerHub    **/    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {        if (isLoginRequest(request, response)) {            if (isLoginSubmission(request, response)) {                if (log.isTraceEnabled()) {                    log.trace("Login submission detected.  Attempting to execute login.");                }                return executeLogin(request, response);            } else {                if (log.isTraceEnabled()) {                    log.trace("Login page view.");                }                //allow them to see the login page ;)                return true;            }        } else {            if (log.isTraceEnabled()) {                log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +                        "Authentication url [" + getLoginUrl() + "]");            }            saveRequestAndRedirectToLogin(request, response);            return false;        }    }}

onAccessDenied调用executeLogin办法。默认的token是UsernamepasswordToken。

结束语

好了,明天先到这里啦,讲了多好内置的过滤器,代码有点多,你们能够用电脑关上文章,而后认真钻研,并回忆本人应用shiro过滤器的时候,是不是和我讲的场景一样,联合起来。

这里是MarkerHub,我是吕一明,感激关注与反对!

参考:

https://www.cnblogs.com/LeeSc...


(完)

举荐浏览

Java笔记大全.md

太赞了,这个Java网站,什么我的项目都有!https://markerhub.com

这个B站的UP主,讲的java真不错!