springMVC+shiro的权限管理和异常统一处理
SpringMVC+shiro的配置请参照网上其他博文,本文主要介绍如何使用shiro,动态管控用户的url请求。如何自定义filter,让前端页面通过返回的异常状态码进行统一的弹窗提示。
使用shiro后发现,当请求不符合过滤器要求时,会自动跳转到unauthorizedUrl页面,而我想实现的是通过后端返回不同的异常状态码,前端ajax判断后进行弹窗提示,这样用户体验上会更友好一些。具体做法是通过自定义shiro的filter来实现的。
首先在shiro的配置文件中配置自定义的filter:
<bean id="loginFilter" class="com.cmcc.hygcc.comm.shiro.LoginFilter"></bean>
<bean id="myFilter" class="com.cmcc.hygcc.comm.shiro.MyFilter"></bean>
<!-- Shiro主过滤器本身功能十分强大,其强大之处就在于它支持任何基于URL路径表达式的、自定义的过滤器的执行 -->
<!-- Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截,Shiro对基于Spring的Web应用提供了完美的支持 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager" />
<!-- 要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面 -->
<property name="loginUrl" value="/" />
<!-- 登录成功后要跳转的连接(本例中此属性用不到,因为登录成功后的处理逻辑在LoginController里硬编码为main.jsp了) -->
<property name="successUrl" value="/index.htm" />
<!-- 用户访问未对其授权的资源时,所显示的连接 -->
<!-- 若想更明显的测试此属性可以修改它的值,如unauthor.jsp,然后用[玄玉]登录后访问/admin/listUser.jsp就看见浏览器会显示unauthor.jsp -->
<property name="unauthorizedUrl" value="/reLogin" />
<property name="filters">
<map>
<entry key="myFilter" value-ref="myFilter" />
<!-- 覆盖authc过滤器,使得未登录的ajax请求返回401状态 -->
<entry key="authc" value-ref="loginFilter" />
</map>
</property>
<!-- Shiro连接约束配置,即过滤链的定义 -->
<!-- 此处可配合我的这篇文章来理解各个过滤连的作用http://blog.csdn.net/jadyer/article/details/12172839 -->
<!-- 下面value值的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的 -->
<!-- anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种 -->
<!-- authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter -->
<property name="filterChainDefinitions">
<value>
/=anon
/font/**=anon
/login.jsp=anon
/jcaptcha*=anon
/images/**=anon
/js/**=anon
/css/**=anon
/system/getSalt*=anon
/loginCheck=anon
/login=anon
/druid=anon
/reLogin=anon
/index.htm=authc
/logout.htm=authc
/system/navigation/get=authc
/dics/list=authc
/**=myFilter
</value>
</property>
</bean>
其中包含两个filter,loginFilter和MyFilter,loginFilter主要是覆盖了自带的authc过滤器,让未登录的请求统一返回401。MyFilter是用于过滤需要权限校验的请求。
import java.io.IOException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.cmcc.hygcc.comm.dao.CommonDao;
/**
* @Type LoginFilter.java
* @Desc 用于自定义过滤器,过滤用户请求时是否是登录状态
* @author Zero
* @date 2017年2月6日 上午9:06:15
* @version
*/
public class LoginFilter extends AuthorizationFilter {
@Autowired
public CommonDao dao;
@Override
protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object arg2)
throws Exception {
Subject subject = getSubject(req, resp);
if (null != subject.getPrincipals()) {
return true;
}
return false;
}
/**
* 会话超时或权限校验未通过的,统一返回401,由前端页面弹窗提示
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response)
throws IOException {
if (isAjax((HttpServletRequest) request)) {
WebUtils.toHttp(response).sendError(401);
} else {
String unauthorizedUrl = getUnauthorizedUrl();
if (StringUtils.hasText(unauthorizedUrl)) {
WebUtils.issueRedirect(request, response, unauthorizedUrl);
} else {
WebUtils.toHttp(response).sendError(401);
}
}
return false;
}
private boolean isAjax(HttpServletRequest request) {
String header = request.getHeader("x-requested-with");
if (null != header && "XMLHttpRequest".endsWith(header)) {
return true;
}
return false;
}
}
import java.io.IOException;
import java.util.Set;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.cmcc.hygcc.comm.dao.CommonDao;
/**
* @Type MyFilter.java
* @Desc 用于自定义过滤器,过滤用户请求是否被授权
* @author Zero
* @date 2017年2月6日 上午9:06:15
* @version
*/
public class MyFilter extends AuthorizationFilter {
@Autowired
public CommonDao dao;
@SuppressWarnings("unchecked")
@Override
protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object arg2)
throws Exception {
HttpServletRequest request = (HttpServletRequest) req;
//获取请求路径
String path = request.getServletPath();
Subject subject = getSubject(req, resp);
if (null != subject.getPrincipals()) {
//根据session中存放的用户权限,比对路径,如果拥有该权限则放行
Set<String> userPrivileges = (Set<String>) request.getSession()
.getAttribute("USER_PRIVILEGES");
if (null != userPrivileges && userPrivileges.contains(path)) {
return true;
}
}
return false;
}
/**
* 会话超时或权限校验未通过的,统一返回401,由前端页面弹窗提示
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response)
throws IOException {
if (isAjax((HttpServletRequest) request)) {
WebUtils.toHttp(response).sendError(401);
} else {
String unauthorizedUrl = getUnauthorizedUrl();
if (StringUtils.hasText(unauthorizedUrl)) {
WebUtils.issueRedirect(request, response, unauthorizedUrl);
} else {
WebUtils.toHttp(response).sendError(401);
}
}
return false;
}
private boolean isAjax(HttpServletRequest request) {
String header = request.getHeader("x-requested-with");
if (null != header && "XMLHttpRequest".endsWith(header)) {
return true;
}
return false;
}
}
主要的判断逻辑如下:当收到ajax请求,且该请求未授权时,返回错误码401。下面贴出前端ajax的处理代码:
$.ajaxSetup({
cache:false,
error : function(XMLHttpRequest, textStatus,errorThrown) {
switch (XMLHttpRequest.status){
case(401):
tempAlert("会话超时或访问未授权,即将跳转到登录页!");
setInterval("toLoginPage()",3000);
break;
default:
tempAlert("网络连接已断开!");
}
}
});
由于前端有部分超链接,对超链接也做了ajax的封装,每个超链接通过onclick方法调用ajaxHref函数,代码如下。这样就可以实现不论前端页面是超链接或是ajax请求,在请求未授权时,页面都会统一弹出确认窗口,并自动跳转到登录页。
function ajaxHref(url){
$.ajax({
type : 'GET',
url : url,
async : false,
success:function(){
window.location.href=url;
}
});
}