shiro内置了許多過濾器用來控制認證授權
anon : org.apache.shiro.web.filter.authc.AnonymousFilter
authc : org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic : org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
perms : org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port : org.apache.shiro.web.filter.authz.PortFilter
rest : org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles : org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl : org.apache.shiro.web.filter.authz.SslFilter
user : org.apache.shiro.web.filter.authc.UserFilter
其中認證是否已登入FormAuthenticationFilter
認證是否授權(角色、權限)RolesAuthorizationFilter,PermissionsAuthorizationFilter
(以下以RolesAuthorizationFilter為例)
核心都繼承自AccessControlFilter
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2QvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2LcZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39zMzEzNwEzM1EjNxATM1EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
左邊是認證登入,右邊是認證授權。注意紅框位置FormAuthenticationFilter 和 RolesAuthorizationFilter 都通過了過渡類繼承自AccessControlFilter
簡單介紹下這個類的幾個方法
onPreHandle:判斷認證是否通過,以及後續處理上代碼:
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
isAccessAllowed:判斷認證是否通過(FormAuthenticationFilter中是認證是否已登入,RolesAuthorizationFilter是認證是否已授權)
onAccessDenied:認證失敗的後續處理
isLoginRequest:判斷是否是登入請求,登入位址在配置檔案中已配:
<!--未登入狀态下通路authc則進入loginUrl配置的路徑-->
<property name="loginUrl" value="/login"></property>
<!--登入成功後跳轉的預設位址-->
<property name="successUrl" value="/main"></property>
<!--如果您請求的資源不再您的權限範圍,則跳轉到/400請求位址 -->
<property name="unauthorizedUrl" value="400"></property>
saveRequest:儲存目前請求
redirectToLogin:重定向到登入頁面
saveRequestAndRedirectToLogin:儲存目前請求并重定向到登入頁面
認證登入流程按照繼承關系:
AccessControlFilter
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
AuthenticatingFilter(判斷是否認證通過,将調用父類的isAccessAllowed)
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return super.isAccessAllowed(request, response, mappedValue) ||
(!isLoginRequest(request, response) && isPermissive(mappedValue));
}
AuthenticationFilter(在這裡真正判斷是否已登入)
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
Subject subject = getSubject(request, response);
return subject.isAuthenticated();
}
登入的話,直接跳轉,如果未登入:
FormAuthenticationFilter
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;
}
} 首先判斷是否是登入請求,如果是get形式登入頁面跳轉繼續過濾器鍊,如果是post表單送出執行executeLogin方法,
将走realm中的doGetAuthenticationInfo方法。
ps:執行executeLogin方法過程中會生成AuthenticationToken,具體代碼:
protected AuthenticationToken createToken(String username, String password,
ServletRequest request, ServletResponse response) {
boolean rememberMe = isRememberMe(request);
String host = getHost(request);
return createToken(username, password, rememberMe, host);
} 預設傳username,password,是以前台頁面name屬性要一一對應。
登入成功或失敗會走onLoginSuccess或onLoginFailure
如果不是登入請求則儲存目前請求跳轉至登入頁面,如果想封裝ajax請求的shiro處理,可自定義自己的FormAuthenticationFilter
認證授權流程按照繼承關系
AccessControlFilter
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
} RolesAuthorizationFilter(認證是否有權限,shiro預設是需要所有角色才認證通過的)
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue;
if (rolesArray == null || rolesArray.length == 0) {
//no roles specified, so nothing to check - allow access.
return true;
}
Set<String> roles = CollectionUtils.asSet(rolesArray);
return subject.hasAllRoles(roles);
}
AuthorizationFilter(認證角色未通過後的處理)
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
Subject subject = getSubject(request, response);
// If the subject isn't identified, redirect to login URL
if (subject.getPrincipal() == null) {
saveRequestAndRedirectToLogin(request, response);
} else {
// If subject is known but not authorized, redirect to the unauthorized URL if there is one
// If no unauthorized URL is specified, just return an unauthorized HTTP status code
String unauthorizedUrl = getUnauthorizedUrl();
//SHIRO-142 - ensure that redirect _or_ error code occurs - both cannot happen due to response commit:
if (StringUtils.hasText(unauthorizedUrl)) {
WebUtils.issueRedirect(request, response, unauthorizedUrl);
} else {
WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
return false;
}
如果未登入,儲存目前請求跳轉至登入頁面,否則判斷是否有配置認證權限未通過須要跳轉的請求,如果配置了則跳轉,否則抛出錯誤碼SC_UNAUTHORIZED(401),可在web.xml中捕獲該錯誤碼自己定義要跳轉的頁面。
這是我了解的shiro認證授權政策,其中認證授權前後所要做的處理都可以通過繼承相應類重制方法來達到自定義效果,無需在contrller層寫多餘的業務邏輯代碼,shiro的這些接口和相關配置檔案都可以幫你搞定!
最後需要在shiro配置檔案中配置
01 < bean id = "shiroFilter" class = "org.apache.shiro.spring.web.ShiroFilterFactoryBean" >
02 < property name = "securityManager" ref = "securityManager" />
03 <!-- 未登入狀态下通路 authc 則進入 loginUrl 配置的路徑 -->
04 < property name = "loginUrl" value = "/login" ></ property >
05 <!-- 登入成功後跳轉的預設位址 -->
06 < property name = "successUrl" value = "/main" ></ property >
07 <!-- 如果您請求的資源不再您的權限範圍,則跳轉到 / 400 請求位址 -->
08 < property name = "unauthorizedUrl" value = "400" ></ property >
09 <property name="filters">
10 <map>
11 <entry key="authc">
12 <bean class="com.luming.shiro.MyFormAuthenticationFilter"/>
13 </entry>
14 <entry key="roles">
15 <bean class="com.luming.shiro.MyRolesAuthorizationFilter"/>
16 </entry>
17 </map>
18 </property>
19 </ bean >
MyFormAuthenticationFilter繼承自FormAuthenticationFilter
MyRolesAuthorizationFilter繼承自RolesAuthorizationFilter
最後基于自身項目的shiro自定義認證授權還須要結合自定義的realm:shiro認證授權
因為JdbcRealm的doGetAuthenticationInfo定義了如何對使用者送出的資料進行認證,doGetAuthorizationInfo定義了授予使用者何種權限,
而FormAuthenticationFilter和RolesAuthorizationFilter主要是負責認證及授權的前後須要做的一些自定義操作,比如IP過濾等