Apache Shiro源码 拦截器过程

Apache Shiro是Java的一个安全框架,使用的人也越来越多,但很多人只是停留在了会使用的的阶段,可能配置文件也只是网上demo的复制修改,却不知道真正的含义是什么。

今天我用shiro的拦截器为入口,简单的过一点shiro的源码 ,有错误的地方希望大家能给指出。

首先我们看一下配置文件中拦截器的拦截规则,这里有两个拦截器,一个是formAuthenticationFilter 这个是shiro 包自带的一个拦截器,roleOrFilter是自定义的一个拦截器。对于/api/user/login 这个接口不做拦截。对/api/user/check 需要过两个拦截器。

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <property name="loginUrl" value="/login.html"/>
    <property name="unauthorizedUrl" value="/error.html"/>
    <property name="filters">

    <util:map>
        <entry key="authc" value-ref="formAuthenticationFilter"/>
        <entry key="roleOrFilter" value-ref="roleOrFilter"/>
    </util:map>
    </property>

    <property name="filterChainDefinitions">
        <value>
            /api/user/login = anon
            /api/user/check = authc, roleOrFilter["管理员"]
        </value>
    </property>
</bean>

当我在URL地址栏输入 http://127.0.0.1:8080/api/user/check,并点击回车的时候,拦截器就开始工作了。首先是到了authc这个拦截器。这个拦截器继承了 AuthenticatingFilter

程序在运行一定的时候会进入到onPrehandle,这个方法结果的返回会决定我们的请求是否被允许

//只要isAccessAllowed或者onAccessDenied有一个为真,就返回true,继续执行
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}

先看isAccessAllowed这个方法

/**如果满足(1).当前的subject是被认证过的。(2).用户请求的不是登录页面,但是在定义该过滤器时,使用了PERMISSIVE=”permissive”参数。只要满足两个条件的一个即可允许操作**/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return super.isAccessAllowed(request, response, mappedValue) ||(!isLoginRequest(request, response) && isPermissive(mappedValue));
    }


//其实就是判断当前的subject是不是被认证过的
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        Subject subject = getSubject(request, response);
        return subject.isAuthenticated();
    }

//判断当前的请求是不是登陆请求
protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
        return pathsMatch(getLoginUrl(), request);
    }

//判断当前的拦截器是不是配置了PERMISSIVE=”permissive”参数,如果配置了就可以通过
protected boolean isPermissive(Object mappedValue) {
        if(mappedValue != null) {
            String[] values = (String[]) mappedValue;
            return Arrays.binarySearch(values, PERMISSIVE) >= 0;
        }
        return false;
    }

因为我们没有登陆,即没有对当前的subject认证过,isAccessAllowed返回false。

再看onAccessDenied

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;
    }
}


//判断当前的其实是不是一个HTTP的POST请求
protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {
        return (request instanceof HttpServletRequest) && WebUtils.toHttp(request).getMethod().equalsIgnoreCase(POST_METHOD);
    }

//经过前面的判断是不是POST请求后,程序就为我们创建一个token,但是我们并没有传入userName和password在创建的时候就会抛出异常了
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);
        }
    }


/**saveRequest就是把一个request保存在session中,redirectToLogin这里就是返回到设置的登录页面,在开头自定义的过滤器中就是重载了这个函数,在实际项目中,一般都会重载这个函数,比方说返回到指定的页面*/
protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        saveRequest(request);
        redirectToLogin(request, response);
    }

就这样在第一个authc拦截器请求就会被拒绝了。就不会走我们的第二个roleOrFilter拦截器了。

那如果我在配置文件上不写上authc,而是让程序直接走我们的自定义的拦截器那会是什么过程呢。

public class RoleOrFilter extends AuthorizationFilter {
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        Subject subject = getSubject(servletRequest, servletResponse);
        String[] rolesArray = (String[]) o;
        if (rolesArray == null || rolesArray.length == 0) {
            return true;
        }
        for (int i = 0; i < rolesArray.length; i++) {
            if (subject.hasRole(rolesArray[i])) {
                return true;
            }
        }
        return false;
    }
}

其实在走到hasRole的时候,hasRole内部实现代码机制就会有检测当前的subject是不是被认证的操作,所以你没有登陆就直接调用一个接口,就算没有写authc直接到第二个拦截器也会被拦下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值