上一個章節我們學習瞭如何自定義自己的filter,這個只是爲了這一章打基礎;相信我們這一羣shiro使用者比較關注異步請求認證失敗會如何處理這個問題,確實我們現在的項目很大一部分請求都是異步的,所以這個問題是無可避免,我看了網上很多資料都是沒有完整地給出擴展方案,下面我把自己的處理方案給展示下,如有不爽,請勿跨省,家無水錶,不收快遞...
直接進入主題,先看看我們之前的配置,自定義一個RoleAuthorizationFilter
<!-- 過濾鏈配置 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/" />
<property name="successUrl" value="/cms/index.do" />
<property name="unauthorizedUrl" value="/" />
<property name="filters">
<map>
<entry key="role">
<bean
class="com.silvery.security.shiro.filter.RoleAuthorizationFilter" />
</entry>
<entry key="authc">
<bean
class="com.silvery.security.shiro.filter.SimpleFormAuthenticationFilter" />
</entry>
</map>
</property>
</bean>
<!-- 權限資源配置 -->
<bean id="filterChainDefinitionsService"
class="com.silvery.security.shiro.service.impl.SimpleFilterChainDefinitionsService">
<property name="definitions">
<value>
/static/** = anon
/admin/user/login.do = anon
/test/** = role[admin]
/abc/** = authc
</value>
</property>
</bean>
public class RoleAuthorizationFilter extends AuthorizationFilter {
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);
for (String role : roles) {
if (subject.hasRole(role)) {
return true;
}
}
return false;
}
}
我們先看看這個源碼,發現是繼承了AuthorizationFilter類,然後只重寫了isAccessAllowed方法,然後我們就想isAccessAllowed是判斷是否擁有權限,那肯定會有一個方法是認證失敗回調的方法,這是框架一貫的做法,來驗證下我們的想當然是不是正確的,我們打開AuthorizationFilter類的源碼看看
public abstract class AuthorizationFilter extends AccessControlFilter
{
public AuthorizationFilter()
{
}
public String getUnauthorizedUrl()
{
return unauthorizedUrl;
}
public void setUnauthorizedUrl(String unauthorizedUrl)
{
this.unauthorizedUrl = unauthorizedUrl;
}
protected boolean onAccessDenied(ServletRequest request, ServletResponse response)
throws IOException
{
Subject subject = getSubject(request, response);
if(subject.getPrincipal() == null)
{
saveRequestAndRedirectToLogin(request, response);
} else
{
String unauthorizedUrl = getUnauthorizedUrl();
if(StringUtils.hasText(unauthorizedUrl))
WebUtils.issueRedirect(request, response, unauthorizedUrl);
else
WebUtils.toHttp(response).sendError(401);
}
return false;
}
private String unauthorizedUrl;
}
看到源碼的時候我就很開心地賤笑了,果然是我想的那樣,我們很明顯地看到一個方法onAccessDenied,認證失敗處理,邏輯就是如果登錄實體爲null就保存請求和跳轉登錄頁面,否則就跳轉無權限配置頁面
我們開始動手改造這個方法,把這個方法也在我們自己的RoleAuthorizationFilter裏重寫下
/**
*
* 1.自定義角色鑑權過濾器(滿足其中一個角色則認證通過) 2.擴展異步請求認證提示功能;
*
* @author shadow
*
*/
public class RoleAuthorizationFilter extends AuthorizationFilter {
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
Subject subject = getSubject(request, response);
if (subject.getPrincipal() == null) {
if (com.silvery.utils.WebUtils.isAjax(httpRequest)) {
com.silvery.utils.WebUtils.sendJson(httpResponse, JsonUtils.toJSONString(new ViewResult(false,
"您尚未登錄或登錄時間過長,請重新登錄!")));
} else {
saveRequestAndRedirectToLogin(request, response);
}
} else {
if (com.silvery.utils.WebUtils.isAjax(httpRequest)) {
com.silvery.utils.WebUtils.sendJson(httpResponse, JsonUtils.toJSONString(new ViewResult(false,
"您沒有足夠的權限執行該操作!")));
} else {
String unauthorizedUrl = getUnauthorizedUrl();
if (StringUtils.hasText(unauthorizedUrl)) {
WebUtils.issueRedirect(request, response, unauthorizedUrl);
} else {
WebUtils.toHttp(response).sendError(401);
}
}
}
return false;
}
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);
for (String role : roles) {
if (subject.hasRole(role)) {
return true;
}
}
return false;
}
}
其實改造也很簡單,只是再加一層ajax的判斷,至於如何判斷ajax就是自己個人的方式,有的項目喜歡加一個標識參數,有的人喜歡直接用header裏面的X-Requested-With參數,這個看自己的需求咯,我個人喜歡是ajax請求認證失敗是返回一串標準的json格式字符串,頁面兼容處理也方便
下面我們測試下如何效果,先寫一個html,配置/test/a.do是不夠權限請求的
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>test ajax request</title>
<script type="text/javascript" src="/static/plugin/jquery/core.js"></script>
<script>
function test(){
$.post('/test/a.do','',function(result){
alert(result);
});
}
</script>
</head>
<body>
<input type="button" value="click" οnclick="test();" />
</body>
</html>
html寫完,就跑起項目,然後先別登錄系統,直接打開html點擊下這個按鈕,發現alert提示{"message":"您尚未登錄或登錄時間過長,請重新登錄!","success":false,"value":null};
然後再登錄系統,再點擊這個按鈕請求一次看看發現alert提示{"message":"您沒有足夠的權限執行該操作!","success":false,"value":null};很明顯尚未登錄和權限不足的ajax時候提示都完美地出現,然後這章節我就可以功成身退了
最後總結下擴展方案,其實shiro的所有filter都是有統一的接口方法,你們可以看看這真實過濾器都是繼承了相同的父級filter,所以其他的filter也可以通過繼承重寫onAccessDenied方法提供我們的異步請求分支處理
歡迎拍磚...