在使用shiro的时候,对于用户权限的管理,相信很多人都已经很熟悉了。
今天,我这里简单的记录一下我自己调试过程中遇到的问题。主要是登录的操作,禁止通过ajax的方式进行访问。
shiro中,登录过程拒绝ajax的访问操作,主要在FormAuthenticationFilter里面实现的。更具体的说,应该是自己重写AccessControlFilter方法中的onAccessDenied方法。
为什么要禁止ajax的登录?安全的考虑!
先看看自己应用中重写的RdFormAuthenticationFilter,它继承于FormAuthenticationFilter,至于FormAuthenticationFilter和AccessControlFilter的关系,自己看shiro的源码吧。
public class RdFormAuthenticationFilter extends FormAuthenticationFilter { private String passwordParam; @Autowired @Qualifier("mqmKefuService") private IMqmKefuService mksService; @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { if (request.getAttribute(getFailureKeyAttribute()) != null) { return true; } HttpServletRequest httpServletRequest = (HttpServletRequest)request; if (isLoginSubmission(request, response)) { return executeLogin(request, response); } else { if ("XMLHttpRequest".equalsIgnoreCase(httpServletRequest.getHeader("X-Requested-With")) || request.getParameter("ajax") != null) { HttpServletResponse res = WebUtils.toHttp(response); res.sendError(HttpServletResponse.SC_UNAUTHORIZED); } return true; } } @Override protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpServletRequest = (HttpServletRequest)request; HttpServletResponse httpServletResponse = (HttpServletResponse)response; String username = (String)SecurityUtils.getSubject().getPrincipal(); if(username == null){ return true; } MqmKefu user = mksService.selectByUsername(username); SecurityUtils.getSubject().getSession().setAttribute(Constants.CURRENT_USER, user); if ("XMLHttpRequest".equalsIgnoreCase(httpServletRequest.getHeader("X-Requested-With")) || request.getParameter("ajax") != null) { // 是ajax请求 httpServletRequest.getRequestDispatcher("/login/timeout/success").forward(httpServletRequest, httpServletResponse); } else { //httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + this.getSuccessUrl()); String redirectUrl = httpServletRequest.getContextPath() + "/home"; SavedRequest sr = WebUtils.getSavedRequest(request); //当用户地址的请求不是在地址栏输入,而是在页面上直接点击登录,那么SavedRequest返回值将会是空的。 if(sr != null) { redirectUrl = sr.getRequestUrl(); } httpServletResponse.sendRedirect(redirectUrl); } return false; } public String getPasswordParam() { return passwordParam; } public void setPasswordParam(String passwordParam) { this.passwordParam = passwordParam; } }
if ("XMLHttpRequest".equalsIgnoreCase(httpServletRequest.getHeader("X-Requested-With")) || request.getParameter("ajax") != null) { HttpServletResponse res = WebUtils.toHttp(response); res.sendError(HttpServletResponse.SC_UNAUTHORIZED); }
如何测试呢?很简单!
1. 将shiro的配置中,重写LogoutFilter,并将其redirectUrl的值配置为自己想要的内容,而不是原来默认的DEFAULT_REDIRECT_URL(值为/)。例如,将redirectUrl改为登录的url,这里是/usr/login.
2. 将前端页面的退出,一种用ajax的post或者get方式触发,一种用href直接配置url。
2.1 针对退出url (/usr/logout)以ajax的方式触发事件到后台,代码逻辑将会进入上述代码AAAA,且此时HTTP请求头部含有X-Requested-With。前端将收到401的错误,页面不跳转。
2.2 针对退出url以a标签的href方式,直接由浏览器以http调用后台服务的方式,则不会进入AAAA的代码逻辑。
针对上述的LogoutFilter的重写,可以看看配置文件:
<bean id="sysUserFilter" class="com.roomdis.mqr.infra.shiro.SysUserFilter"/> <bean id="sysLogoutFilter" class="com.roomdis.mqr.infra.shiro.RdLogoutFilter"> <property name="redirectUrl" value="/user/login"/> </bean> <bean id="jCaptchaValidateFilter" class="com.roomdis.mqr.infra.shiro.JCaptchaValidateFilter"> <property name="jcaptchaEbabled" value="true"/> <property name="jcaptchaParam" value="jcaptchaCode"/> <property name="failureKeyAttribute" value="shiroLoginFailure"/> </bean> <bean id="filterChainManager" class="com.roomdis.mqr.infra.shiro.RdDefaultFilterChainManager"> <property name="loginUrl" value="/user/login" /> <property name="successUrl" value="/home" /> <property name="unauthorizedUrl" value="/unauth" /> <property name="customFilters"> <util:map> <entry key="authc" value-ref="authcFilter"/> <entry key="sysUser" value-ref="sysUserFilter"/> <entry key="jCaptchaValidate" value-ref="jCaptchaValidateFilter"/> <entry key="logout" value-ref="sysLogoutFilter" /> </util:map> </property> <property name="defaultFilterChainDefinitions"> <value> / = anon /*.png = anon /css/* = anon /js/** = anon /image/* = anon /index.html = anon /user/login = jCaptchaValidate,authc /jcaptcha* = anon /jcaptcha.jsp = anon /home = sysUser,user /logout = logout <!-- /home = authc, perms[/home] perms 表示需要该权限才能访问的页面 --> </value> </property> </bean>
上述配置的红色部分,是重写LogoutFilter的对应bean的信息。
蓝色部分,要特别注意,logout的url,权限部分处理,必须是和自己代码逻辑中的重定向部分一致使用。例如这里的/logout,对应代码逻辑中的地方(红色)如下:
/** * logout 即常说的登出操作 * * @param req * @param rsp * @param model * @return */ @GET @Path("/user/logout") @Produces(MediaType.APPLICATION_JSON) public void logout(@Context HttpServletRequest req, @Context HttpServletResponse rsp){ Map<String, Object> infoMap = new HashMap<String, Object>(); String basePath = basePath(req); String usernamep = req.getParameter("username"); infoMap.put("basePath", basePath); SecurityUtils.getSubject().getSession().removeAttribute(Constants.CURRENT_USER); try { rsp.sendRedirect(req.getContextPath() + "/logout"); } catch (IOException e) { e.printStackTrace(); } }
前端的代码:
<p class="checkmove"> <span class="fl sp_title"> <#if username??> <label id="username">${username}</label> <a class=logout-btn href="${basePath}/user/logout">退出</a> #能够正常运行,即不会触发401的错误的用法 <!-- <button class=button id="leave">退出</button> #这样子用,配合后台的ajax的方式触发,就会出现401的错误 --> </#if> <select> <option value="0">上线</option> <option value="1">离开</option> <option value="2">隐身</option> <option value="3">下线</option> </select> </span> </p>
下面上一个图,看看我的项目雏形!