Apache Shiro原始碼 攔截器過程
阿新 • • 發佈:2019-01-26
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直接到第二個攔截器也會被攔下。