1. 程式人生 > >Apache Shiro原始碼 攔截器過程

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直接到第二個攔截器也會被攔下。